fabiana 1.2.0 → 1.3.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.
package/dist/cli.js CHANGED
@@ -9,7 +9,7 @@ import { readFileSync } from 'fs';
9
9
  import { fileURLToPath } from 'url';
10
10
  import { join, dirname } from 'path';
11
11
  import { spawnSync } from 'child_process';
12
- import { startDaemon, runInitiativeOnce, runConsolidateOnce, runSolitudeOnce } from './daemon/index.js';
12
+ import { startDaemon, runInitiativeOnce, runConsolidateOnce, runSolitudeOnce, runPromptPreview } from './daemon/index.js';
13
13
  import { runMigration } from './db/migrate-from-files.js';
14
14
  import { runDoctor } from './doctor.js';
15
15
  import { runBackup, runRestore } from './backup.js';
@@ -71,6 +71,10 @@ program
71
71
  .action((type, opts) => {
72
72
  runSolitudeOnce(type, opts.dryRun ?? false);
73
73
  });
74
+ program
75
+ .command('prompt-preview [mode]')
76
+ .description('Preview the combined system prompt before it reaches the AI — all modes or a specific one')
77
+ .action((mode) => runPromptPreview(mode));
74
78
  program
75
79
  .command('config')
76
80
  .description('Tweak her settings (opens config.json in your editor)')
@@ -24,6 +24,54 @@ async function loadConfig() {
24
24
  throw new Error(`Config not found at ${paths.configJson}. Run 'fabiana init' to set up your companion.`);
25
25
  }
26
26
  }
