apexbot 1.0.2 → 1.0.4
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/agent/agentManager.js +52 -5
- package/dist/agent/toolExecutor.js +144 -0
- package/dist/channels/channelManager.js +3 -1
- package/dist/cli/index.js +114 -0
- package/dist/gateway/index.js +62 -8
- package/dist/skills/index.js +212 -0
- package/dist/skills/obsidian.js +440 -0
- package/dist/skills/reminder.js +430 -0
- package/dist/skills/system.js +360 -0
- package/dist/skills/weather.js +144 -0
- package/dist/tools/datetime.js +188 -0
- package/dist/tools/file.js +352 -0
- package/dist/tools/index.js +111 -0
- package/dist/tools/loader.js +66 -0
- package/dist/tools/math.js +248 -0
- package/dist/tools/shell.js +104 -0
- package/dist/tools/web.js +197 -0
- package/package.json +2 -2
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* ApexBot Skills System
|
|
4
|
+
*
|
|
5
|
+
* Skills are collections of tools for specific integrations (Obsidian, Spotify, etc.)
|
|
6
|
+
* Similar to Clawdbot's skills/integrations architecture.
|
|
7
|
+
*/
|
|
8
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
9
|
+
if (k2 === undefined) k2 = k;
|
|
10
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
11
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
12
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
13
|
+
}
|
|
14
|
+
Object.defineProperty(o, k2, desc);
|
|
15
|
+
}) : (function(o, m, k, k2) {
|
|
16
|
+
if (k2 === undefined) k2 = k;
|
|
17
|
+
o[k2] = m[k];
|
|
18
|
+
}));
|
|
19
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
20
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
21
|
+
}) : function(o, v) {
|
|
22
|
+
o["default"] = v;
|
|
23
|
+
});
|
|
24
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
25
|
+
var ownKeys = function(o) {
|
|
26
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
27
|
+
var ar = [];
|
|
28
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
29
|
+
return ar;
|
|
30
|
+
};
|
|
31
|
+
return ownKeys(o);
|
|
32
|
+
};
|
|
33
|
+
return function (mod) {
|
|
34
|
+
if (mod && mod.__esModule) return mod;
|
|
35
|
+
var result = {};
|
|
36
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
37
|
+
__setModuleDefault(result, mod);
|
|
38
|
+
return result;
|
|
39
|
+
};
|
|
40
|
+
})();
|
|
41
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
42
|
+
exports.SkillManager = void 0;
|
|
43
|
+
exports.getSkillManager = getSkillManager;
|
|
44
|
+
exports.initSkillManager = initSkillManager;
|
|
45
|
+
const events_1 = require("events");
|
|
46
|
+
const fs = __importStar(require("fs/promises"));
|
|
47
|
+
const path = __importStar(require("path"));
|
|
48
|
+
const tools_1 = require("../tools");
|
|
49
|
+
class SkillManager extends events_1.EventEmitter {
|
|
50
|
+
skills = new Map();
|
|
51
|
+
enabledSkills = new Set();
|
|
52
|
+
skillConfigs = new Map();
|
|
53
|
+
configDir;
|
|
54
|
+
constructor(configDir) {
|
|
55
|
+
super();
|
|
56
|
+
this.configDir = configDir;
|
|
57
|
+
}
|
|
58
|
+
async loadConfig() {
|
|
59
|
+
try {
|
|
60
|
+
const configPath = path.join(this.configDir, 'skills.json');
|
|
61
|
+
const data = await fs.readFile(configPath, 'utf-8');
|
|
62
|
+
const config = JSON.parse(data);
|
|
63
|
+
this.enabledSkills = new Set(config.enabled || []);
|
|
64
|
+
this.skillConfigs = new Map(Object.entries(config.configs || {}));
|
|
65
|
+
console.log('[Skills] Loaded config, enabled:', Array.from(this.enabledSkills));
|
|
66
|
+
}
|
|
67
|
+
catch (error) {
|
|
68
|
+
// Config doesn't exist yet, use defaults
|
|
69
|
+
console.log('[Skills] No config found, using defaults');
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
async saveConfig() {
|
|
73
|
+
const configPath = path.join(this.configDir, 'skills.json');
|
|
74
|
+
const config = {
|
|
75
|
+
enabled: Array.from(this.enabledSkills),
|
|
76
|
+
configs: Object.fromEntries(this.skillConfigs),
|
|
77
|
+
};
|
|
78
|
+
await fs.mkdir(this.configDir, { recursive: true });
|
|
79
|
+
await fs.writeFile(configPath, JSON.stringify(config, null, 2));
|
|
80
|
+
console.log('[Skills] Saved config');
|
|
81
|
+
}
|
|
82
|
+
register(skill) {
|
|
83
|
+
const name = skill.manifest.name;
|
|
84
|
+
this.skills.set(name, skill);
|
|
85
|
+
console.log(`[Skills] Registered: ${name} v${skill.manifest.version}`);
|
|
86
|
+
this.emit('skill:registered', { name, manifest: skill.manifest });
|
|
87
|
+
}
|
|
88
|
+
async enable(name, config) {
|
|
89
|
+
const skill = this.skills.get(name);
|
|
90
|
+
if (!skill) {
|
|
91
|
+
console.error(`[Skills] Skill not found: ${name}`);
|
|
92
|
+
return false;
|
|
93
|
+
}
|
|
94
|
+
try {
|
|
95
|
+
// Store config
|
|
96
|
+
if (config) {
|
|
97
|
+
this.skillConfigs.set(name, config);
|
|
98
|
+
}
|
|
99
|
+
// Initialize skill
|
|
100
|
+
const skillConfig = this.skillConfigs.get(name) || {};
|
|
101
|
+
if (skill.initialize) {
|
|
102
|
+
await skill.initialize(skillConfig);
|
|
103
|
+
}
|
|
104
|
+
// Register tools
|
|
105
|
+
for (const tool of skill.tools) {
|
|
106
|
+
tools_1.toolRegistry.register(tool);
|
|
107
|
+
}
|
|
108
|
+
this.enabledSkills.add(name);
|
|
109
|
+
await this.saveConfig();
|
|
110
|
+
console.log(`[Skills] Enabled: ${name}`);
|
|
111
|
+
this.emit('skill:enabled', { name });
|
|
112
|
+
return true;
|
|
113
|
+
}
|
|
114
|
+
catch (error) {
|
|
115
|
+
console.error(`[Skills] Failed to enable ${name}:`, error.message);
|
|
116
|
+
return false;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
async disable(name) {
|
|
120
|
+
const skill = this.skills.get(name);
|
|
121
|
+
if (!skill) {
|
|
122
|
+
return false;
|
|
123
|
+
}
|
|
124
|
+
try {
|
|
125
|
+
// Shutdown skill
|
|
126
|
+
if (skill.shutdown) {
|
|
127
|
+
await skill.shutdown();
|
|
128
|
+
}
|
|
129
|
+
// Unregister tools
|
|
130
|
+
for (const tool of skill.tools) {
|
|
131
|
+
tools_1.toolRegistry.unregister(tool.definition.name);
|
|
132
|
+
}
|
|
133
|
+
this.enabledSkills.delete(name);
|
|
134
|
+
await this.saveConfig();
|
|
135
|
+
console.log(`[Skills] Disabled: ${name}`);
|
|
136
|
+
this.emit('skill:disabled', { name });
|
|
137
|
+
return true;
|
|
138
|
+
}
|
|
139
|
+
catch (error) {
|
|
140
|
+
console.error(`[Skills] Failed to disable ${name}:`, error.message);
|
|
141
|
+
return false;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
isEnabled(name) {
|
|
145
|
+
return this.enabledSkills.has(name);
|
|
146
|
+
}
|
|
147
|
+
get(name) {
|
|
148
|
+
return this.skills.get(name);
|
|
149
|
+
}
|
|
150
|
+
list() {
|
|
151
|
+
return Array.from(this.skills.values()).map(skill => ({
|
|
152
|
+
name: skill.manifest.name,
|
|
153
|
+
version: skill.manifest.version,
|
|
154
|
+
description: skill.manifest.description,
|
|
155
|
+
category: skill.manifest.category,
|
|
156
|
+
enabled: this.enabledSkills.has(skill.manifest.name),
|
|
157
|
+
tools: skill.tools.map(t => t.definition.name),
|
|
158
|
+
}));
|
|
159
|
+
}
|
|
160
|
+
listEnabled() {
|
|
161
|
+
return this.list().filter(s => s.enabled);
|
|
162
|
+
}
|
|
163
|
+
listByCategory(category) {
|
|
164
|
+
return this.list().filter(s => s.category === category);
|
|
165
|
+
}
|
|
166
|
+
getConfig(name) {
|
|
167
|
+
return this.skillConfigs.get(name);
|
|
168
|
+
}
|
|
169
|
+
async setConfig(name, config) {
|
|
170
|
+
this.skillConfigs.set(name, config);
|
|
171
|
+
await this.saveConfig();
|
|
172
|
+
// Re-initialize if enabled
|
|
173
|
+
if (this.enabledSkills.has(name)) {
|
|
174
|
+
const skill = this.skills.get(name);
|
|
175
|
+
if (skill?.initialize) {
|
|
176
|
+
await skill.initialize(config);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
// Initialize all enabled skills on startup
|
|
181
|
+
async initializeEnabled() {
|
|
182
|
+
for (const name of this.enabledSkills) {
|
|
183
|
+
const skill = this.skills.get(name);
|
|
184
|
+
if (skill) {
|
|
185
|
+
try {
|
|
186
|
+
const config = this.skillConfigs.get(name) || {};
|
|
187
|
+
if (skill.initialize) {
|
|
188
|
+
await skill.initialize(config);
|
|
189
|
+
}
|
|
190
|
+
for (const tool of skill.tools) {
|
|
191
|
+
tools_1.toolRegistry.register(tool);
|
|
192
|
+
}
|
|
193
|
+
console.log(`[Skills] Initialized: ${name}`);
|
|
194
|
+
}
|
|
195
|
+
catch (error) {
|
|
196
|
+
console.error(`[Skills] Failed to initialize ${name}:`, error.message);
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
exports.SkillManager = SkillManager;
|
|
203
|
+
// Global skill manager instance (initialized in gateway)
|
|
204
|
+
let skillManager = null;
|
|
205
|
+
function getSkillManager() {
|
|
206
|
+
return skillManager;
|
|
207
|
+
}
|
|
208
|
+
function initSkillManager(configDir) {
|
|
209
|
+
skillManager = new SkillManager(configDir);
|
|
210
|
+
return skillManager;
|
|
211
|
+
}
|
|
212
|
+
exports.default = { SkillManager, getSkillManager, initSkillManager };
|
|
@@ -0,0 +1,440 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Obsidian Skill
|
|
4
|
+
*
|
|
5
|
+
* Integration with Obsidian note-taking app.
|
|
6
|
+
* Supports reading, writing, searching notes, and managing daily notes.
|
|
7
|
+
*/
|
|
8
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
9
|
+
if (k2 === undefined) k2 = k;
|
|
10
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
11
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
12
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
13
|
+
}
|
|
14
|
+
Object.defineProperty(o, k2, desc);
|
|
15
|
+
}) : (function(o, m, k, k2) {
|
|
16
|
+
if (k2 === undefined) k2 = k;
|
|
17
|
+
o[k2] = m[k];
|
|
18
|
+
}));
|
|
19
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
20
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
21
|
+
}) : function(o, v) {
|
|
22
|
+
o["default"] = v;
|
|
23
|
+
});
|
|
24
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
25
|
+
var ownKeys = function(o) {
|
|
26
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
27
|
+
var ar = [];
|
|
28
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
29
|
+
return ar;
|
|
30
|
+
};
|
|
31
|
+
return ownKeys(o);
|
|
32
|
+
};
|
|
33
|
+
return function (mod) {
|
|
34
|
+
if (mod && mod.__esModule) return mod;
|
|
35
|
+
var result = {};
|
|
36
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
37
|
+
__setModuleDefault(result, mod);
|
|
38
|
+
return result;
|
|
39
|
+
};
|
|
40
|
+
})();
|
|
41
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
42
|
+
const fs = __importStar(require("fs/promises"));
|
|
43
|
+
const path = __importStar(require("path"));
|
|
44
|
+
let vaultPath = '';
|
|
45
|
+
const searchNotesTool = {
|
|
46
|
+
definition: {
|
|
47
|
+
name: 'obsidian_search',
|
|
48
|
+
description: 'Search for notes in Obsidian vault by content or filename.',
|
|
49
|
+
category: 'productivity',
|
|
50
|
+
parameters: [
|
|
51
|
+
{
|
|
52
|
+
name: 'query',
|
|
53
|
+
type: 'string',
|
|
54
|
+
description: 'Search query',
|
|
55
|
+
required: true,
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
name: 'type',
|
|
59
|
+
type: 'string',
|
|
60
|
+
description: 'Search type: content, filename, tag',
|
|
61
|
+
required: false,
|
|
62
|
+
default: 'content',
|
|
63
|
+
enum: ['content', 'filename', 'tag'],
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
name: 'limit',
|
|
67
|
+
type: 'number',
|
|
68
|
+
description: 'Maximum results',
|
|
69
|
+
required: false,
|
|
70
|
+
default: 10,
|
|
71
|
+
},
|
|
72
|
+
],
|
|
73
|
+
returns: 'List of matching notes',
|
|
74
|
+
},
|
|
75
|
+
async execute(params, context) {
|
|
76
|
+
const { query, type = 'content', limit = 10 } = params;
|
|
77
|
+
if (!vaultPath) {
|
|
78
|
+
return { success: false, error: 'Obsidian vault not configured' };
|
|
79
|
+
}
|
|
80
|
+
try {
|
|
81
|
+
const results = [];
|
|
82
|
+
async function searchDir(dir) {
|
|
83
|
+
const entries = await fs.readdir(dir, { withFileTypes: true });
|
|
84
|
+
for (const entry of entries) {
|
|
85
|
+
if (results.length >= limit)
|
|
86
|
+
return;
|
|
87
|
+
const fullPath = path.join(dir, entry.name);
|
|
88
|
+
const relativePath = path.relative(vaultPath, fullPath);
|
|
89
|
+
// Skip hidden files/folders
|
|
90
|
+
if (entry.name.startsWith('.'))
|
|
91
|
+
continue;
|
|
92
|
+
if (entry.isDirectory()) {
|
|
93
|
+
await searchDir(fullPath);
|
|
94
|
+
}
|
|
95
|
+
else if (entry.name.endsWith('.md')) {
|
|
96
|
+
const noteName = entry.name.replace('.md', '');
|
|
97
|
+
if (type === 'filename') {
|
|
98
|
+
if (noteName.toLowerCase().includes(query.toLowerCase())) {
|
|
99
|
+
results.push({
|
|
100
|
+
name: noteName,
|
|
101
|
+
path: relativePath,
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
else if (type === 'content' || type === 'tag') {
|
|
106
|
+
const content = await fs.readFile(fullPath, 'utf-8');
|
|
107
|
+
if (type === 'tag') {
|
|
108
|
+
const tagPattern = new RegExp(`#${query}\\b`, 'i');
|
|
109
|
+
if (tagPattern.test(content)) {
|
|
110
|
+
results.push({
|
|
111
|
+
name: noteName,
|
|
112
|
+
path: relativePath,
|
|
113
|
+
preview: content.slice(0, 200),
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
else {
|
|
118
|
+
if (content.toLowerCase().includes(query.toLowerCase())) {
|
|
119
|
+
const idx = content.toLowerCase().indexOf(query.toLowerCase());
|
|
120
|
+
const start = Math.max(0, idx - 50);
|
|
121
|
+
const end = Math.min(content.length, idx + query.length + 50);
|
|
122
|
+
results.push({
|
|
123
|
+
name: noteName,
|
|
124
|
+
path: relativePath,
|
|
125
|
+
preview: content.slice(start, end),
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
await searchDir(vaultPath);
|
|
134
|
+
return {
|
|
135
|
+
success: true,
|
|
136
|
+
data: {
|
|
137
|
+
query,
|
|
138
|
+
type,
|
|
139
|
+
results,
|
|
140
|
+
count: results.length,
|
|
141
|
+
},
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
catch (error) {
|
|
145
|
+
return { success: false, error: error.message };
|
|
146
|
+
}
|
|
147
|
+
},
|
|
148
|
+
};
|
|
149
|
+
const readNoteTool = {
|
|
150
|
+
definition: {
|
|
151
|
+
name: 'obsidian_read',
|
|
152
|
+
description: 'Read the content of an Obsidian note.',
|
|
153
|
+
category: 'productivity',
|
|
154
|
+
parameters: [
|
|
155
|
+
{
|
|
156
|
+
name: 'name',
|
|
157
|
+
type: 'string',
|
|
158
|
+
description: 'Note name or path (without .md)',
|
|
159
|
+
required: true,
|
|
160
|
+
},
|
|
161
|
+
],
|
|
162
|
+
returns: 'Note content',
|
|
163
|
+
},
|
|
164
|
+
async execute(params, context) {
|
|
165
|
+
const { name } = params;
|
|
166
|
+
if (!vaultPath) {
|
|
167
|
+
return { success: false, error: 'Obsidian vault not configured' };
|
|
168
|
+
}
|
|
169
|
+
try {
|
|
170
|
+
let notePath = name;
|
|
171
|
+
if (!notePath.endsWith('.md')) {
|
|
172
|
+
notePath += '.md';
|
|
173
|
+
}
|
|
174
|
+
const fullPath = path.join(vaultPath, notePath);
|
|
175
|
+
const content = await fs.readFile(fullPath, 'utf-8');
|
|
176
|
+
// Parse frontmatter if present
|
|
177
|
+
let frontmatter = {};
|
|
178
|
+
let body = content;
|
|
179
|
+
if (content.startsWith('---')) {
|
|
180
|
+
const endIdx = content.indexOf('---', 3);
|
|
181
|
+
if (endIdx !== -1) {
|
|
182
|
+
const yamlStr = content.slice(3, endIdx).trim();
|
|
183
|
+
body = content.slice(endIdx + 3).trim();
|
|
184
|
+
// Simple YAML parsing
|
|
185
|
+
yamlStr.split('\n').forEach(line => {
|
|
186
|
+
const [key, ...valueParts] = line.split(':');
|
|
187
|
+
if (key && valueParts.length) {
|
|
188
|
+
frontmatter[key.trim()] = valueParts.join(':').trim();
|
|
189
|
+
}
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
return {
|
|
194
|
+
success: true,
|
|
195
|
+
data: {
|
|
196
|
+
name,
|
|
197
|
+
path: notePath,
|
|
198
|
+
frontmatter,
|
|
199
|
+
content: body,
|
|
200
|
+
},
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
catch (error) {
|
|
204
|
+
return { success: false, error: error.message };
|
|
205
|
+
}
|
|
206
|
+
},
|
|
207
|
+
};
|
|
208
|
+
const writeNoteTool = {
|
|
209
|
+
definition: {
|
|
210
|
+
name: 'obsidian_write',
|
|
211
|
+
description: 'Create or update an Obsidian note.',
|
|
212
|
+
category: 'productivity',
|
|
213
|
+
parameters: [
|
|
214
|
+
{
|
|
215
|
+
name: 'name',
|
|
216
|
+
type: 'string',
|
|
217
|
+
description: 'Note name or path (without .md)',
|
|
218
|
+
required: true,
|
|
219
|
+
},
|
|
220
|
+
{
|
|
221
|
+
name: 'content',
|
|
222
|
+
type: 'string',
|
|
223
|
+
description: 'Note content (markdown)',
|
|
224
|
+
required: true,
|
|
225
|
+
},
|
|
226
|
+
{
|
|
227
|
+
name: 'append',
|
|
228
|
+
type: 'boolean',
|
|
229
|
+
description: 'Append to existing note',
|
|
230
|
+
required: false,
|
|
231
|
+
default: false,
|
|
232
|
+
},
|
|
233
|
+
],
|
|
234
|
+
returns: 'Success confirmation',
|
|
235
|
+
},
|
|
236
|
+
async execute(params, context) {
|
|
237
|
+
const { name, content, append = false } = params;
|
|
238
|
+
if (!vaultPath) {
|
|
239
|
+
return { success: false, error: 'Obsidian vault not configured' };
|
|
240
|
+
}
|
|
241
|
+
try {
|
|
242
|
+
let notePath = name;
|
|
243
|
+
if (!notePath.endsWith('.md')) {
|
|
244
|
+
notePath += '.md';
|
|
245
|
+
}
|
|
246
|
+
const fullPath = path.join(vaultPath, notePath);
|
|
247
|
+
// Ensure directory exists
|
|
248
|
+
await fs.mkdir(path.dirname(fullPath), { recursive: true });
|
|
249
|
+
if (append) {
|
|
250
|
+
const existing = await fs.readFile(fullPath, 'utf-8').catch(() => '');
|
|
251
|
+
await fs.writeFile(fullPath, existing + '\n' + content);
|
|
252
|
+
}
|
|
253
|
+
else {
|
|
254
|
+
await fs.writeFile(fullPath, content);
|
|
255
|
+
}
|
|
256
|
+
return {
|
|
257
|
+
success: true,
|
|
258
|
+
data: {
|
|
259
|
+
name,
|
|
260
|
+
path: notePath,
|
|
261
|
+
action: append ? 'appended' : 'created',
|
|
262
|
+
},
|
|
263
|
+
};
|
|
264
|
+
}
|
|
265
|
+
catch (error) {
|
|
266
|
+
return { success: false, error: error.message };
|
|
267
|
+
}
|
|
268
|
+
},
|
|
269
|
+
};
|
|
270
|
+
const dailyNoteTool = {
|
|
271
|
+
definition: {
|
|
272
|
+
name: 'obsidian_daily',
|
|
273
|
+
description: 'Get or create today\'s daily note.',
|
|
274
|
+
category: 'productivity',
|
|
275
|
+
parameters: [
|
|
276
|
+
{
|
|
277
|
+
name: 'date',
|
|
278
|
+
type: 'string',
|
|
279
|
+
description: 'Date (YYYY-MM-DD), defaults to today',
|
|
280
|
+
required: false,
|
|
281
|
+
},
|
|
282
|
+
{
|
|
283
|
+
name: 'append',
|
|
284
|
+
type: 'string',
|
|
285
|
+
description: 'Content to append to daily note',
|
|
286
|
+
required: false,
|
|
287
|
+
},
|
|
288
|
+
],
|
|
289
|
+
returns: 'Daily note content',
|
|
290
|
+
},
|
|
291
|
+
async execute(params, context) {
|
|
292
|
+
const { date, append } = params;
|
|
293
|
+
if (!vaultPath) {
|
|
294
|
+
return { success: false, error: 'Obsidian vault not configured' };
|
|
295
|
+
}
|
|
296
|
+
try {
|
|
297
|
+
const targetDate = date ? new Date(date) : new Date();
|
|
298
|
+
const dateStr = targetDate.toISOString().split('T')[0];
|
|
299
|
+
const notePath = `Daily Notes/${dateStr}.md`;
|
|
300
|
+
const fullPath = path.join(vaultPath, notePath);
|
|
301
|
+
// Ensure directory exists
|
|
302
|
+
await fs.mkdir(path.dirname(fullPath), { recursive: true });
|
|
303
|
+
let content;
|
|
304
|
+
try {
|
|
305
|
+
content = await fs.readFile(fullPath, 'utf-8');
|
|
306
|
+
}
|
|
307
|
+
catch {
|
|
308
|
+
// Create new daily note
|
|
309
|
+
content = `# ${targetDate.toLocaleDateString('en-US', {
|
|
310
|
+
weekday: 'long',
|
|
311
|
+
year: 'numeric',
|
|
312
|
+
month: 'long',
|
|
313
|
+
day: 'numeric'
|
|
314
|
+
})}\n\n`;
|
|
315
|
+
await fs.writeFile(fullPath, content);
|
|
316
|
+
}
|
|
317
|
+
if (append) {
|
|
318
|
+
content += '\n' + append;
|
|
319
|
+
await fs.writeFile(fullPath, content);
|
|
320
|
+
}
|
|
321
|
+
return {
|
|
322
|
+
success: true,
|
|
323
|
+
data: {
|
|
324
|
+
date: dateStr,
|
|
325
|
+
path: notePath,
|
|
326
|
+
content,
|
|
327
|
+
},
|
|
328
|
+
};
|
|
329
|
+
}
|
|
330
|
+
catch (error) {
|
|
331
|
+
return { success: false, error: error.message };
|
|
332
|
+
}
|
|
333
|
+
},
|
|
334
|
+
};
|
|
335
|
+
const listNotesTool = {
|
|
336
|
+
definition: {
|
|
337
|
+
name: 'obsidian_list',
|
|
338
|
+
description: 'List notes in Obsidian vault.',
|
|
339
|
+
category: 'productivity',
|
|
340
|
+
parameters: [
|
|
341
|
+
{
|
|
342
|
+
name: 'folder',
|
|
343
|
+
type: 'string',
|
|
344
|
+
description: 'Folder to list (default: root)',
|
|
345
|
+
required: false,
|
|
346
|
+
default: '',
|
|
347
|
+
},
|
|
348
|
+
{
|
|
349
|
+
name: 'recursive',
|
|
350
|
+
type: 'boolean',
|
|
351
|
+
description: 'List recursively',
|
|
352
|
+
required: false,
|
|
353
|
+
default: false,
|
|
354
|
+
},
|
|
355
|
+
],
|
|
356
|
+
returns: 'List of notes',
|
|
357
|
+
},
|
|
358
|
+
async execute(params, context) {
|
|
359
|
+
const { folder = '', recursive = false } = params;
|
|
360
|
+
if (!vaultPath) {
|
|
361
|
+
return { success: false, error: 'Obsidian vault not configured' };
|
|
362
|
+
}
|
|
363
|
+
try {
|
|
364
|
+
const targetPath = path.join(vaultPath, folder);
|
|
365
|
+
const notes = [];
|
|
366
|
+
async function listDir(dir) {
|
|
367
|
+
const entries = await fs.readdir(dir, { withFileTypes: true });
|
|
368
|
+
for (const entry of entries) {
|
|
369
|
+
if (entry.name.startsWith('.'))
|
|
370
|
+
continue;
|
|
371
|
+
const fullPath = path.join(dir, entry.name);
|
|
372
|
+
const relativePath = path.relative(vaultPath, fullPath);
|
|
373
|
+
if (entry.isDirectory()) {
|
|
374
|
+
if (recursive) {
|
|
375
|
+
await listDir(fullPath);
|
|
376
|
+
}
|
|
377
|
+
else {
|
|
378
|
+
notes.push({
|
|
379
|
+
name: entry.name,
|
|
380
|
+
path: relativePath,
|
|
381
|
+
isFolder: true,
|
|
382
|
+
});
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
else if (entry.name.endsWith('.md')) {
|
|
386
|
+
const stat = await fs.stat(fullPath);
|
|
387
|
+
notes.push({
|
|
388
|
+
name: entry.name.replace('.md', ''),
|
|
389
|
+
path: relativePath,
|
|
390
|
+
isFolder: false,
|
|
391
|
+
modified: stat.mtime,
|
|
392
|
+
size: stat.size,
|
|
393
|
+
});
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
await listDir(targetPath);
|
|
398
|
+
return {
|
|
399
|
+
success: true,
|
|
400
|
+
data: {
|
|
401
|
+
folder,
|
|
402
|
+
notes,
|
|
403
|
+
count: notes.length,
|
|
404
|
+
},
|
|
405
|
+
};
|
|
406
|
+
}
|
|
407
|
+
catch (error) {
|
|
408
|
+
return { success: false, error: error.message };
|
|
409
|
+
}
|
|
410
|
+
},
|
|
411
|
+
};
|
|
412
|
+
const manifest = {
|
|
413
|
+
name: 'obsidian',
|
|
414
|
+
version: '1.0.0',
|
|
415
|
+
description: 'Integration with Obsidian note-taking app',
|
|
416
|
+
category: 'productivity',
|
|
417
|
+
icon: 'notes',
|
|
418
|
+
tools: ['obsidian_search', 'obsidian_read', 'obsidian_write', 'obsidian_daily', 'obsidian_list'],
|
|
419
|
+
config: [
|
|
420
|
+
{
|
|
421
|
+
name: 'vaultPath',
|
|
422
|
+
type: 'string',
|
|
423
|
+
description: 'Path to your Obsidian vault folder',
|
|
424
|
+
required: true,
|
|
425
|
+
},
|
|
426
|
+
],
|
|
427
|
+
};
|
|
428
|
+
const obsidianSkill = {
|
|
429
|
+
manifest,
|
|
430
|
+
tools: [searchNotesTool, readNoteTool, writeNoteTool, dailyNoteTool, listNotesTool],
|
|
431
|
+
async initialize(config) {
|
|
432
|
+
vaultPath = config.vaultPath || '';
|
|
433
|
+
console.log(`[Obsidian] Initialized with vault: ${vaultPath}`);
|
|
434
|
+
},
|
|
435
|
+
async shutdown() {
|
|
436
|
+
vaultPath = '';
|
|
437
|
+
console.log('[Obsidian] Shutdown');
|
|
438
|
+
},
|
|
439
|
+
};
|
|
440
|
+
exports.default = obsidianSkill;
|