memoryblock 0.1.0-beta → 0.1.1-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/README.md +221 -0
- package/dist/bin/mblk.js +0 -0
- package/package.json +61 -16
- package/src/bin/mblk.ts +0 -347
- package/src/cli/commands/create.ts +0 -62
- package/src/cli/commands/delete.ts +0 -165
- package/src/cli/commands/init.ts +0 -241
- package/src/cli/commands/permissions.ts +0 -77
- package/src/cli/commands/plugin-settings.ts +0 -111
- package/src/cli/commands/plugins.ts +0 -89
- package/src/cli/commands/reset.ts +0 -97
- package/src/cli/commands/server.ts +0 -327
- package/src/cli/commands/service.ts +0 -300
- package/src/cli/commands/start.ts +0 -843
- package/src/cli/commands/status.ts +0 -90
- package/src/cli/commands/stop.ts +0 -91
- package/src/cli/commands/web.ts +0 -74
- package/src/cli/constants.ts +0 -88
- package/src/cli/logger.ts +0 -48
- package/src/engine/agent.ts +0 -19
- package/src/engine/conversation-log.ts +0 -90
- package/src/engine/cost-tracker.ts +0 -134
- package/src/engine/gatekeeper.ts +0 -53
- package/src/engine/memory.ts +0 -87
- package/src/engine/monitor.ts +0 -719
- package/src/engine/prompts.ts +0 -102
- package/src/index.ts +0 -88
- package/src/schemas.ts +0 -126
- package/src/types.ts +0 -220
- package/src/utils/config.ts +0 -106
- package/src/utils/fs.ts +0 -64
- package/tsconfig.json +0 -10
|
@@ -1,62 +0,0 @@
|
|
|
1
|
-
import { join } from 'node:path';
|
|
2
|
-
import { ensureDir, writeJson, atomicWrite, pathExists } from '../../utils/fs.js';
|
|
3
|
-
import {
|
|
4
|
-
loadGlobalConfig, resolveBlockPath, isInitialized,
|
|
5
|
-
} from '../../utils/config.js';
|
|
6
|
-
import { BlockConfigSchema, PulseStateSchema } from '../../schemas.js';
|
|
7
|
-
import { log } from '../logger.js';
|
|
8
|
-
import { FILE_TEMPLATES } from '../../engine/prompts.js';
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
// Templates moved to prompts.ts
|
|
12
|
-
|
|
13
|
-
export async function createCommand(blockName: string): Promise<void> {
|
|
14
|
-
if (!(await isInitialized())) {
|
|
15
|
-
throw new Error('Not initialized. Run `mblk init` first.');
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
if (!/^[a-z0-9][a-z0-9-]{0,31}$/.test(blockName)) {
|
|
19
|
-
throw new Error('Block name must start with a letter/number and contain only lowercase letters, numbers, and hyphens (max 32 chars).');
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
const globalConfig = await loadGlobalConfig();
|
|
23
|
-
const blockPath = resolveBlockPath(globalConfig, blockName);
|
|
24
|
-
|
|
25
|
-
if (await pathExists(blockPath)) {
|
|
26
|
-
throw new Error(`Block "${blockName}" already exists at ${blockPath}`);
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
log.brand(`Creating block: ${blockName}\n`);
|
|
30
|
-
|
|
31
|
-
// Directory structure
|
|
32
|
-
await ensureDir(blockPath);
|
|
33
|
-
await ensureDir(join(blockPath, 'agents'));
|
|
34
|
-
await ensureDir(join(blockPath, 'logs'));
|
|
35
|
-
|
|
36
|
-
// Block config (inherits global defaults)
|
|
37
|
-
const blockConfig = BlockConfigSchema.parse({
|
|
38
|
-
name: blockName,
|
|
39
|
-
adapter: globalConfig.defaults.adapter,
|
|
40
|
-
memory: globalConfig.defaults.memory,
|
|
41
|
-
pulse: globalConfig.defaults.pulse,
|
|
42
|
-
});
|
|
43
|
-
await writeJson(join(blockPath, 'config.json'), blockConfig);
|
|
44
|
-
log.success('Created config.json');
|
|
45
|
-
|
|
46
|
-
// Initial pulse state
|
|
47
|
-
const pulse = PulseStateSchema.parse({});
|
|
48
|
-
await writeJson(join(blockPath, 'pulse.json'), pulse);
|
|
49
|
-
log.success('Created pulse.json');
|
|
50
|
-
|
|
51
|
-
// Core identity files
|
|
52
|
-
await atomicWrite(join(blockPath, 'memory.md'), FILE_TEMPLATES.MEMORY_MD);
|
|
53
|
-
log.success('Created memory.md');
|
|
54
|
-
|
|
55
|
-
await atomicWrite(join(blockPath, 'monitor.md'), FILE_TEMPLATES.MONITOR_MD(blockName));
|
|
56
|
-
log.success('Created monitor.md');
|
|
57
|
-
|
|
58
|
-
console.log('');
|
|
59
|
-
log.brand(`Block "${blockName}" is ready.`);
|
|
60
|
-
log.dim(` Path: ${blockPath}`);
|
|
61
|
-
log.dim(` Start: mblk start ${blockName}`);
|
|
62
|
-
}
|
|
@@ -1,165 +0,0 @@
|
|
|
1
|
-
import { join } from 'node:path';
|
|
2
|
-
import { promises as fsp } from 'node:fs';
|
|
3
|
-
import * as p from '@clack/prompts';
|
|
4
|
-
import chalk from 'chalk';
|
|
5
|
-
import { loadGlobalConfig, resolveBlocksDir, isInitialized } from '../../utils/config.js';
|
|
6
|
-
import { ensureDir, pathExists } from '../../utils/fs.js';
|
|
7
|
-
import { log } from '../logger.js';
|
|
8
|
-
import { t } from '@memoryblock/locale';
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* Find archived folders matching a block name.
|
|
12
|
-
* Supports both:
|
|
13
|
-
* - Exact archive name: "dev-pal_2026-03-21T10-33-24-242Z"
|
|
14
|
-
* - Block name prefix: "dev-pal" (matches all archives of that block)
|
|
15
|
-
*/
|
|
16
|
-
async function findArchives(archiveDir: string, query: string): Promise<string[]> {
|
|
17
|
-
// Strip _archive/ prefix if user pastes it
|
|
18
|
-
const name = query.replace(/^_archive\//, '');
|
|
19
|
-
|
|
20
|
-
try {
|
|
21
|
-
const entries = await fsp.readdir(archiveDir);
|
|
22
|
-
|
|
23
|
-
// 1. Exact match
|
|
24
|
-
if (entries.includes(name)) return [name];
|
|
25
|
-
|
|
26
|
-
// 2. Prefix match: "dev-pal" matches "dev-pal_2026-03-21T10-33-24-242Z"
|
|
27
|
-
const matches = entries
|
|
28
|
-
.filter(e => e.startsWith(`${name}_`))
|
|
29
|
-
.sort()
|
|
30
|
-
.reverse(); // newest first
|
|
31
|
-
|
|
32
|
-
return matches;
|
|
33
|
-
} catch {
|
|
34
|
-
return [];
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
/**
|
|
39
|
-
* Resolve a single archive — if multiple exist, let user pick.
|
|
40
|
-
*/
|
|
41
|
-
async function resolveArchive(archiveDir: string, query: string): Promise<string | null> {
|
|
42
|
-
const matches = await findArchives(archiveDir, query);
|
|
43
|
-
|
|
44
|
-
if (matches.length === 0) return null;
|
|
45
|
-
if (matches.length === 1) return matches[0];
|
|
46
|
-
|
|
47
|
-
// Multiple archives — let the user pick
|
|
48
|
-
const selection = await p.select({
|
|
49
|
-
message: `Multiple archives found for "${query}". Which one?`,
|
|
50
|
-
options: matches.map(m => {
|
|
51
|
-
// Extract the timestamp for a cleaner label
|
|
52
|
-
const tsMatch = m.match(/_(\d{4}-\d{2}-\d{2}T\d{2}-\d{2}-\d{2})/);
|
|
53
|
-
const hint = tsMatch ? tsMatch[1].replace(/-/g, ':').replace('T', ' ').slice(0, 16) : '';
|
|
54
|
-
return { value: m, label: m, hint };
|
|
55
|
-
}),
|
|
56
|
-
});
|
|
57
|
-
|
|
58
|
-
if (p.isCancel(selection)) return null;
|
|
59
|
-
return selection as string;
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
export async function deleteCommand(blockName: string, options?: { hard?: boolean }): Promise<void> {
|
|
63
|
-
if (!(await isInitialized())) {
|
|
64
|
-
throw new Error(t.general.notInitialized);
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
const globalConfig = await loadGlobalConfig();
|
|
68
|
-
const blocksDir = resolveBlocksDir(globalConfig);
|
|
69
|
-
const blockPath = join(blocksDir, blockName);
|
|
70
|
-
|
|
71
|
-
// Check if it's a direct block path first
|
|
72
|
-
if (await pathExists(blockPath)) {
|
|
73
|
-
if (blockName.startsWith('_archive/')) {
|
|
74
|
-
if (!options?.hard) {
|
|
75
|
-
throw new Error(t.archive.mustUseHard);
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
if (options?.hard) {
|
|
80
|
-
try {
|
|
81
|
-
await fsp.rm(blockPath, { recursive: true, force: true });
|
|
82
|
-
log.success(t.archive.hardDeleteSuccess(blockName));
|
|
83
|
-
} catch (err) {
|
|
84
|
-
throw new Error(`Failed to delete: ${(err as Error).message}`);
|
|
85
|
-
}
|
|
86
|
-
return;
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
// Soft delete — move to _archive directory
|
|
90
|
-
const archiveDir = join(blocksDir, '_archive');
|
|
91
|
-
await ensureDir(archiveDir);
|
|
92
|
-
|
|
93
|
-
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
|
|
94
|
-
const archiveName = `${blockName}_${timestamp}`;
|
|
95
|
-
const archivePath = join(archiveDir, archiveName);
|
|
96
|
-
|
|
97
|
-
try {
|
|
98
|
-
await fsp.rename(blockPath, archivePath);
|
|
99
|
-
log.success(t.archive.success(blockName));
|
|
100
|
-
log.dim(` ${t.archive.location(`_archive/${archiveName}`)}`);
|
|
101
|
-
log.dim(` ${t.archive.restoreCmd(blockName)}`);
|
|
102
|
-
log.dim(` ${t.archive.deleteCmd(blockName)}`);
|
|
103
|
-
} catch (err) {
|
|
104
|
-
throw new Error(`Failed to archive block: ${(err as Error).message}`);
|
|
105
|
-
}
|
|
106
|
-
return;
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
// Not a live block — maybe user wants to hard-delete an archive by name?
|
|
110
|
-
if (options?.hard) {
|
|
111
|
-
const archiveDir = join(blocksDir, '_archive');
|
|
112
|
-
const resolved = await resolveArchive(archiveDir, blockName);
|
|
113
|
-
|
|
114
|
-
if (!resolved) {
|
|
115
|
-
throw new Error(`Block or archive "${blockName}" not found.`);
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
const archivePath = join(archiveDir, resolved);
|
|
119
|
-
try {
|
|
120
|
-
await fsp.rm(archivePath, { recursive: true, force: true });
|
|
121
|
-
log.success(`"${resolved}" permanently deleted from archive.`);
|
|
122
|
-
} catch (err) {
|
|
123
|
-
throw new Error(`Failed to delete: ${(err as Error).message}`);
|
|
124
|
-
}
|
|
125
|
-
return;
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
throw new Error(`Block "${blockName}" not found. Run \`mblk status\` to see available blocks.`);
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
export async function restoreCommand(archiveName: string): Promise<void> {
|
|
132
|
-
if (!(await isInitialized())) {
|
|
133
|
-
throw new Error(t.general.notInitialized);
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
const globalConfig = await loadGlobalConfig();
|
|
137
|
-
const blocksDir = resolveBlocksDir(globalConfig);
|
|
138
|
-
const archiveDir = join(blocksDir, '_archive');
|
|
139
|
-
|
|
140
|
-
// Resolve the archive — supports block name, full name, or prefix
|
|
141
|
-
const resolved = await resolveArchive(archiveDir, archiveName);
|
|
142
|
-
|
|
143
|
-
if (!resolved) {
|
|
144
|
-
throw new Error(`No archive found for "${archiveName}". Run \`mblk status\` to check archives.`);
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
const archivePath = join(archiveDir, resolved);
|
|
148
|
-
|
|
149
|
-
// Extract original block name (strip timestamp suffix)
|
|
150
|
-
const match = resolved.match(/^(.*?)_\d{4}-\d{2}-\d{2}T.*/);
|
|
151
|
-
const targetName = match ? match[1] : resolved;
|
|
152
|
-
const targetPath = join(blocksDir, targetName);
|
|
153
|
-
|
|
154
|
-
if (await pathExists(targetPath)) {
|
|
155
|
-
throw new Error(`Cannot restore: A block named "${targetName}" already exists. Delete or rename it first.`);
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
try {
|
|
159
|
-
await fsp.rename(archivePath, targetPath);
|
|
160
|
-
log.success(`Block "${targetName}" restored successfully.`);
|
|
161
|
-
log.dim(` Start with: ${chalk.bold(`mblk start ${targetName}`)}`);
|
|
162
|
-
} catch (err) {
|
|
163
|
-
throw new Error(`Failed to restore block: ${(err as Error).message}`);
|
|
164
|
-
}
|
|
165
|
-
}
|
package/src/cli/commands/init.ts
DELETED
|
@@ -1,241 +0,0 @@
|
|
|
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
|
-
}
|
|
@@ -1,77 +0,0 @@
|
|
|
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
|
-
}
|
|
@@ -1,111 +0,0 @@
|
|
|
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
|
-
}
|