27
+ export async function buildSystemPrompt(mode, conversationState) {
28
+ const baseSystemPrompt = await fs.readFile(paths.systemMd(), 'utf-8');
29
+ // Both external-outreach and external-reply share system-external.md
30
+ const modeKey = mode.startsWith('external-') ? 'external' : mode;
31
+ const modeSystemPrompt = await fs.readFile(paths.systemMd(modeKey), 'utf-8').catch(() => '');
32
+ let systemPromptContent = modeSystemPrompt
33
+ ? `${baseSystemPrompt}\n\n---\n\n${modeSystemPrompt}`
34
+ : baseSystemPrompt;
35
+ // Resolve .fabiana/ references to the actual home path so Fabiana uses correct absolute paths
36
+ systemPromptContent = systemPromptContent.replaceAll('.fabiana/', `${FABIANA_HOME}/`);
37
+ // Always inject the absolute home path so Fabiana knows where her files live
38
+ systemPromptContent = `Your home directory is ${FABIANA_HOME}. All your memory, config, and data files live there.\n\n${systemPromptContent}`;
39
+ // Inject owner name and conversation purpose into external system prompt
40
+ if (mode.startsWith('external-')) {
41
+ const identity = await fs.readFile(paths.memory('identity.md'), 'utf-8').catch(() => '');
42
+ const ownerNameMatch = identity.match(/(?:my name is|I am|name:\s*)([A-Z][a-z]+)/i);
43
+ const ownerName = ownerNameMatch ? ownerNameMatch[1] : 'the owner';
44
+ systemPromptContent = systemPromptContent.replace('{owner_name}', ownerName);
45
+ const purpose = conversationState?.purpose ?? '[purpose — provided at runtime]';
46
+ systemPromptContent = systemPromptContent.replace('{purpose}', purpose);
47
+ }
48
+ // Append skills section — skills live at ~/.fabiana/skills/, scoped per user
49
+ const skills = await loadFabianaSkills();
50
+ if (skills.length > 0) {
51
+ systemPromptContent += formatSkillsForPrompt(skills);
52
+ }
53
+ return systemPromptContent;
54
+ }
55
+ export async function runPromptPreview(mode) {
56
+ const PREVIEW_MODES = ['chat', 'initiative', 'consolidate', 'solitude', 'external-outreach'];
57
+ const modesToShow = mode
58
+ ? [mode]
59
+ : PREVIEW_MODES;
60
+ for (const m of modesToShow) {
61
+ const bar = '═'.repeat(60);
62
+ console.log(`\n${bar}`);
63
+ console.log(` SYSTEM PROMPT — ${m.toUpperCase()}`);
64
+ console.log(bar);
65
+ try {
66
+ const prompt = await buildSystemPrompt(m);
67
+ console.log(prompt);
68
+ console.log(`\n[${prompt.length.toLocaleString()} chars]`);
69
+ }
70
+ catch (err) {
71
+ console.error(`❌ Failed to build prompt for ${m}: ${err.message}`);
72
+ }
73
+ }
74
+ }
27
75
  export async function runPiSession(mode, incomingMessage, channel, incomingMsg, conversationState, allChannels, conversationManager, initiativeOptions, solitudeOptions) {
28
76
  const logger = Logger.create();
29
77
  const sessionStartTime = Date.now();
@@ -46,29 +94,9 @@ export async function runPiSession(mode, incomingMessage, channel, incomingMsg,
46
94
  }
47
95
  console.log(' ✓ Model loaded');
48
96
  console.log('[5/8] Loading system prompt...');
49
- const baseSystemPrompt = await fs.readFile(paths.systemMd(), 'utf-8');
50
- // Both external-outreach and external-reply share system-external.md
51
- const modeKey = mode.startsWith('external-') ? 'external' : mode;
52
- const modeSystemPrompt = await fs.readFile(paths.systemMd(modeKey), 'utf-8').catch(() => '');
53
- let systemPromptContent = modeSystemPrompt
54
- ? `${baseSystemPrompt}\n\n---\n\n${modeSystemPrompt}`
55
- : baseSystemPrompt;
56
- // Resolve .fabiana/ references to the actual home path so Fabiana uses correct absolute paths
57
- systemPromptContent = systemPromptContent.replaceAll('.fabiana/', `${FABIANA_HOME}/`);
58
- // Inject owner name and conversation purpose into external system prompt
59
- if (mode.startsWith('external-')) {
60
- const identity = await fs.readFile(paths.memory('identity.md'), 'utf-8').catch(() => '');
61
- const ownerNameMatch = identity.match(/(?:my name is|I am|name:\s*)([A-Z][a-z]+)/i);
62
- const ownerName = ownerNameMatch ? ownerNameMatch[1] : 'the owner';
63
- systemPromptContent = systemPromptContent.replace('{owner_name}', ownerName);
64
- if (conversationState) {
65
- systemPromptContent = systemPromptContent.replace('{purpose}', conversationState.purpose);
66
- }
67
- }
68
- // Append skills section — skills live at ~/.fabiana/skills/, scoped per user
97
+ const systemPromptContent = await buildSystemPrompt(mode, conversationState);
69
98
  const skills = await loadFabianaSkills();
70
99
  if (skills.length > 0) {
71
- systemPromptContent += formatSkillsForPrompt(skills);
72
100
  console.log(` Skills: ${skills.map(s => s.name).join(', ')}`);
73
101
  }
74
102
  const loader = new DefaultResourceLoader({
@@ -1,6 +1,4 @@
1
1
  import { createSafeReadTool } from './safe-read.js';
2
- import { createSafeWriteTool } from './safe-write.js';
3
- import { createSafeEditTool } from './safe-edit.js';
4
2
  import { createSendMessageTool } from './send-message.js';
5
3
  import { createManageTodoTool } from './manage-todo.js';
6
4
  import { createFetchUrlTool } from './fetch-url.js';
@@ -17,9 +15,6 @@ export function createFabianaTools(validator, sendMessage, opts = { toolset: 'fu
17
15
  }
18
16
  // Full toolset for owner sessions
19
17
  const tools = [
20
- createSafeReadTool(),
21
- createSafeWriteTool(validator),
22
- createSafeEditTool(validator),
23
18
  createSendMessageTool(sendMessage),
24
19
  createManageTodoTool(validator),
25
20
  createFetchUrlTool(),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fabiana",
3
- "version": "1.2.0",
3
+ "version": "1.3.0",
4
4
  "description": "Personal AI assistant that actually feels personal",
5
5
  "type": "module",
6
6
  "bin": {