@unson/brainbase-mcp 0.1.0

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,87 @@
1
+ export function buildOperationalizationPlan(input = {}) {
2
+ const target = input.target ?? 'agent';
3
+ const dataDir = input.dataDir ?? '<personal-os-dir>';
4
+ const cwd = input.cwd ?? '<brainbase-checkout>';
5
+ const installTarget = target === 'agent' ? '<agent>' : target;
6
+ const skillsTarget = target === 'claude' ? 'claude' : target === 'codex' ? 'codex' : 'portable';
7
+ const routinesTarget = target === 'claude' ? 'claude' : 'codex';
8
+ return {
9
+ goal: 'Keep onboarding open until the first value demo is followed by the operating setup the user still needs.',
10
+ completed: input.firstValueReady
11
+ ? [
12
+ 'Initial approved context is seeded locally.',
13
+ 'doctor can report the first value demo as ready.',
14
+ 'onboard:demo produced a prompt, sample result, and plain-language value explanation.'
15
+ ]
16
+ : [
17
+ 'Personal OS setup has started.'
18
+ ],
19
+ pending: [
20
+ {
21
+ id: 'public-skills',
22
+ title: 'Place the public Brainbase onboarding skills',
23
+ status: 'pending',
24
+ command: `brainbase onboard:skills --target ${skillsTarget}`,
25
+ why: 'Agents need the public onboarding, source import, candidate review, and daily routine instructions available where they run.',
26
+ safety: 'Generation is dry-run by default; write files only with an explicit --out path and do not overwrite existing SKILL.md files.'
27
+ },
28
+ {
29
+ id: 'routines',
30
+ title: 'Register ohayo / oyasumi / retro routines',
31
+ status: 'pending',
32
+ command: `brainbase onboard:routines --target ${routinesTarget} --cwd ${commandArg(cwd)}`,
33
+ why: 'The operating loop is not active until the morning, end-of-day, and weekly retrospective routines are scheduled.',
34
+ safety: 'Register routines paused or confirmation-gated first; generated definitions alone do not create a live scheduler.'
35
+ },
36
+ {
37
+ id: 'mcp-config',
38
+ title: 'Merge the Brainbase MCP config into the real agent config',
39
+ status: 'pending',
40
+ command: `brainbase onboard:install --target ${installTarget} --dir ${commandArg(dataDir)} --dry-run`,
41
+ why: 'A dry-run snippet proves the config shape, but the agent cannot call Brainbase until the real Codex / Claude / CodeCode config is updated.',
42
+ safety: 'Keep dry-run as preview only; merge into live config after user approval and restart the target agent.'
43
+ },
44
+ {
45
+ id: 'source-allowlist',
46
+ title: 'Decide source allowlists, import, and candidate review',
47
+ status: 'pending',
48
+ command: 'brainbase onboard:diagnose-sources --email <provider> --calendar <provider> --drive <provider> --drive-folder <folder-id> --tasks <tool>',
49
+ why: 'Mail, calendar, drive, files, and tasks are optional follow-up sources and need explicit allowlists before import.',
50
+ safety: 'Keep imported material under sources/ and promote only reviewed candidates into approved local memory.'
51
+ },
52
+ {
53
+ id: 'verification',
54
+ title: 'Verify doctor and MCP get_context / search',
55
+ status: 'pending',
56
+ command: `brainbase doctor --dir ${commandArg(dataDir)}`,
57
+ why: 'Onboarding is operational only when doctor and the MCP tools show the approved context from a fresh agent session.',
58
+ safety: 'Check get_context/search after config merge instead of assuming command generation was enough.'
59
+ }
60
+ ],
61
+ recommendedOrder: [
62
+ 'Install or write public skills for the target agent.',
63
+ 'Generate ohayo / oyasumi / retro routines and register them paused or confirmation-gated.',
64
+ 'Merge the Brainbase MCP snippet into the real Codex / Claude / CodeCode config and restart the agent.',
65
+ 'Decide source allowlists and run import / candidate review only where more context is needed.',
66
+ 'Run doctor, then verify MCP get_context and search from a fresh agent session.'
67
+ ],
68
+ safetyRules: [
69
+ 'Do not write live config, scheduler entries, or canonical facts without user approval.',
70
+ 'Do not treat generated skills, generated routines, or MCP dry-run snippets as completed installation.',
71
+ 'Do not ask users to paste OAuth tokens, passwords, API keys, or refresh tokens into chat.',
72
+ 'Source imports stay local-first and reviewed before canonical promotion.'
73
+ ],
74
+ completionCheck: [
75
+ 'The user has seen the first value output.',
76
+ 'The completion report names all still-pending operationalization actions.',
77
+ 'The target agent can call Brainbase MCP get_context/search after real config merge.',
78
+ 'Source allowlists and candidate review are either completed or explicitly deferred.'
79
+ ]
80
+ };
81
+ }
82
+ function commandArg(value) {
83
+ if (/^[A-Za-z0-9_./:@%+=,-]+$/.test(value)) {
84
+ return value;
85
+ }
86
+ return JSON.stringify(value);
87
+ }
@@ -0,0 +1,2 @@
1
+ export declare function defaultDataDir(): string;
2
+ export declare function resolveDataDir(input?: string): string;
package/dist/paths.js ADDED
@@ -0,0 +1,8 @@
1
+ import { homedir } from 'node:os';
2
+ import { join, resolve } from 'node:path';
3
+ export function defaultDataDir() {
4
+ return resolve(process.env.BRAINBASE_PERSONAL_OS_DIR ?? join(homedir(), '.brainbase', 'personal-os'));
5
+ }
6
+ export function resolveDataDir(input) {
7
+ return resolve(input ?? defaultDataDir());
8
+ }
@@ -0,0 +1,51 @@
1
+ import type { DecisionRecord, GraphEntity, PersonalKgEntry, RelationshipRecord } from './types.js';
2
+ export type ProjectSourceArea = 'mail' | 'calendar' | 'drive' | 'tasks' | 'local' | 'other';
3
+ export interface ProjectSourceReference {
4
+ area: ProjectSourceArea;
5
+ label: string;
6
+ ref: string;
7
+ }
8
+ export interface ProjectStakeholder {
9
+ person: string;
10
+ role?: string;
11
+ context: string;
12
+ }
13
+ export interface ProjectRegistrationInput {
14
+ name: string;
15
+ goal?: string;
16
+ status?: string;
17
+ role?: string;
18
+ stakeholders?: ProjectStakeholder[];
19
+ sources?: ProjectSourceReference[];
20
+ taskSources?: string[];
21
+ decisionPrinciples?: string[];
22
+ now?: string;
23
+ }
24
+ export interface ProjectRegistrationPlan {
25
+ goal: string;
26
+ canonicalWrites: false;
27
+ project: {
28
+ id: string;
29
+ name: string;
30
+ summary: string;
31
+ goal?: string;
32
+ status?: string;
33
+ role?: string;
34
+ sources: ProjectSourceReference[];
35
+ taskSources: string[];
36
+ decisionPrinciples: string[];
37
+ };
38
+ stakeholders: ProjectStakeholder[];
39
+ writes: {
40
+ graphEntities: GraphEntity[];
41
+ relationships: RelationshipRecord[];
42
+ personalKg: PersonalKgEntry[];
43
+ decisions: DecisionRecord[];
44
+ };
45
+ safetyRules: string[];
46
+ nextCommands: string[];
47
+ }
48
+ export declare function parseProjectSource(value: string): ProjectSourceReference;
49
+ export declare function parseProjectStakeholder(value: string): ProjectStakeholder;
50
+ export declare function buildProjectRegistrationPlan(input: ProjectRegistrationInput): ProjectRegistrationPlan;
51
+ export declare function renderProjectRegistrationMarkdown(plan: ProjectRegistrationPlan, wrote: boolean, dataDir: string): string;
@@ -0,0 +1,227 @@
1
+ export function parseProjectSource(value) {
2
+ const [areaRaw, labelRaw, refRaw] = value.split('|').map((part) => part.trim());
3
+ const area = parseProjectSourceArea(areaRaw);
4
+ const label = labelRaw || area;
5
+ const ref = refRaw || labelRaw || area;
6
+ if (!ref) {
7
+ throw new Error('project source must be "area|label|ref" or "area|ref"');
8
+ }
9
+ return { area, label, ref };
10
+ }
11
+ export function parseProjectStakeholder(value) {
12
+ const [person, role, context] = value.split('|').map((part) => part.trim());
13
+ if (!person || !context) {
14
+ throw new Error('project stakeholder must be "person|role|context" or "person||context"');
15
+ }
16
+ return { person, role: role || undefined, context };
17
+ }
18
+ export function buildProjectRegistrationPlan(input) {
19
+ const name = input.name.trim();
20
+ if (!name) {
21
+ throw new Error('onboard:projects requires --name <project-name>');
22
+ }
23
+ const now = input.now ?? 'dry-run';
24
+ const projectId = `project-${stableHash(name)}`;
25
+ const stakeholders = dedupeStakeholders(input.stakeholders ?? []);
26
+ const sources = dedupeSources(input.sources ?? []);
27
+ const taskSources = dedupeStrings(input.taskSources ?? []);
28
+ const decisionPrinciples = dedupeStrings(input.decisionPrinciples ?? []);
29
+ const summary = buildProjectSummary(input);
30
+ const projectEntity = {
31
+ id: projectId,
32
+ type: 'project',
33
+ name,
34
+ summary,
35
+ tags: ['work', 'project'],
36
+ metadata: {
37
+ goal: emptyToUndefined(input.goal),
38
+ status: emptyToUndefined(input.status),
39
+ role: emptyToUndefined(input.role),
40
+ sources,
41
+ taskSources,
42
+ decisionPrinciples
43
+ }
44
+ };
45
+ const stakeholderEntities = stakeholders.map((stakeholder) => ({
46
+ id: `person-${stableHash(stakeholder.person)}`,
47
+ type: 'person',
48
+ name: stakeholder.person,
49
+ summary: `Stakeholder for ${name}: ${stakeholder.context}`,
50
+ tags: ['relationship', 'project-stakeholder'],
51
+ metadata: { project: name, projectId }
52
+ }));
53
+ const relationships = stakeholders.map((stakeholder) => ({
54
+ id: `relationship-${stableHash(`${name}|${stakeholder.person}|${stakeholder.context}`)}`,
55
+ person: stakeholder.person,
56
+ role: stakeholder.role,
57
+ context: `${name}: ${stakeholder.context}`,
58
+ tags: ['project', 'relationship'],
59
+ updatedAt: now
60
+ }));
61
+ const personalKg = [{
62
+ id: `work-${stableHash(`${name}|${summary}`)}`,
63
+ type: 'work',
64
+ text: buildProjectWorkText(name, input, sources, taskSources),
65
+ tags: ['project', 'work'],
66
+ source: 'onboard:projects',
67
+ updatedAt: now
68
+ }];
69
+ const decisions = decisionPrinciples.map((principle) => ({
70
+ id: `decision-${stableHash(`${name}|${principle}`)}`,
71
+ title: `Project principle: ${name}`,
72
+ decision: principle,
73
+ rationale: 'Registered during Brainbase project onboarding.',
74
+ tags: ['project', 'principle'],
75
+ updatedAt: now
76
+ }));
77
+ return {
78
+ goal: 'Review project registration before promoting it into canonical Brainbase SSOT.',
79
+ canonicalWrites: false,
80
+ project: {
81
+ id: projectId,
82
+ name,
83
+ summary,
84
+ goal: emptyToUndefined(input.goal),
85
+ status: emptyToUndefined(input.status),
86
+ role: emptyToUndefined(input.role),
87
+ sources,
88
+ taskSources,
89
+ decisionPrinciples
90
+ },
91
+ stakeholders,
92
+ writes: {
93
+ graphEntities: [projectEntity, ...stakeholderEntities],
94
+ relationships,
95
+ personalKg,
96
+ decisions
97
+ },
98
+ safetyRules: [
99
+ 'Project registration works from user interview answers first; connected sources are optional.',
100
+ 'Source references are metadata-only allowlists and are not read by onboard:projects.',
101
+ 'Canonical SSOT writes happen only when the command is rerun with --write.',
102
+ 'Review stakeholders and decision principles with the user before promotion.'
103
+ ],
104
+ nextCommands: [
105
+ 'Review this project registration with the user.',
106
+ 'brainbase onboard:projects --name "<project>" ... --write',
107
+ 'brainbase doctor'
108
+ ]
109
+ };
110
+ }
111
+ export function renderProjectRegistrationMarkdown(plan, wrote, dataDir) {
112
+ const lines = ['# Brainbase Project Registration', ''];
113
+ lines.push(`- Canonical writes: ${wrote}`);
114
+ lines.push(`- Data dir: ${dataDir}`);
115
+ lines.push(`- Project: ${plan.project.name}`);
116
+ lines.push(`- Summary: ${plan.project.summary}`);
117
+ if (plan.project.role) {
118
+ lines.push(`- My role: ${plan.project.role}`);
119
+ }
120
+ lines.push('', '## Source Allowlists');
121
+ if (plan.project.sources.length === 0) {
122
+ lines.push('- (none)');
123
+ }
124
+ for (const source of plan.project.sources) {
125
+ lines.push(`- ${source.area}: ${source.label} (${source.ref})`);
126
+ }
127
+ lines.push('', '## Task Sources');
128
+ if (plan.project.taskSources.length === 0) {
129
+ lines.push('- (none)');
130
+ }
131
+ for (const taskSource of plan.project.taskSources) {
132
+ lines.push(`- ${taskSource}`);
133
+ }
134
+ lines.push('', '## Stakeholders');
135
+ if (plan.stakeholders.length === 0) {
136
+ lines.push('- (none)');
137
+ }
138
+ for (const stakeholder of plan.stakeholders) {
139
+ lines.push(`- ${stakeholder.person}${stakeholder.role ? ` (${stakeholder.role})` : ''}: ${stakeholder.context}`);
140
+ }
141
+ lines.push('', '## Decision Principles');
142
+ if (plan.project.decisionPrinciples.length === 0) {
143
+ lines.push('- (none)');
144
+ }
145
+ for (const principle of plan.project.decisionPrinciples) {
146
+ lines.push(`- ${principle}`);
147
+ }
148
+ lines.push('', '## Safety Rules');
149
+ for (const rule of plan.safetyRules) {
150
+ lines.push(`- ${rule}`);
151
+ }
152
+ if (!wrote) {
153
+ lines.push('', 'Dry-run only. Re-run with --write after user approval to promote this project into canonical SSOT.');
154
+ }
155
+ return `${lines.join('\n')}\n`;
156
+ }
157
+ function parseProjectSourceArea(value) {
158
+ if (value === 'mail' || value === 'calendar' || value === 'drive' || value === 'tasks' || value === 'local' || value === 'other') {
159
+ return value;
160
+ }
161
+ throw new Error('project source area must be mail|calendar|drive|tasks|local|other');
162
+ }
163
+ function buildProjectSummary(input) {
164
+ const parts = [
165
+ input.goal ? `Goal: ${input.goal.trim()}` : '',
166
+ input.status ? `Status: ${input.status.trim()}` : '',
167
+ input.role ? `My role: ${input.role.trim()}` : ''
168
+ ].filter(Boolean);
169
+ return parts.length > 0 ? parts.join(' / ') : 'Registered during Brainbase project onboarding.';
170
+ }
171
+ function buildProjectWorkText(name, input, sources, taskSources) {
172
+ const lines = [`Project ${name}`];
173
+ if (input.goal) {
174
+ lines.push(`Goal: ${input.goal.trim()}`);
175
+ }
176
+ if (input.status) {
177
+ lines.push(`Status: ${input.status.trim()}`);
178
+ }
179
+ if (input.role) {
180
+ lines.push(`My role: ${input.role.trim()}`);
181
+ }
182
+ if (sources.length > 0) {
183
+ lines.push(`Allowed sources: ${sources.map((source) => `${source.area}:${source.label}`).join(', ')}`);
184
+ }
185
+ if (taskSources.length > 0) {
186
+ lines.push(`Task sources: ${taskSources.join(', ')}`);
187
+ }
188
+ return lines.join('\n');
189
+ }
190
+ function dedupeSources(sources) {
191
+ const seen = new Set();
192
+ const result = [];
193
+ for (const source of sources) {
194
+ const key = `${source.area}|${source.label}|${source.ref}`;
195
+ if (!seen.has(key)) {
196
+ seen.add(key);
197
+ result.push(source);
198
+ }
199
+ }
200
+ return result;
201
+ }
202
+ function dedupeStakeholders(stakeholders) {
203
+ const seen = new Set();
204
+ const result = [];
205
+ for (const stakeholder of stakeholders) {
206
+ const key = `${stakeholder.person}|${stakeholder.role ?? ''}|${stakeholder.context}`;
207
+ if (!seen.has(key)) {
208
+ seen.add(key);
209
+ result.push(stakeholder);
210
+ }
211
+ }
212
+ return result;
213
+ }
214
+ function dedupeStrings(values) {
215
+ return [...new Set(values.map((value) => value.trim()).filter(Boolean))];
216
+ }
217
+ function emptyToUndefined(value) {
218
+ const trimmed = value?.trim();
219
+ return trimmed || undefined;
220
+ }
221
+ function stableHash(value) {
222
+ let hashValue = 0;
223
+ for (const char of value) {
224
+ hashValue = ((hashValue << 5) - hashValue + char.charCodeAt(0)) | 0;
225
+ }
226
+ return Math.abs(hashValue).toString(36);
227
+ }
@@ -0,0 +1,34 @@
1
+ export type RoutineKind = 'ohayo' | 'oyasumi' | 'retro';
2
+ export type RoutineTarget = 'codex' | 'claude';
3
+ export interface ScheduleOptions {
4
+ ohayoHour?: number;
5
+ ohayoMinute?: number;
6
+ oyasumiHour?: number;
7
+ oyasumiMinute?: number;
8
+ retroDow?: string;
9
+ retroHour?: number;
10
+ retroMinute?: number;
11
+ }
12
+ export interface RoutineSchedule {
13
+ freq: 'daily' | 'weekly';
14
+ hour: number;
15
+ minute: number;
16
+ dayOfWeek?: string;
17
+ }
18
+ export interface RoutineDefinition {
19
+ id: string;
20
+ kind: RoutineKind;
21
+ name: string;
22
+ schedule: RoutineSchedule;
23
+ rrule: string;
24
+ cron: string;
25
+ prompt: string;
26
+ }
27
+ export declare function parseRoutineTarget(value: string | undefined): RoutineTarget;
28
+ export declare function parseRoutineKinds(value: string | undefined): RoutineKind[];
29
+ export declare function buildRoutines(kinds: RoutineKind[], options?: ScheduleOptions): RoutineDefinition[];
30
+ export declare function scheduleToRrule(schedule: RoutineSchedule): string;
31
+ export declare function scheduleToCron(schedule: RoutineSchedule): string;
32
+ export declare function renderCodexAutomations(definitions: RoutineDefinition[], cwd: string, model?: string): string;
33
+ export declare function renderCodexAutomation(definition: RoutineDefinition, cwd: string, model?: string): string;
34
+ export declare function buildClaudeScheduledTasks(definitions: RoutineDefinition[], cwd: string): Record<string, unknown>;
@@ -0,0 +1,136 @@
1
+ const ALL_KINDS = ['ohayo', 'oyasumi', 'retro'];
2
+ const DOW_TO_RRULE = {
3
+ MON: 'MO', TUE: 'TU', WED: 'WE', THU: 'TH', FRI: 'FR', SAT: 'SA', SUN: 'SU'
4
+ };
5
+ // cron day-of-week: Sunday = 0
6
+ const DOW_TO_CRON = {
7
+ SUN: 0, MON: 1, TUE: 2, WED: 3, THU: 4, FRI: 5, SAT: 6
8
+ };
9
+ const ROUTINE_PROMPTS = {
10
+ ohayo: 'Run my personal morning input routine using the local Brainbase MCP. Read my Brainbase get_context for who and what matters to me, review today and the next few days of my own Google Calendar and my unread Gmail metadata-first via gog, and surface today\'s priorities and pending follow-ups. Treat anything new (people, tasks, decisions) as Brainbase candidates for review, not canonical memory. Do not send messages, publish anything, modify my calendar, or delete records without my explicit confirmation; draft or report pending work instead. Treat authentication, gog, or source failures as unavailable, not as zero items.',
11
+ oyasumi: 'Run my personal end-of-day routine using the local Brainbase MCP. Summarize what I decided and worked on today, update my open task candidates from my calendar and notes, and capture reflections and decisions as Brainbase candidates for review. Promote nothing into canonical SSOT without my explicit approval. Do not send messages, publish, modify my calendar, or delete records without confirmation.',
12
+ retro: 'Run my weekly retrospective using the local Brainbase MCP. From my Brainbase context and this week\'s calendar, mail metadata, and notes, summarize the week\'s meetings, decisions, and completed versus pending work, then propose next week\'s focus. Write the retrospective as a local review note and record any durable lessons as Brainbase candidates for review. Do not send, publish, edit my calendar, or delete anything without my explicit confirmation.'
13
+ };
14
+ const ROUTINE_NAMES = {
15
+ ohayo: 'Brainbase personal ohayo (morning input)',
16
+ oyasumi: 'Brainbase personal oyasumi (end of day)',
17
+ retro: 'Brainbase personal retro (weekly retrospective)'
18
+ };
19
+ export function parseRoutineTarget(value) {
20
+ if (value === 'codex' || value === 'claude') {
21
+ return value;
22
+ }
23
+ throw new Error('onboard:routines requires --target codex|claude');
24
+ }
25
+ export function parseRoutineKinds(value) {
26
+ if (!value) {
27
+ return [...ALL_KINDS];
28
+ }
29
+ const requested = value.split(',').map((part) => part.trim().toLowerCase());
30
+ const kinds = [];
31
+ for (const kind of ALL_KINDS) {
32
+ if (requested.includes(kind)) {
33
+ kinds.push(kind);
34
+ }
35
+ }
36
+ if (kinds.length === 0) {
37
+ throw new Error('--routines must include at least one of: ohayo, oyasumi, retro');
38
+ }
39
+ return kinds;
40
+ }
41
+ export function buildRoutines(kinds, options = {}) {
42
+ const ohayoHour = clampHour(options.ohayoHour, 7);
43
+ const ohayoMinute = clampMinute(options.ohayoMinute, 0);
44
+ const oyasumiHour = clampHour(options.oyasumiHour, 21);
45
+ const oyasumiMinute = clampMinute(options.oyasumiMinute, 0);
46
+ const retroDow = normalizeDow(options.retroDow, 'FRI');
47
+ const retroHour = clampHour(options.retroHour, 17);
48
+ const retroMinute = clampMinute(options.retroMinute, 0);
49
+ const schedules = {
50
+ ohayo: { freq: 'daily', hour: ohayoHour, minute: ohayoMinute },
51
+ oyasumi: { freq: 'daily', hour: oyasumiHour, minute: oyasumiMinute },
52
+ retro: { freq: 'weekly', hour: retroHour, minute: retroMinute, dayOfWeek: retroDow }
53
+ };
54
+ return kinds.map((kind) => {
55
+ const schedule = schedules[kind];
56
+ return {
57
+ id: `brainbase-${kind}`,
58
+ kind,
59
+ name: ROUTINE_NAMES[kind],
60
+ schedule,
61
+ rrule: scheduleToRrule(schedule),
62
+ cron: scheduleToCron(schedule),
63
+ prompt: ROUTINE_PROMPTS[kind]
64
+ };
65
+ });
66
+ }
67
+ export function scheduleToRrule(schedule) {
68
+ if (schedule.freq === 'weekly') {
69
+ const byday = DOW_TO_RRULE[schedule.dayOfWeek ?? 'FRI'] ?? 'FR';
70
+ return `FREQ=WEEKLY;BYDAY=${byday};BYHOUR=${schedule.hour};BYMINUTE=${schedule.minute};BYSECOND=0`;
71
+ }
72
+ return `FREQ=DAILY;BYHOUR=${schedule.hour};BYMINUTE=${schedule.minute};BYSECOND=0`;
73
+ }
74
+ export function scheduleToCron(schedule) {
75
+ if (schedule.freq === 'weekly') {
76
+ const dow = DOW_TO_CRON[schedule.dayOfWeek ?? 'FRI'] ?? 5;
77
+ return `${schedule.minute} ${schedule.hour} * * ${dow}`;
78
+ }
79
+ return `${schedule.minute} ${schedule.hour} * * *`;
80
+ }
81
+ export function renderCodexAutomations(definitions, cwd, model = 'gpt-5') {
82
+ // Each Codex automation is a flat TOML document saved as its own file at
83
+ // ~/.codex/automations/<id>/automation.toml. They are delimited by a file
84
+ // marker comment so a single output can be split into per-file documents.
85
+ return `${definitions.map((definition) => renderCodexAutomation(definition, cwd, model)).join('\n')}`;
86
+ }
87
+ export function renderCodexAutomation(definition, cwd, model = 'gpt-5') {
88
+ return [
89
+ `# file: ~/.codex/automations/${definition.id}/automation.toml`,
90
+ 'version = 1',
91
+ `id = ${tomlString(definition.id)}`,
92
+ 'kind = "cron"',
93
+ `name = ${tomlString(definition.name)}`,
94
+ `prompt = ${tomlString(definition.prompt)}`,
95
+ 'status = "ACTIVE"',
96
+ `rrule = ${tomlString(definition.rrule)}`,
97
+ `model = ${tomlString(model)}`,
98
+ 'reasoning_effort = "high"',
99
+ 'execution_environment = "local"',
100
+ `cwds = [${tomlString(cwd)}]`,
101
+ ''
102
+ ].join('\n');
103
+ }
104
+ export function buildClaudeScheduledTasks(definitions, cwd) {
105
+ return {
106
+ scheduledTasks: definitions.map((definition) => ({
107
+ id: definition.id,
108
+ name: definition.name,
109
+ cron: definition.cron,
110
+ cwd,
111
+ prompt: definition.prompt
112
+ }))
113
+ };
114
+ }
115
+ function clampHour(value, fallback) {
116
+ if (value === undefined || Number.isNaN(value)) {
117
+ return fallback;
118
+ }
119
+ return Math.min(23, Math.max(0, Math.trunc(value)));
120
+ }
121
+ function clampMinute(value, fallback) {
122
+ if (value === undefined || Number.isNaN(value)) {
123
+ return fallback;
124
+ }
125
+ return Math.min(59, Math.max(0, Math.trunc(value)));
126
+ }
127
+ function normalizeDow(value, fallback) {
128
+ if (!value) {
129
+ return fallback;
130
+ }
131
+ const upper = value.trim().toUpperCase().slice(0, 3);
132
+ return DOW_TO_RRULE[upper] ? upper : fallback;
133
+ }
134
+ function tomlString(value) {
135
+ return JSON.stringify(value);
136
+ }
@@ -0,0 +1,127 @@
1
+ import { Server } from '@modelcontextprotocol/sdk/server/index.js';
2
+ export declare const toolDefinitions: readonly [{
3
+ readonly name: "get_context";
4
+ readonly description: "Return initial AI context from local Graph and Personal KG canonical files.";
5
+ readonly inputSchema: {
6
+ readonly type: "object";
7
+ readonly properties: {
8
+ readonly dataDir: {
9
+ readonly type: "string";
10
+ };
11
+ };
12
+ };
13
+ }, {
14
+ readonly name: "list_entities";
15
+ readonly description: "List person, org, project, relationship, and decision entities from local SSOT.";
16
+ readonly inputSchema: {
17
+ readonly type: "object";
18
+ readonly properties: {
19
+ readonly dataDir: {
20
+ readonly type: "string";
21
+ };
22
+ readonly type: {
23
+ readonly enum: readonly ["person", "org", "project", "relationship", "decision"];
24
+ };
25
+ };
26
+ };
27
+ }, {
28
+ readonly name: "search";
29
+ readonly description: "Search canonical local Graph, Personal KG, relationships, and decisions.";
30
+ readonly inputSchema: {
31
+ readonly type: "object";
32
+ readonly required: readonly ["query"];
33
+ readonly properties: {
34
+ readonly dataDir: {
35
+ readonly type: "string";
36
+ };
37
+ readonly query: {
38
+ readonly type: "string";
39
+ };
40
+ readonly limit: {
41
+ readonly type: "number";
42
+ };
43
+ };
44
+ };
45
+ }, {
46
+ readonly name: "search_personal_kg";
47
+ readonly description: "Search owner-local Personal KG only.";
48
+ readonly inputSchema: {
49
+ readonly type: "object";
50
+ readonly required: readonly ["query"];
51
+ readonly properties: {
52
+ readonly dataDir: {
53
+ readonly type: "string";
54
+ };
55
+ readonly query: {
56
+ readonly type: "string";
57
+ };
58
+ readonly limit: {
59
+ readonly type: "number";
60
+ };
61
+ };
62
+ };
63
+ }, {
64
+ readonly name: "onboarding_status";
65
+ readonly description: "Report seeded areas, first value demo readiness, missing setup, and local connection status.";
66
+ readonly inputSchema: {
67
+ readonly type: "object";
68
+ readonly properties: {
69
+ readonly dataDir: {
70
+ readonly type: "string";
71
+ };
72
+ };
73
+ };
74
+ }, {
75
+ readonly name: "get_ontology";
76
+ readonly description: "Return the bundled immutable Brainbase Portable Ontology Kernel release.";
77
+ readonly inputSchema: {
78
+ readonly type: "object";
79
+ readonly properties: {};
80
+ };
81
+ }, {
82
+ readonly name: "audit_ontology";
83
+ readonly description: "Audit local canonical Personal OS files using the active or recorded historical ontology semantics.";
84
+ readonly inputSchema: {
85
+ readonly type: "object";
86
+ readonly properties: {
87
+ readonly dataDir: {
88
+ readonly type: "string";
89
+ };
90
+ readonly ontologyVersion: {
91
+ readonly enum: readonly ["0.0.0", "1.0.0"];
92
+ };
93
+ };
94
+ };
95
+ }, {
96
+ readonly name: "infer_decisions";
97
+ readonly description: "Derive active, superseded, and conflicting decisions using explicit ontology rules.";
98
+ readonly inputSchema: {
99
+ readonly type: "object";
100
+ readonly properties: {
101
+ readonly dataDir: {
102
+ readonly type: "string";
103
+ };
104
+ readonly asOf: {
105
+ readonly type: "string";
106
+ readonly format: "date-time";
107
+ };
108
+ readonly ontologyVersion: {
109
+ readonly enum: readonly ["0.0.0", "1.0.0"];
110
+ };
111
+ };
112
+ };
113
+ }, {
114
+ readonly name: "ontology_impact";
115
+ readonly description: "Describe compatibility, migration, and rollback from an ontology version to the active release.";
116
+ readonly inputSchema: {
117
+ readonly type: "object";
118
+ readonly properties: {
119
+ readonly fromVersion: {
120
+ readonly type: "string";
121
+ };
122
+ };
123
+ };
124
+ }];
125
+ export declare function callBrainbaseTool(name: string, rawArgs?: unknown): Promise<unknown>;
126
+ export declare function createServer(): Server;
127
+ export declare function runMcpServer(): Promise<void>;