memoryblock 0.0.1 → 0.1.0-beta
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/bin/mblk.js +6 -0
- package/dist/bin/mblk.d.ts +3 -0
- package/dist/bin/mblk.d.ts.map +1 -0
- package/dist/bin/mblk.js +339 -0
- package/dist/bin/mblk.js.map +1 -0
- package/dist/cli/commands/create.d.ts +2 -0
- package/dist/cli/commands/create.d.ts.map +1 -0
- package/dist/cli/commands/create.js +48 -0
- package/dist/cli/commands/create.js.map +1 -0
- package/dist/cli/commands/delete.d.ts +5 -0
- package/dist/cli/commands/delete.d.ts.map +1 -0
- package/dist/cli/commands/delete.js +147 -0
- package/dist/cli/commands/delete.js.map +1 -0
- package/dist/cli/commands/init.d.ts +9 -0
- package/dist/cli/commands/init.d.ts.map +1 -0
- package/dist/cli/commands/init.js +209 -0
- package/dist/cli/commands/init.js.map +1 -0
- package/dist/cli/commands/permissions.d.ts +13 -0
- package/dist/cli/commands/permissions.d.ts.map +1 -0
- package/dist/cli/commands/permissions.js +60 -0
- package/dist/cli/commands/permissions.js.map +1 -0
- package/dist/cli/commands/plugin-settings.d.ts +6 -0
- package/dist/cli/commands/plugin-settings.d.ts.map +1 -0
- package/dist/cli/commands/plugin-settings.js +118 -0
- package/dist/cli/commands/plugin-settings.js.map +1 -0
- package/dist/cli/commands/plugins.d.ts +3 -0
- package/dist/cli/commands/plugins.d.ts.map +1 -0
- package/dist/cli/commands/plugins.js +83 -0
- package/dist/cli/commands/plugins.js.map +1 -0
- package/dist/cli/commands/reset.d.ts +8 -0
- package/dist/cli/commands/reset.d.ts.map +1 -0
- package/dist/cli/commands/reset.js +96 -0
- package/dist/cli/commands/reset.js.map +1 -0
- package/dist/cli/commands/server.d.ts +25 -0
- package/dist/cli/commands/server.d.ts.map +1 -0
- package/dist/cli/commands/server.js +295 -0
- package/dist/cli/commands/server.js.map +1 -0
- package/dist/cli/commands/service.d.ts +18 -0
- package/dist/cli/commands/service.d.ts.map +1 -0
- package/dist/cli/commands/service.js +309 -0
- package/dist/cli/commands/service.js.map +1 -0
- package/dist/cli/commands/start.d.ts +11 -0
- package/dist/cli/commands/start.d.ts.map +1 -0
- package/dist/cli/commands/start.js +801 -0
- package/dist/cli/commands/start.js.map +1 -0
- package/dist/cli/commands/status.d.ts +2 -0
- package/dist/cli/commands/status.d.ts.map +1 -0
- package/dist/cli/commands/status.js +78 -0
- package/dist/cli/commands/status.js.map +1 -0
- package/dist/cli/commands/stop.d.ts +9 -0
- package/dist/cli/commands/stop.d.ts.map +1 -0
- package/dist/cli/commands/stop.js +83 -0
- package/dist/cli/commands/stop.js.map +1 -0
- package/dist/cli/commands/web.d.ts +5 -0
- package/dist/cli/commands/web.d.ts.map +1 -0
- package/dist/cli/commands/web.js +63 -0
- package/dist/cli/commands/web.js.map +1 -0
- package/dist/cli/constants.d.ts +38 -0
- package/dist/cli/constants.d.ts.map +1 -0
- package/dist/cli/constants.js +80 -0
- package/dist/cli/constants.js.map +1 -0
- package/dist/cli/logger.d.ts +12 -0
- package/dist/cli/logger.d.ts.map +1 -0
- package/dist/cli/logger.js +40 -0
- package/dist/cli/logger.js.map +1 -0
- package/dist/engine/agent.d.ts +15 -0
- package/dist/engine/agent.d.ts.map +1 -0
- package/dist/engine/agent.js +19 -0
- package/dist/engine/agent.js.map +1 -0
- package/dist/engine/conversation-log.d.ts +35 -0
- package/dist/engine/conversation-log.d.ts.map +1 -0
- package/dist/engine/conversation-log.js +83 -0
- package/dist/engine/conversation-log.js.map +1 -0
- package/dist/engine/cost-tracker.d.ts +52 -0
- package/dist/engine/cost-tracker.d.ts.map +1 -0
- package/dist/engine/cost-tracker.js +110 -0
- package/dist/engine/cost-tracker.js.map +1 -0
- package/dist/engine/gatekeeper.d.ts +20 -0
- package/dist/engine/gatekeeper.d.ts.map +1 -0
- package/dist/engine/gatekeeper.js +43 -0
- package/dist/engine/gatekeeper.js.map +1 -0
- package/dist/engine/memory.d.ts +28 -0
- package/dist/engine/memory.d.ts.map +1 -0
- package/dist/engine/memory.js +69 -0
- package/dist/engine/memory.js.map +1 -0
- package/dist/engine/monitor.d.ts +81 -0
- package/dist/engine/monitor.d.ts.map +1 -0
- package/dist/engine/monitor.js +610 -0
- package/dist/engine/monitor.js.map +1 -0
- package/dist/engine/prompts.d.ts +31 -0
- package/dist/engine/prompts.d.ts.map +1 -0
- package/dist/engine/prompts.js +93 -0
- package/dist/engine/prompts.js.map +1 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +15 -0
- package/dist/index.js.map +1 -0
- package/dist/schemas.d.ts +544 -0
- package/dist/schemas.d.ts.map +1 -0
- package/dist/schemas.js +111 -0
- package/dist/schemas.js.map +1 -0
- package/dist/types.d.ts +188 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/dist/utils/config.d.ts +24 -0
- package/dist/utils/config.d.ts.map +1 -0
- package/dist/utils/config.js +86 -0
- package/dist/utils/config.js.map +1 -0
- package/dist/utils/fs.d.ts +18 -0
- package/dist/utils/fs.d.ts.map +1 -0
- package/dist/utils/fs.js +65 -0
- package/dist/utils/fs.js.map +1 -0
- package/package.json +37 -8
- package/src/bin/mblk.ts +347 -0
- package/src/cli/commands/create.ts +62 -0
- package/src/cli/commands/delete.ts +165 -0
- package/src/cli/commands/init.ts +241 -0
- package/src/cli/commands/permissions.ts +77 -0
- package/src/cli/commands/plugin-settings.ts +111 -0
- package/src/cli/commands/plugins.ts +89 -0
- package/src/cli/commands/reset.ts +97 -0
- package/src/cli/commands/server.ts +327 -0
- package/src/cli/commands/service.ts +300 -0
- package/src/cli/commands/start.ts +843 -0
- package/src/cli/commands/status.ts +90 -0
- package/src/cli/commands/stop.ts +91 -0
- package/src/cli/commands/web.ts +74 -0
- package/src/cli/constants.ts +88 -0
- package/src/cli/logger.ts +48 -0
- package/src/engine/agent.ts +19 -0
- package/src/engine/conversation-log.ts +90 -0
- package/src/engine/cost-tracker.ts +134 -0
- package/src/engine/gatekeeper.ts +53 -0
- package/src/engine/memory.ts +87 -0
- package/src/engine/monitor.ts +719 -0
- package/src/engine/prompts.ts +102 -0
- package/src/index.ts +88 -0
- package/src/schemas.ts +126 -0
- package/src/types.ts +220 -0
- package/src/utils/config.ts +106 -0
- package/src/utils/fs.ts +64 -0
- package/tsconfig.json +10 -0
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
import * as p from '@clack/prompts';
|
|
2
|
+
import chalk from 'chalk';
|
|
3
|
+
import { ensureDir, writeJson } from '../../utils/fs.js';
|
|
4
|
+
import {
|
|
5
|
+
getWsRoot, getConfigPath, getAuthPath, isInitialized,
|
|
6
|
+
} from '../../utils/config.js';
|
|
7
|
+
import { GlobalConfigSchema } from '../../schemas.js';
|
|
8
|
+
import { log } from '../logger.js';
|
|
9
|
+
|
|
10
|
+
import { PROVIDERS, CHANNELS, PLUGINS, PROVIDER_AUTH, CHANNEL_AUTH } from '../constants.js';
|
|
11
|
+
|
|
12
|
+
// Brand accent
|
|
13
|
+
const ACCENT = chalk.hex('#805AD5');
|
|
14
|
+
const DIM = chalk.dim;
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Interactive onboarding wizard.
|
|
18
|
+
* Multi-step setup: providers → channels → plugins → API keys → first block.
|
|
19
|
+
* Uses @clack/prompts for styled terminal UI.
|
|
20
|
+
*/
|
|
21
|
+
export async function initCommand(options?: { nonInteractive?: boolean }): Promise<void> {
|
|
22
|
+
const wsDir = getWsRoot();
|
|
23
|
+
|
|
24
|
+
// Non-interactive: create defaults and exit
|
|
25
|
+
if (options?.nonInteractive) {
|
|
26
|
+
await ensureDir(wsDir);
|
|
27
|
+
const defaultConfig = GlobalConfigSchema.parse({
|
|
28
|
+
blocksDir: './blocks'
|
|
29
|
+
});
|
|
30
|
+
await writeJson(getConfigPath(), defaultConfig);
|
|
31
|
+
await writeJson(getAuthPath(), {
|
|
32
|
+
aws: { accessKeyId: '', secretAccessKey: '', region: 'us-east-1' },
|
|
33
|
+
anthropic: { apiKey: '' },
|
|
34
|
+
openai: { apiKey: '' },
|
|
35
|
+
gemini: { apiKey: '' },
|
|
36
|
+
telegram: { botToken: '', chatId: '' },
|
|
37
|
+
brave: { apiKey: '' }
|
|
38
|
+
});
|
|
39
|
+
log.brand('Initialized (non-interactive)');
|
|
40
|
+
log.success(`Workspace: ${wsDir}`);
|
|
41
|
+
log.success(`Config: ${getConfigPath()}`);
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const alreadyInit = await isInitialized();
|
|
46
|
+
|
|
47
|
+
// Welcome
|
|
48
|
+
console.log('');
|
|
49
|
+
log.banner();
|
|
50
|
+
console.log(DIM(' Deploy isolated AI workspaces on your machine.\n'));
|
|
51
|
+
|
|
52
|
+
p.intro(chalk.bold('Setup Wizard'));
|
|
53
|
+
|
|
54
|
+
if (alreadyInit) {
|
|
55
|
+
const proceed = await p.confirm({
|
|
56
|
+
message: 'Already configured globally. Re-run setup?',
|
|
57
|
+
initialValue: false,
|
|
58
|
+
});
|
|
59
|
+
if (p.isCancel(proceed) || !proceed) {
|
|
60
|
+
p.outro('Setup cancelled.');
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// ─── Step 1: Providers ───────────────────────────────
|
|
66
|
+
const selectedProviders = await p.multiselect({
|
|
67
|
+
message: 'Which providers do you want to use?',
|
|
68
|
+
options: PROVIDERS,
|
|
69
|
+
initialValues: ['bedrock'],
|
|
70
|
+
required: true,
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
if (p.isCancel(selectedProviders)) {
|
|
74
|
+
p.outro('Setup cancelled.');
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// ─── Step 2: Channels ───────────────────────────────
|
|
79
|
+
const selectedChannels = await p.multiselect({
|
|
80
|
+
message: 'Which channels do you want to enable?',
|
|
81
|
+
options: CHANNELS.map(ch => ({
|
|
82
|
+
...ch,
|
|
83
|
+
label: (ch.value === 'cli' || ch.value === 'web') ? `${ch.label} (always on)` : ch.label,
|
|
84
|
+
})),
|
|
85
|
+
initialValues: ['cli', 'web'],
|
|
86
|
+
required: true,
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
if (p.isCancel(selectedChannels)) {
|
|
90
|
+
p.outro('Setup cancelled.');
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// ─── Step 3: Skills & Plugins ───────────────────────
|
|
95
|
+
p.log.info(`${chalk.green('✓')} Core skills (file ops, shell, dev) — always available`);
|
|
96
|
+
p.log.info(`${chalk.green('✓')} Multi-Agent Orchestration — always available`);
|
|
97
|
+
|
|
98
|
+
let selectedPlugins: symbol | string[] = [];
|
|
99
|
+
if (PLUGINS.length > 0) {
|
|
100
|
+
selectedPlugins = await p.multiselect({
|
|
101
|
+
message: 'Select additional skills & plugins to install:',
|
|
102
|
+
options: PLUGINS.map(pl => ({
|
|
103
|
+
value: pl.value,
|
|
104
|
+
label: pl.label,
|
|
105
|
+
hint: pl.hint,
|
|
106
|
+
})),
|
|
107
|
+
required: false,
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
if (p.isCancel(selectedPlugins)) {
|
|
111
|
+
p.outro('Setup cancelled.');
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// ─── Step 4: API Keys ───────────────────────────────
|
|
117
|
+
const authData: Record<string, Record<string, string>> = {};
|
|
118
|
+
|
|
119
|
+
for (const provider of (selectedProviders as string[])) {
|
|
120
|
+
const providerAuth = PROVIDER_AUTH[provider];
|
|
121
|
+
if (!providerAuth || providerAuth.fields.length === 0) continue;
|
|
122
|
+
|
|
123
|
+
const providerLabel = PROVIDERS.find(p => p.value === provider)?.label || provider;
|
|
124
|
+
p.log.step(`${providerLabel} credentials`);
|
|
125
|
+
|
|
126
|
+
const data: Record<string, string> = {};
|
|
127
|
+
for (const field of providerAuth.fields) {
|
|
128
|
+
const value = await p.text({
|
|
129
|
+
message: field.label,
|
|
130
|
+
placeholder: field.key === 'region' ? 'us-east-1' : '',
|
|
131
|
+
defaultValue: field.key === 'region' ? 'us-east-1' : undefined,
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
if (p.isCancel(value)) {
|
|
135
|
+
p.outro('Setup cancelled.');
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
data[field.key] = (value as string) || '';
|
|
140
|
+
}
|
|
141
|
+
authData[provider === 'bedrock' ? 'aws' : provider] = data;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// Channel auth (only telegram has auth currently)
|
|
145
|
+
for (const channel of (selectedChannels as string[])) {
|
|
146
|
+
const fields = CHANNEL_AUTH[channel];
|
|
147
|
+
if (!fields) continue;
|
|
148
|
+
|
|
149
|
+
const channelLabel = CHANNELS.find(c => c.value === channel)?.label || channel;
|
|
150
|
+
p.log.step(`${channelLabel} credentials`);
|
|
151
|
+
|
|
152
|
+
const data: Record<string, string> = {};
|
|
153
|
+
for (const field of fields) {
|
|
154
|
+
const value = await p.text({
|
|
155
|
+
message: field.label,
|
|
156
|
+
placeholder: field.secret ? '' : 'optional',
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
if (p.isCancel(value)) {
|
|
160
|
+
p.outro('Setup cancelled.');
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
data[field.key] = (value as string) || '';
|
|
165
|
+
}
|
|
166
|
+
authData[channel] = data;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// ─── Step 5: Connection Testing ─────────────────────
|
|
170
|
+
const s = p.spinner();
|
|
171
|
+
const results: Array<{ name: string; ok: boolean }> = [];
|
|
172
|
+
|
|
173
|
+
// Test Bedrock
|
|
174
|
+
if (authData.aws?.accessKeyId) {
|
|
175
|
+
s.start('Testing Bedrock connection...');
|
|
176
|
+
try {
|
|
177
|
+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
178
|
+
// @ts-ignore — dynamic import, may not be installed
|
|
179
|
+
const { BedrockRuntimeClient, ConverseCommand } = await import('@aws-sdk/client-bedrock-runtime');
|
|
180
|
+
const client = new BedrockRuntimeClient({
|
|
181
|
+
region: authData.aws.region || 'us-east-1',
|
|
182
|
+
credentials: { accessKeyId: authData.aws.accessKeyId, secretAccessKey: authData.aws.secretAccessKey },
|
|
183
|
+
});
|
|
184
|
+
await client.send(new ConverseCommand({
|
|
185
|
+
modelId: 'anthropic.claude-3-haiku-20240307-v1:0',
|
|
186
|
+
messages: [{ role: 'user', content: [{ text: 'hi' }] }],
|
|
187
|
+
inferenceConfig: { maxTokens: 1 },
|
|
188
|
+
}));
|
|
189
|
+
results.push({ name: 'Bedrock', ok: true });
|
|
190
|
+
s.stop('Bedrock ✓');
|
|
191
|
+
} catch (err: any) {
|
|
192
|
+
// AccessDeniedException means credentials ARE valid, just model not enabled
|
|
193
|
+
const isAccessIssue = err?.name === 'AccessDeniedException' || err?.Code === 'AccessDeniedException';
|
|
194
|
+
if (isAccessIssue) {
|
|
195
|
+
results.push({ name: 'Bedrock', ok: true });
|
|
196
|
+
s.stop('Bedrock ✓ (credentials valid, enable models in AWS console)');
|
|
197
|
+
} else {
|
|
198
|
+
results.push({ name: 'Bedrock', ok: false });
|
|
199
|
+
s.stop('Bedrock ✗ (check credentials later)');
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// Test Telegram
|
|
205
|
+
if (authData.telegram?.botToken) {
|
|
206
|
+
s.start('Testing Telegram bot...');
|
|
207
|
+
try {
|
|
208
|
+
const res = await fetch(`https://api.telegram.org/bot${authData.telegram.botToken}/getMe`);
|
|
209
|
+
const data = await res.json() as { ok: boolean };
|
|
210
|
+
results.push({ name: 'Telegram', ok: data.ok });
|
|
211
|
+
s.stop(data.ok ? 'Telegram ✓' : 'Telegram ✗');
|
|
212
|
+
} catch {
|
|
213
|
+
results.push({ name: 'Telegram', ok: false });
|
|
214
|
+
s.stop('Telegram ✗');
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
// ─── Step 6: Save Configuration ─────────────────────
|
|
219
|
+
await ensureDir(wsDir);
|
|
220
|
+
|
|
221
|
+
const defaultConfig = GlobalConfigSchema.parse({
|
|
222
|
+
blocksDir: './blocks',
|
|
223
|
+
});
|
|
224
|
+
await writeJson(getConfigPath(), defaultConfig);
|
|
225
|
+
await writeJson(getAuthPath(), authData);
|
|
226
|
+
|
|
227
|
+
// ─── Done ───────────────────────────────────────────
|
|
228
|
+
console.log('');
|
|
229
|
+
p.note(
|
|
230
|
+
[
|
|
231
|
+
...results.map(r => `${r.ok ? chalk.green('✓') : chalk.yellow('○')} ${r.name}`),
|
|
232
|
+
...(results.length === 0 ? [DIM('No connections configured yet')] : []),
|
|
233
|
+
'',
|
|
234
|
+
DIM(`Workspace: ${wsDir}`),
|
|
235
|
+
DIM(`Config: ${getConfigPath()}`),
|
|
236
|
+
].join('\n'),
|
|
237
|
+
'Setup Complete',
|
|
238
|
+
);
|
|
239
|
+
|
|
240
|
+
p.outro(`Run ${ACCENT('mblk start <name>')} to create and start your first block.`);
|
|
241
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { loadGlobalConfig, resolveBlockPath, loadBlockConfig, saveBlockConfig } from '../../utils/config.js';
|
|
2
|
+
import { pathExists } from '../../utils/fs.js';
|
|
3
|
+
import { log } from '../logger.js';
|
|
4
|
+
import { join } from 'node:path';
|
|
5
|
+
|
|
6
|
+
type PermissionScope = 'block' | 'workspace' | 'system';
|
|
7
|
+
|
|
8
|
+
const VALID_SCOPES: PermissionScope[] = ['block', 'workspace', 'system'];
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* View or update block permissions.
|
|
12
|
+
* Permissions are CLI-only — they cannot be changed via chat or web.
|
|
13
|
+
*/
|
|
14
|
+
export async function permissionsCommand(
|
|
15
|
+
blockName: string,
|
|
16
|
+
options?: {
|
|
17
|
+
scope?: string;
|
|
18
|
+
allowShell?: boolean;
|
|
19
|
+
denyShell?: boolean;
|
|
20
|
+
allowNetwork?: boolean;
|
|
21
|
+
denyNetwork?: boolean;
|
|
22
|
+
maxTimeout?: string;
|
|
23
|
+
},
|
|
24
|
+
): Promise<void> {
|
|
25
|
+
const globalConfig = await loadGlobalConfig();
|
|
26
|
+
const blockPath = resolveBlockPath(globalConfig, blockName);
|
|
27
|
+
|
|
28
|
+
if (!await pathExists(join(blockPath, 'config.json'))) {
|
|
29
|
+
log.error(`Block "${blockName}" not found.`);
|
|
30
|
+
process.exit(1);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const config = await loadBlockConfig(blockPath);
|
|
34
|
+
const perms = config.permissions || { scope: 'block', allowShell: false, allowNetwork: true, maxTimeout: 120_000 };
|
|
35
|
+
|
|
36
|
+
// No flags = show current permissions
|
|
37
|
+
const hasFlags = options?.scope || options?.allowShell || options?.denyShell
|
|
38
|
+
|| options?.allowNetwork || options?.denyNetwork || options?.maxTimeout;
|
|
39
|
+
|
|
40
|
+
if (!hasFlags) {
|
|
41
|
+
log.brand(`permissions — ${blockName}\n`);
|
|
42
|
+
console.log(` Scope: ${perms.scope}`);
|
|
43
|
+
console.log(` Shell Access: ${perms.allowShell ? '✓ allowed' : '✗ denied'}`);
|
|
44
|
+
console.log(` Network: ${perms.allowNetwork ? '✓ allowed' : '✗ denied'}`);
|
|
45
|
+
console.log(` Max Timeout: ${(perms.maxTimeout / 1000).toFixed(0)}s`);
|
|
46
|
+
console.log('');
|
|
47
|
+
|
|
48
|
+
log.dim(' Scopes:');
|
|
49
|
+
log.dim(' block — read/write own block directory only (default)');
|
|
50
|
+
log.dim(' workspace — access the entire workspace');
|
|
51
|
+
log.dim(' system — unrestricted file and shell access');
|
|
52
|
+
console.log('');
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Apply changes
|
|
57
|
+
if (options?.scope) {
|
|
58
|
+
if (!VALID_SCOPES.includes(options.scope as PermissionScope)) {
|
|
59
|
+
log.error(`Invalid scope: "${options.scope}". Use: block, workspace, or system.`);
|
|
60
|
+
process.exit(1);
|
|
61
|
+
}
|
|
62
|
+
perms.scope = options.scope as PermissionScope;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
if (options?.allowShell) perms.allowShell = true;
|
|
66
|
+
if (options?.denyShell) perms.allowShell = false;
|
|
67
|
+
if (options?.allowNetwork) perms.allowNetwork = true;
|
|
68
|
+
if (options?.denyNetwork) perms.allowNetwork = false;
|
|
69
|
+
if (options?.maxTimeout) perms.maxTimeout = parseInt(options.maxTimeout, 10) * 1000;
|
|
70
|
+
|
|
71
|
+
// Save
|
|
72
|
+
(config as any).permissions = perms;
|
|
73
|
+
await saveBlockConfig(blockPath, config);
|
|
74
|
+
|
|
75
|
+
log.success(` Permissions updated for "${blockName}".`);
|
|
76
|
+
console.log(` Scope: ${perms.scope}, Shell: ${perms.allowShell ? 'yes' : 'no'}, Network: ${perms.allowNetwork ? 'yes' : 'no'}, Timeout: ${(perms.maxTimeout / 1000).toFixed(0)}s`);
|
|
77
|
+
}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import * as p from '@clack/prompts';
|
|
2
|
+
import { log } from '../logger.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* mblk settings <plugin> — view/edit plugin settings via CLI.
|
|
6
|
+
* Auto-generates forms from the plugin's settings schema.
|
|
7
|
+
*/
|
|
8
|
+
export async function pluginSettingsCommand(pluginId?: string): Promise<void> {
|
|
9
|
+
let installer: any;
|
|
10
|
+
try {
|
|
11
|
+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
12
|
+
// @ts-ignore — optional runtime dependency, not needed at compile time
|
|
13
|
+
const mod = await import('@memoryblock/plugin-installer');
|
|
14
|
+
installer = new mod.PluginInstaller();
|
|
15
|
+
} catch {
|
|
16
|
+
log.error('Plugin installer not available.');
|
|
17
|
+
process.exit(1);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const plugins = await installer.listPlugins();
|
|
21
|
+
|
|
22
|
+
// No plugin specified — list all plugins with settings
|
|
23
|
+
if (!pluginId) {
|
|
24
|
+
const withSettings = plugins.filter((pl: any) => pl.settings && Object.keys(pl.settings).length > 0);
|
|
25
|
+
|
|
26
|
+
if (withSettings.length === 0) {
|
|
27
|
+
log.dim(' No plugins with configurable settings.');
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
log.brand('plugin settings\n');
|
|
32
|
+
for (const pl of withSettings) {
|
|
33
|
+
const fields = Object.keys(pl.settings).length;
|
|
34
|
+
console.log(` ${pl.core ? '●' : '○'} ${pl.name} (${pl.id}) — ${fields} setting${fields > 1 ? 's' : ''}`);
|
|
35
|
+
}
|
|
36
|
+
console.log('');
|
|
37
|
+
log.dim(' Run `mblk settings <plugin-id>` to configure.');
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Find the plugin
|
|
42
|
+
const plugin = plugins.find((pl: any) => pl.id === pluginId);
|
|
43
|
+
if (!plugin) {
|
|
44
|
+
log.error(`Plugin "${pluginId}" not found.`);
|
|
45
|
+
process.exit(1);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
if (!plugin.settings || Object.keys(plugin.settings).length === 0) {
|
|
49
|
+
log.dim(` Plugin "${plugin.name}" has no configurable settings.`);
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Load current values
|
|
54
|
+
const current = await installer.getPluginSettings(pluginId);
|
|
55
|
+
|
|
56
|
+
p.intro(`${plugin.name} Settings`);
|
|
57
|
+
|
|
58
|
+
const updates: Record<string, unknown> = {};
|
|
59
|
+
|
|
60
|
+
for (const [key, field] of Object.entries(plugin.settings) as [string, any][]) {
|
|
61
|
+
const currentVal = current[key];
|
|
62
|
+
|
|
63
|
+
if (field.type === 'select') {
|
|
64
|
+
const result = await p.select({
|
|
65
|
+
message: field.label,
|
|
66
|
+
options: (field.options || []).map((o: string) => ({
|
|
67
|
+
value: o,
|
|
68
|
+
label: o,
|
|
69
|
+
hint: o === currentVal ? 'current' : undefined,
|
|
70
|
+
})),
|
|
71
|
+
initialValue: currentVal || field.default,
|
|
72
|
+
});
|
|
73
|
+
if (p.isCancel(result)) { p.outro('Cancelled.'); return; }
|
|
74
|
+
updates[key] = result;
|
|
75
|
+
} else if (field.type === 'toggle') {
|
|
76
|
+
const result = await p.confirm({
|
|
77
|
+
message: field.label,
|
|
78
|
+
initialValue: currentVal ?? field.default ?? false,
|
|
79
|
+
});
|
|
80
|
+
if (p.isCancel(result)) { p.outro('Cancelled.'); return; }
|
|
81
|
+
updates[key] = result;
|
|
82
|
+
} else if (field.type === 'number') {
|
|
83
|
+
const result = await p.text({
|
|
84
|
+
message: `${field.label} (${field.min ?? ''}–${field.max ?? ''})`,
|
|
85
|
+
defaultValue: String(currentVal ?? field.default ?? ''),
|
|
86
|
+
placeholder: String(field.default ?? ''),
|
|
87
|
+
validate: (v: string | undefined) => {
|
|
88
|
+
const n = parseInt(v || '', 10);
|
|
89
|
+
if (isNaN(n)) return 'Must be a number';
|
|
90
|
+
if (field.min !== undefined && n < field.min) return `Min: ${field.min}`;
|
|
91
|
+
if (field.max !== undefined && n > field.max) return `Max: ${field.max}`;
|
|
92
|
+
},
|
|
93
|
+
});
|
|
94
|
+
if (p.isCancel(result)) { p.outro('Cancelled.'); return; }
|
|
95
|
+
updates[key] = parseInt(String(result), 10);
|
|
96
|
+
} else {
|
|
97
|
+
// text / password
|
|
98
|
+
const result = await p.text({
|
|
99
|
+
message: field.label,
|
|
100
|
+
defaultValue: String(currentVal ?? field.default ?? ''),
|
|
101
|
+
placeholder: field.placeholder || '',
|
|
102
|
+
});
|
|
103
|
+
if (p.isCancel(result)) { p.outro('Cancelled.'); return; }
|
|
104
|
+
updates[key] = result;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Save
|
|
109
|
+
await installer.savePluginSettings(pluginId, updates);
|
|
110
|
+
p.outro(`Settings saved for ${plugin.name}.`);
|
|
111
|
+
}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { log } from '../logger.js';
|
|
2
|
+
|
|
3
|
+
const INSTALLER_PKG = '@memoryblock/plugin-installer';
|
|
4
|
+
|
|
5
|
+
export async function addCommand(pluginId: string): Promise<void> {
|
|
6
|
+
let installer: any;
|
|
7
|
+
try {
|
|
8
|
+
const pkg = await import(INSTALLER_PKG);
|
|
9
|
+
installer = new pkg.PluginInstaller();
|
|
10
|
+
} catch {
|
|
11
|
+
log.error('Plugin installer not available. Install @memoryblock/plugin-installer.');
|
|
12
|
+
process.exit(1);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
// Show available if no ID
|
|
16
|
+
if (!pluginId) {
|
|
17
|
+
const plugins = await installer.listPlugins();
|
|
18
|
+
log.brand('Skills & Plugins\n');
|
|
19
|
+
|
|
20
|
+
// Check installed status for each plugin
|
|
21
|
+
const rows: Array<{ id: string; name: string; status: string; installed: boolean }> = [];
|
|
22
|
+
for (const p of plugins) {
|
|
23
|
+
let installed = false;
|
|
24
|
+
try {
|
|
25
|
+
require.resolve(p.package, { paths: [process.cwd()] });
|
|
26
|
+
installed = true;
|
|
27
|
+
} catch { /* ignore */ }
|
|
28
|
+
rows.push({
|
|
29
|
+
id: p.id,
|
|
30
|
+
name: p.name,
|
|
31
|
+
status: p.status === 'upcoming' ? 'upcoming' : 'available',
|
|
32
|
+
installed,
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Table header
|
|
37
|
+
const colId = 16, colName = 24, colStatus = 12;
|
|
38
|
+
const header = ` ${'ID'.padEnd(colId)}${'Name'.padEnd(colName)}${'Status'.padEnd(colStatus)}Installed`;
|
|
39
|
+
const separator = ` ${'─'.repeat(colId)}${'─'.repeat(colName)}${'─'.repeat(colStatus)}${'─'.repeat(9)}`;
|
|
40
|
+
console.log(header);
|
|
41
|
+
console.log(separator);
|
|
42
|
+
|
|
43
|
+
for (const row of rows) {
|
|
44
|
+
const installedMark = row.installed ? ' ✓' : ' ·';
|
|
45
|
+
const statusLabel = row.status === 'upcoming' ? 'upcoming' : 'ready';
|
|
46
|
+
console.log(` ${row.id.padEnd(colId)}${row.name.padEnd(colName)}${statusLabel.padEnd(colStatus)}${installedMark}`);
|
|
47
|
+
}
|
|
48
|
+
console.log('');
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
log.dim(` installing ${pluginId}...`);
|
|
53
|
+
const result = await installer.install(pluginId);
|
|
54
|
+
|
|
55
|
+
if (result.success) {
|
|
56
|
+
log.success(result.message);
|
|
57
|
+
if (result.plugin?.requiresAuth?.length) {
|
|
58
|
+
log.dim(` requires: ${result.plugin.requiresAuth.join(', ')}`);
|
|
59
|
+
}
|
|
60
|
+
} else {
|
|
61
|
+
log.warn(result.message);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export async function removeCommand(pluginId: string): Promise<void> {
|
|
66
|
+
if (!pluginId) {
|
|
67
|
+
log.error('Specify a plugin ID: mblk remove <id>');
|
|
68
|
+
process.exit(1);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
let installer: any;
|
|
72
|
+
try {
|
|
73
|
+
const pkg = await import(INSTALLER_PKG);
|
|
74
|
+
installer = new pkg.PluginInstaller();
|
|
75
|
+
} catch {
|
|
76
|
+
log.error('Plugin installer not available.');
|
|
77
|
+
process.exit(1);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
log.dim(` removing ${pluginId}...`);
|
|
81
|
+
const result = await installer.remove(pluginId);
|
|
82
|
+
|
|
83
|
+
if (result.success) {
|
|
84
|
+
log.success(result.message);
|
|
85
|
+
} else {
|
|
86
|
+
log.error(result.message);
|
|
87
|
+
process.exit(1);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { log } from '../logger.js';
|
|
2
|
+
import { loadGlobalConfig, resolveBlockPath } from '../../utils/config.js';
|
|
3
|
+
import { promises as fsp } from 'node:fs';
|
|
4
|
+
import { join } from 'node:path';
|
|
5
|
+
import { createInterface } from 'node:readline';
|
|
6
|
+
import chalk from 'chalk';
|
|
7
|
+
import { FILE_TEMPLATES } from '../../engine/prompts.js';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* mblk reset <block> — Light cleanup: reset memory, pulse, costs, session
|
|
11
|
+
* mblk reset <block> --hard — Full wipe: also deletes logs/ (with confirmation)
|
|
12
|
+
*/
|
|
13
|
+
export async function resetCommand(blockName: string, options: { hard?: boolean }): Promise<void> {
|
|
14
|
+
const globalConfig = await loadGlobalConfig();
|
|
15
|
+
const blockPath = resolveBlockPath(globalConfig, blockName);
|
|
16
|
+
|
|
17
|
+
// Verify block exists
|
|
18
|
+
try {
|
|
19
|
+
await fsp.access(blockPath);
|
|
20
|
+
} catch {
|
|
21
|
+
throw new Error(`Block "${blockName}" not found at ${blockPath}`);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// --hard requires confirmation
|
|
25
|
+
if (options.hard) {
|
|
26
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
27
|
+
const answer = await new Promise<string>((resolve) => {
|
|
28
|
+
rl.question(chalk.yellow(`\n⚠ --hard will wipe ALL data for "${blockName}" (logs, memory, session). Continue? (y/n): `), resolve);
|
|
29
|
+
});
|
|
30
|
+
rl.close();
|
|
31
|
+
|
|
32
|
+
if (answer.trim().toLowerCase() !== 'y') {
|
|
33
|
+
log.dim(' cancelled.');
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
log.dim(` resetting ${blockName}${options.hard ? ' (hard)' : ''}...`);
|
|
39
|
+
|
|
40
|
+
// Light reset: memory, pulse, costs, session, chat.json, and logs directory
|
|
41
|
+
const filesToReset = ['memory.md', 'pulse.json', 'costs.json', 'session.json', 'chat.json'];
|
|
42
|
+
|
|
43
|
+
for (const file of filesToReset) {
|
|
44
|
+
const filePath = join(blockPath, file);
|
|
45
|
+
try {
|
|
46
|
+
await fsp.access(filePath);
|
|
47
|
+
if (file === 'memory.md') {
|
|
48
|
+
await fsp.writeFile(filePath, FILE_TEMPLATES.MEMORY_MD, 'utf-8');
|
|
49
|
+
} else if (file === 'pulse.json') {
|
|
50
|
+
await fsp.writeFile(filePath, JSON.stringify(FILE_TEMPLATES.PULSE_JSON, null, 4), 'utf-8');
|
|
51
|
+
} else if (file === 'costs.json') {
|
|
52
|
+
await fsp.writeFile(filePath, JSON.stringify(FILE_TEMPLATES.COSTS_JSON, null, 4), 'utf-8');
|
|
53
|
+
} else if (file === 'session.json' || file === 'chat.json') {
|
|
54
|
+
await fsp.unlink(filePath);
|
|
55
|
+
}
|
|
56
|
+
log.dim(` ✓ ${file}`);
|
|
57
|
+
} catch {
|
|
58
|
+
// Skip if file doesn't exist
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Wipe logs continuously for both soft and hard resets
|
|
63
|
+
const logsDir = join(blockPath, 'logs');
|
|
64
|
+
try {
|
|
65
|
+
const files = await fsp.readdir(logsDir);
|
|
66
|
+
for (const file of files) {
|
|
67
|
+
await fsp.unlink(join(logsDir, file));
|
|
68
|
+
}
|
|
69
|
+
log.dim(` ✓ logs/ wiped (${files.length} files)`);
|
|
70
|
+
} catch {
|
|
71
|
+
// Logs dir might not exist or be empty
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Hard reset: wipe monitor.md and monitor identity from config
|
|
75
|
+
if (options.hard) {
|
|
76
|
+
const monitorPath = join(blockPath, 'monitor.md');
|
|
77
|
+
try {
|
|
78
|
+
await fsp.writeFile(monitorPath, FILE_TEMPLATES.MONITOR_MD(blockName), 'utf-8');
|
|
79
|
+
log.dim(' ✓ monitor.md reset');
|
|
80
|
+
} catch { /* ignore */ }
|
|
81
|
+
|
|
82
|
+
// Clear monitor identity from config
|
|
83
|
+
const configPath = join(blockPath, 'config.json');
|
|
84
|
+
try {
|
|
85
|
+
const raw = await fsp.readFile(configPath, 'utf-8');
|
|
86
|
+
const config = JSON.parse(raw);
|
|
87
|
+
delete config.monitorName;
|
|
88
|
+
delete config.monitorEmoji;
|
|
89
|
+
await fsp.writeFile(configPath, JSON.stringify(config, null, 4), 'utf-8');
|
|
90
|
+
log.dim(' ✓ monitor identity cleared');
|
|
91
|
+
} catch { /* ignore */ }
|
|
92
|
+
} else {
|
|
93
|
+
log.dim(' ℹ monitor.md and identity preserved (use --hard to wipe)');
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
console.log(`\n${chalk.green(`✓ ${blockName} reset.`)}`);
|
|
97
|
+
}
|