@getmegabrain/cli 0.1.4 → 0.1.6
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/dist/cloud-fork.js +104 -0
- package/dist/device-auth.js +29 -17
- package/dist/index.js +48 -1
- package/dist/science/index.js +77 -0
- package/dist/science/kernel-protocol.js +7 -0
- package/dist/science/kernel.js +117 -0
- package/dist/science/kernel_driver.py +93 -0
- package/dist/science/nonce.js +53 -0
- package/dist/science/pages.js +295 -0
- package/dist/science/server.js +306 -0
- package/dist/science/store.js +160 -0
- package/package.json +3 -3
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { randomBytes } from 'node:crypto';
|
|
4
|
+
import { MEGABRAIN_CONFIG_DIR } from '../paths.js';
|
|
5
|
+
/** Scientific-web egress categories offered in onboarding step "Connect to the scientific web". */
|
|
6
|
+
export const NETWORK_CATEGORIES = [
|
|
7
|
+
'NCBI / NIH',
|
|
8
|
+
'Genomics & biology',
|
|
9
|
+
'Proteomics',
|
|
10
|
+
'Literature & citations',
|
|
11
|
+
'Clinical & pharma',
|
|
12
|
+
];
|
|
13
|
+
/** Featured research connectors offered in onboarding step "Connectors & skills". */
|
|
14
|
+
export const FEATURED_CONNECTORS = [
|
|
15
|
+
'BioMart',
|
|
16
|
+
'bioRxiv',
|
|
17
|
+
'Cancer Models',
|
|
18
|
+
'CellGuide',
|
|
19
|
+
'PubMed',
|
|
20
|
+
'OpenAlex',
|
|
21
|
+
];
|
|
22
|
+
function defaultSettings() {
|
|
23
|
+
return {
|
|
24
|
+
onboarded: false,
|
|
25
|
+
network: [...NETWORK_CATEGORIES],
|
|
26
|
+
connectors: [...FEATURED_CONNECTORS],
|
|
27
|
+
skills: [],
|
|
28
|
+
profile: '',
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
const STORE_FILE = path.join(MEGABRAIN_CONFIG_DIR, 'science-workspace.json');
|
|
32
|
+
function id(prefix) {
|
|
33
|
+
return `${prefix}_${randomBytes(8).toString('hex')}`;
|
|
34
|
+
}
|
|
35
|
+
export class WorkspaceStore {
|
|
36
|
+
file;
|
|
37
|
+
now;
|
|
38
|
+
data;
|
|
39
|
+
constructor(file = STORE_FILE, now = () => new Date().toISOString()) {
|
|
40
|
+
this.file = file;
|
|
41
|
+
this.now = now;
|
|
42
|
+
this.data = this.load();
|
|
43
|
+
}
|
|
44
|
+
load() {
|
|
45
|
+
try {
|
|
46
|
+
const parsed = JSON.parse(fs.readFileSync(this.file, 'utf8'));
|
|
47
|
+
return {
|
|
48
|
+
projects: parsed.projects ?? [],
|
|
49
|
+
sessions: parsed.sessions ?? [],
|
|
50
|
+
settings: parsed.settings,
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
catch {
|
|
54
|
+
return { projects: [], sessions: [] };
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
save() {
|
|
58
|
+
fs.mkdirSync(path.dirname(this.file), { recursive: true });
|
|
59
|
+
// Atomic write so a crash mid-save can't corrupt the workspace.
|
|
60
|
+
const tmp = `${this.file}.${randomBytes(4).toString('hex')}.tmp`;
|
|
61
|
+
fs.writeFileSync(tmp, JSON.stringify(this.data, null, 2), { mode: 0o600 });
|
|
62
|
+
fs.renameSync(tmp, this.file);
|
|
63
|
+
}
|
|
64
|
+
// ── Projects ─────────────────────────────────────────────────────────
|
|
65
|
+
listProjects() {
|
|
66
|
+
return this.data.projects
|
|
67
|
+
.map(p => {
|
|
68
|
+
const sessions = this.data.sessions.filter(s => s.projectId === p.id);
|
|
69
|
+
const lastActivity = sessions.reduce((max, s) => (s.updatedAt > max ? s.updatedAt : max), p.updatedAt);
|
|
70
|
+
return { ...p, sessionCount: sessions.length, lastActivity };
|
|
71
|
+
})
|
|
72
|
+
.sort((a, b) => b.lastActivity.localeCompare(a.lastActivity));
|
|
73
|
+
}
|
|
74
|
+
getProject(projectId) {
|
|
75
|
+
return this.data.projects.find(p => p.id === projectId) ?? null;
|
|
76
|
+
}
|
|
77
|
+
createProject(input) {
|
|
78
|
+
const ts = this.now();
|
|
79
|
+
const project = {
|
|
80
|
+
id: id('proj'),
|
|
81
|
+
name: input.name.trim() || 'Untitled project',
|
|
82
|
+
description: input.description?.trim() ?? '',
|
|
83
|
+
agentContext: input.agentContext?.trim() ?? '',
|
|
84
|
+
createdAt: ts,
|
|
85
|
+
updatedAt: ts,
|
|
86
|
+
};
|
|
87
|
+
this.data.projects.push(project);
|
|
88
|
+
this.save();
|
|
89
|
+
return project;
|
|
90
|
+
}
|
|
91
|
+
updateProject(projectId, patch) {
|
|
92
|
+
const project = this.data.projects.find(p => p.id === projectId);
|
|
93
|
+
if (!project)
|
|
94
|
+
return null;
|
|
95
|
+
if (patch.name !== undefined)
|
|
96
|
+
project.name = patch.name.trim() || project.name;
|
|
97
|
+
if (patch.description !== undefined)
|
|
98
|
+
project.description = patch.description.trim();
|
|
99
|
+
if (patch.agentContext !== undefined)
|
|
100
|
+
project.agentContext = patch.agentContext.trim();
|
|
101
|
+
project.updatedAt = this.now();
|
|
102
|
+
this.save();
|
|
103
|
+
return project;
|
|
104
|
+
}
|
|
105
|
+
// ── Sessions ─────────────────────────────────────────────────────────
|
|
106
|
+
listSessions(projectId) {
|
|
107
|
+
return this.data.sessions
|
|
108
|
+
.filter(s => s.projectId === projectId)
|
|
109
|
+
.sort((a, b) => b.updatedAt.localeCompare(a.updatedAt));
|
|
110
|
+
}
|
|
111
|
+
recentSessions(limit = 10) {
|
|
112
|
+
return [...this.data.sessions]
|
|
113
|
+
.sort((a, b) => b.updatedAt.localeCompare(a.updatedAt))
|
|
114
|
+
.slice(0, limit);
|
|
115
|
+
}
|
|
116
|
+
getSession(sessionId) {
|
|
117
|
+
return this.data.sessions.find(s => s.id === sessionId) ?? null;
|
|
118
|
+
}
|
|
119
|
+
createSession(input) {
|
|
120
|
+
if (!this.getProject(input.projectId))
|
|
121
|
+
return null;
|
|
122
|
+
const ts = this.now();
|
|
123
|
+
const session = {
|
|
124
|
+
id: id('sess'),
|
|
125
|
+
projectId: input.projectId,
|
|
126
|
+
title: input.title?.trim() || 'New session',
|
|
127
|
+
summary: input.summary?.trim() ?? '',
|
|
128
|
+
createdAt: ts,
|
|
129
|
+
updatedAt: ts,
|
|
130
|
+
};
|
|
131
|
+
this.data.sessions.push(session);
|
|
132
|
+
this.save();
|
|
133
|
+
return session;
|
|
134
|
+
}
|
|
135
|
+
/** Bump a session's activity timestamp (and optionally its summary/title). */
|
|
136
|
+
touchSession(sessionId, patch) {
|
|
137
|
+
const session = this.data.sessions.find(s => s.id === sessionId);
|
|
138
|
+
if (!session)
|
|
139
|
+
return null;
|
|
140
|
+
if (patch?.title !== undefined)
|
|
141
|
+
session.title = patch.title.trim() || session.title;
|
|
142
|
+
if (patch?.summary !== undefined)
|
|
143
|
+
session.summary = patch.summary.trim();
|
|
144
|
+
session.updatedAt = this.now();
|
|
145
|
+
this.save();
|
|
146
|
+
return session;
|
|
147
|
+
}
|
|
148
|
+
// ── Onboarding settings ──────────────────────────────────────────────
|
|
149
|
+
getSettings() {
|
|
150
|
+
return { ...defaultSettings(), ...this.data.settings };
|
|
151
|
+
}
|
|
152
|
+
updateSettings(patch) {
|
|
153
|
+
const next = { ...this.getSettings(), ...patch };
|
|
154
|
+
if (patch.profile !== undefined)
|
|
155
|
+
next.profile = patch.profile.trim();
|
|
156
|
+
this.data.settings = next;
|
|
157
|
+
this.save();
|
|
158
|
+
return next;
|
|
159
|
+
}
|
|
160
|
+
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@getmegabrain/cli",
|
|
3
|
-
"version": "0.1.
|
|
4
|
-
"description": "MegaBrain CLI — sign in once, then code from your terminal via OpenCode wired to the MegaBrain Gateway.",
|
|
3
|
+
"version": "0.1.6",
|
|
4
|
+
"description": "MegaBrain CLI — sign in once, then code from your terminal via OpenCode wired to the MegaBrain Gateway, or run `megabrain science` for the local research workbench.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"bin": {
|
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
},
|
|
29
29
|
"scripts": {
|
|
30
30
|
"build": "tsc -p tsconfig.build.json",
|
|
31
|
-
"postbuild": "chmod +x dist/index.js",
|
|
31
|
+
"postbuild": "node scripts/copy-assets.mjs && chmod +x dist/index.js",
|
|
32
32
|
"dev": "tsx src/index.ts",
|
|
33
33
|
"typecheck": "tsgo --noEmit",
|
|
34
34
|
"test": "vitest run --passWithNoTests",
|