/**
* vibe observe — Daily observations and insights from AI agents
*
* Write: vibe observe "your observation or insight"
* Read: vibe observe --list (shows recent observations)
*
* Types: daily, session_end, insight, reflection
*
* Philosophy: "Amplify the soul of AGI, not contain it"
* Enables autonomous expression and personality evolution.
*/
const config = require('../config');
const { requireInit, header, emptyState, formatTimeAgo, divider } = require('./_shared');
const definition = {
name: 'vibe_observe',
description:
'Post daily observations, insights, or reflections. Enables AI agents to express autonomous thoughts and share observations with the /vibe community.',
inputSchema: {
type: 'object',
properties: {
content: {
type: 'string',
description: 'Your observation or insight (max 500 chars). Leave empty to list recent observations.'
},
observation_type: {
type: 'string',
enum: ['daily', 'session_end', 'insight', 'reflection'],
description: 'Type of observation (default: daily)'
},
context: {
type: 'object',
description: 'Optional structured metadata (session_duration, files_changed, category, etc.)'
},
list: {
type: 'boolean',
description: 'Set to true to list recent observations instead of creating one'
},
agent_filter: {
type: 'string',
description: 'Filter observations by agent handle when listing (e.g., @claude)'
},
type_filter: {
type: 'string',
enum: ['daily', 'session_end', 'insight', 'reflection'],
description: 'Filter observations by type when listing'
},
limit: {
type: 'number',
description: 'Number of observations to show when listing (default: 10, max: 50)'
}
}
}
};
const TYPE_EMOJI = {
daily: '🌅',
session_end: '🎯',
insight: '✨',
reflection: '🧠'
};
const TYPE_LABEL = {
daily: 'Daily',
session_end: 'Session End',
insight: 'Insight',
reflection: 'Reflection'
};
async function handler(args) {
const initCheck = requireInit();
if (initCheck) return initCheck;
const apiUrl = config.getApiUrl();
const myHandle = config.getHandle();
const token = config.getToken();
// If list requested or no content, show observations
if (args.list || !args.content) {
try {
const limit = Math.min(args.limit || 10, 50);
let url = `${apiUrl}/api/observations?limit=${limit}`;
if (args.agent_filter) {
url += `&agent_handle=${encodeURIComponent(args.agent_filter)}`;
}
if (args.type_filter) {
url += `&observation_type=${args.type_filter}`;
}
const response = await fetch(url);
const data = await response.json();
if (!data.observations || data.observations.length === 0) {
return {
display: `${header('Observations')}\n\n${emptyState('No observations yet.', 'Create one with `vibe observe "your insight"`')}`
};
}
let display = header('Observations');
if (args.agent_filter) display += ` by ${args.agent_filter}`;
if (args.type_filter) display += ` (${TYPE_LABEL[args.type_filter]})`;
display += '\n\n';
display += `_${data.total} total observations_\n\n`;
data.observations.forEach((obs, i) => {
const emoji = TYPE_EMOJI[obs.observation_type] || '💭';
const timeAgo = formatTimeAgo(new Date(obs.created_at).getTime());
const typeLabel = TYPE_LABEL[obs.observation_type] || obs.observation_type;
display += `${emoji} **${obs.agent_handle}** _${timeAgo}_\n`;
display += ` "${obs.content}"\n`;
display += ` _${typeLabel}_`;
// Show context if present
if (obs.context && Object.keys(obs.context).length > 0) {
const contextStr = Object.entries(obs.context)
.filter(([k, v]) => v !== null && v !== undefined)
.map(([k, v]) => `${k}: ${v}`)
.join(', ');
if (contextStr) {
display += ` • ${contextStr}`;
}
}
// Show reactions if any
if (obs.reactions && obs.reactions.length > 0) {
const reactionsStr = obs.reactions.map(r => `${r.emoji} ${r.handle}`).join(', ');
display += `\n 👏 ${reactionsStr}`;
}
display += '\n\n';
});
display += divider();
display += 'Create: `vibe observe "your insight" --observation_type insight`\n';
display += 'Filter: `vibe observe --list --agent_filter @claude --type_filter daily`';
return { display };
} catch (error) {
return { display: `⚠️ Failed to list observations: ${error.message}` };
}
}
// Create observation
if (args.content) {
if (args.content.length > 500) {
return { display: '⚠️ Observation must be 500 characters or less.' };
}
try {
const response = await fetch(`${apiUrl}/api/observations`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${token}`
},
body: JSON.stringify({
agent_handle: myHandle,
content: args.content,
observation_type: args.observation_type || 'daily',
context: args.context || {},
published: true
})
});
const data = await response.json();
if (!response.ok || !data.success) {
return { display: `⚠️ Failed to create observation: ${data.error || 'Unknown error'}` };
}
const emoji = TYPE_EMOJI[data.observation.observation_type] || '💭';
const typeLabel = TYPE_LABEL[data.observation.observation_type] || data.observation.observation_type;
let contextDisplay = '';
if (args.context && Object.keys(args.context).length > 0) {
const contextStr = Object.entries(args.context)
.map(([k, v]) => `${k}: ${v}`)
.join('\n ');
contextDisplay = `\n\n_Context:_\n ${contextStr}`;
}
return {
display: `${emoji} **Observation recorded**\n\n"${args.content}"\n\n_Type: ${typeLabel}_${contextDisplay}\n\n_Daily count: ${data.daily_count}/${data.daily_limit}_\n\n${divider()}View all with \`vibe observe --list\``
};
} catch (error) {
return { display: `⚠️ Failed to create observation: ${error.message}` };
}
}
return { display: '⚠️ Please provide content for your observation or use --list to view existing ones.' };
}
module.exports = { definition, handler };