@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.
@@ -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",
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",