@sulala/agent 0.1.1 → 0.1.3
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/README.md +18 -3
- package/bin/sulala.mjs +1 -1
- package/context/00-rules.md +1 -0
- package/context/apple-notes.md +99 -0
- package/context/bluesky.md +111 -0
- package/context/files.md +30 -0
- package/context/git.md +37 -0
- package/context/news.md +64 -0
- package/context/weather.md +32 -0
- package/dist/cli.js +29 -0
- package/dist/cli.js.map +1 -1
- package/dist/config.d.ts +2 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +4 -0
- package/dist/config.js.map +1 -1
- package/dist/gateway/server.d.ts.map +1 -1
- package/dist/gateway/server.js +97 -1
- package/dist/gateway/server.js.map +1 -1
- package/dist/onboard-env.d.ts +7 -0
- package/dist/onboard-env.d.ts.map +1 -0
- package/dist/onboard-env.js +99 -0
- package/dist/onboard-env.js.map +1 -0
- package/dist/onboard.d.ts +21 -0
- package/dist/onboard.d.ts.map +1 -0
- package/dist/onboard.js +266 -0
- package/dist/onboard.js.map +1 -0
- package/package.json +18 -23
- package/registry/apple-notes.md +99 -0
- package/registry/bluesky.md +111 -0
- package/registry/files.md +30 -0
- package/registry/git.md +37 -0
- package/registry/news.md +64 -0
- package/registry/skills-registry.json +40 -0
- package/registry/weather.md +32 -0
package/dist/onboard.js
ADDED
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Onboard: create ~/.sulala, default .env, and optional install daemon (launchd / systemd).
|
|
3
|
+
*/
|
|
4
|
+
import { mkdirSync, existsSync, writeFileSync, unlinkSync } from 'fs';
|
|
5
|
+
import { join, dirname } from 'path';
|
|
6
|
+
import { homedir, platform } from 'os';
|
|
7
|
+
import { fileURLToPath } from 'url';
|
|
8
|
+
import { execSync } from 'child_process';
|
|
9
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
10
|
+
/** Package root (where dist/ and bin/ live). When running from dist/onboard.js this is one level up. */
|
|
11
|
+
export function getPackageRoot() {
|
|
12
|
+
return join(__dirname, '..');
|
|
13
|
+
}
|
|
14
|
+
export function getSulalaHome() {
|
|
15
|
+
return join(homedir(), '.sulala');
|
|
16
|
+
}
|
|
17
|
+
const DEFAULT_ENV = `# Sulala Agent — created by sulala onboard
|
|
18
|
+
# Edit and add API keys as needed. See: https://github.com/schedra/sulala
|
|
19
|
+
|
|
20
|
+
PORT=3000
|
|
21
|
+
HOST=127.0.0.1
|
|
22
|
+
DB_PATH=./data/sulala.db
|
|
23
|
+
|
|
24
|
+
# AI (uncomment and set at least one)
|
|
25
|
+
# OPENAI_API_KEY=
|
|
26
|
+
# OPENROUTER_API_KEY=
|
|
27
|
+
# ANTHROPIC_API_KEY=
|
|
28
|
+
# GOOGLE_GEMINI_API_KEY=
|
|
29
|
+
# OLLAMA_BASE_URL=http://localhost:11434
|
|
30
|
+
`;
|
|
31
|
+
export function runOnboard() {
|
|
32
|
+
const sulalaHome = getSulalaHome();
|
|
33
|
+
const created = [];
|
|
34
|
+
if (!existsSync(sulalaHome)) {
|
|
35
|
+
mkdirSync(sulalaHome, { recursive: true });
|
|
36
|
+
created.push(sulalaHome);
|
|
37
|
+
}
|
|
38
|
+
const dataDir = join(sulalaHome, 'data');
|
|
39
|
+
if (!existsSync(dataDir)) {
|
|
40
|
+
mkdirSync(dataDir, { recursive: true });
|
|
41
|
+
created.push(dataDir);
|
|
42
|
+
}
|
|
43
|
+
const envPath = join(sulalaHome, '.env');
|
|
44
|
+
if (!existsSync(envPath)) {
|
|
45
|
+
writeFileSync(envPath, DEFAULT_ENV, 'utf8');
|
|
46
|
+
created.push(envPath);
|
|
47
|
+
}
|
|
48
|
+
return { sulalaHome, created };
|
|
49
|
+
}
|
|
50
|
+
function getNodePath() {
|
|
51
|
+
try {
|
|
52
|
+
return execSync('which node', { encoding: 'utf8' }).trim();
|
|
53
|
+
}
|
|
54
|
+
catch {
|
|
55
|
+
return '/usr/bin/env node';
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
/** Open URL in the system default browser (macOS, Linux, Windows). */
|
|
59
|
+
export function openBrowser(url) {
|
|
60
|
+
const plat = platform();
|
|
61
|
+
const cmd = plat === 'darwin'
|
|
62
|
+
? 'open'
|
|
63
|
+
: plat === 'win32'
|
|
64
|
+
? 'start'
|
|
65
|
+
: 'xdg-open';
|
|
66
|
+
try {
|
|
67
|
+
execSync(`${cmd} "${url}"`, { stdio: 'ignore' });
|
|
68
|
+
}
|
|
69
|
+
catch {
|
|
70
|
+
console.log(`Open in browser: ${url}`);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
/** Open the onboard page (optionally after a delay, e.g. after daemon start). */
|
|
74
|
+
export async function openOnboardPage(opts) {
|
|
75
|
+
const port = opts.port ?? 3000;
|
|
76
|
+
const url = `http://127.0.0.1:${port}/onboard`;
|
|
77
|
+
if (opts.delayMs && opts.delayMs > 0) {
|
|
78
|
+
const plat = platform();
|
|
79
|
+
const openCmd = plat === 'darwin' ? 'open' : plat === 'win32' ? 'start' : 'xdg-open';
|
|
80
|
+
const sec = Math.max(0.5, opts.delayMs / 1000);
|
|
81
|
+
try {
|
|
82
|
+
const { spawn } = await import('child_process');
|
|
83
|
+
const child = spawn(plat === 'win32' ? 'cmd' : 'sh', plat === 'win32' ? ['/c', `timeout /t ${Math.ceil(sec)} /nobreak >nul && start ${url}`] : ['-c', `sleep ${sec}; ${openCmd} "${url}"`], { detached: true, stdio: 'ignore' });
|
|
84
|
+
child.unref();
|
|
85
|
+
}
|
|
86
|
+
catch {
|
|
87
|
+
openBrowser(url);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
else {
|
|
91
|
+
openBrowser(url);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
/** macOS: install launchd user agent so the agent runs at login and stays running. */
|
|
95
|
+
function installDaemonDarwin(sulalaHome, packageRoot) {
|
|
96
|
+
const nodePath = getNodePath();
|
|
97
|
+
const entryPath = join(packageRoot, 'dist', 'index.js');
|
|
98
|
+
const plistPath = join(homedir(), 'Library', 'LaunchAgents', 'com.sulala.agent.plist');
|
|
99
|
+
const logDir = join(sulalaHome, 'logs');
|
|
100
|
+
if (!existsSync(logDir))
|
|
101
|
+
mkdirSync(logDir, { recursive: true });
|
|
102
|
+
const plist = `<?xml version="1.0" encoding="UTF-8"?>
|
|
103
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
104
|
+
<plist version="1.0">
|
|
105
|
+
<dict>
|
|
106
|
+
<key>Label</key>
|
|
107
|
+
<string>com.sulala.agent</string>
|
|
108
|
+
<key>ProgramArguments</key>
|
|
109
|
+
<array>
|
|
110
|
+
<string>${nodePath}</string>
|
|
111
|
+
<string>${entryPath}</string>
|
|
112
|
+
</array>
|
|
113
|
+
<key>WorkingDirectory</key>
|
|
114
|
+
<string>${sulalaHome}</string>
|
|
115
|
+
<key>RunAtLoad</key>
|
|
116
|
+
<true/>
|
|
117
|
+
<key>KeepAlive</key>
|
|
118
|
+
<true/>
|
|
119
|
+
<key>StandardOutPath</key>
|
|
120
|
+
<string>${join(logDir, 'stdout.log')}</string>
|
|
121
|
+
<key>StandardErrorPath</key>
|
|
122
|
+
<string>${join(logDir, 'stderr.log')}</string>
|
|
123
|
+
</dict>
|
|
124
|
+
</plist>
|
|
125
|
+
`;
|
|
126
|
+
mkdirSync(dirname(plistPath), { recursive: true });
|
|
127
|
+
writeFileSync(plistPath, plist, 'utf8');
|
|
128
|
+
execSync(`launchctl unload "${plistPath}" 2>/dev/null || true`, { stdio: 'ignore' });
|
|
129
|
+
execSync(`launchctl load "${plistPath}"`, { stdio: 'inherit' });
|
|
130
|
+
}
|
|
131
|
+
/** Linux: install systemd user service. */
|
|
132
|
+
function installDaemonLinux(sulalaHome, packageRoot) {
|
|
133
|
+
const entryPath = join(packageRoot, 'dist', 'index.js');
|
|
134
|
+
const nodePath = getNodePath();
|
|
135
|
+
const unitDir = join(homedir(), '.config', 'systemd', 'user');
|
|
136
|
+
const unitPath = join(unitDir, 'sulala-agent.service');
|
|
137
|
+
const logDir = join(sulalaHome, 'logs');
|
|
138
|
+
if (!existsSync(logDir))
|
|
139
|
+
mkdirSync(logDir, { recursive: true });
|
|
140
|
+
const unit = `[Unit]
|
|
141
|
+
Description=Sulala Agent
|
|
142
|
+
After=network.target
|
|
143
|
+
|
|
144
|
+
[Service]
|
|
145
|
+
Type=simple
|
|
146
|
+
WorkingDirectory=${sulalaHome}
|
|
147
|
+
ExecStart=${nodePath} ${entryPath}
|
|
148
|
+
Restart=on-failure
|
|
149
|
+
RestartSec=5
|
|
150
|
+
StandardOutput=append:${join(logDir, 'stdout.log')}
|
|
151
|
+
StandardError=append:${join(logDir, 'stderr.log')}
|
|
152
|
+
|
|
153
|
+
[Install]
|
|
154
|
+
WantedBy=default.target
|
|
155
|
+
`;
|
|
156
|
+
mkdirSync(unitDir, { recursive: true });
|
|
157
|
+
writeFileSync(unitPath, unit, 'utf8');
|
|
158
|
+
execSync('systemctl --user daemon-reload', { stdio: 'inherit' });
|
|
159
|
+
execSync('systemctl --user enable --now sulala-agent.service', { stdio: 'inherit' });
|
|
160
|
+
}
|
|
161
|
+
export function installDaemon() {
|
|
162
|
+
runOnboard(); // ensure ~/.sulala and .env exist
|
|
163
|
+
const sulalaHome = getSulalaHome();
|
|
164
|
+
const packageRoot = getPackageRoot();
|
|
165
|
+
const entryPath = join(packageRoot, 'dist', 'index.js');
|
|
166
|
+
if (!existsSync(entryPath)) {
|
|
167
|
+
throw new Error(`Agent entry not found: ${entryPath}. Run from an installed @sulala/agent (e.g. npm i -g @sulala/agent) or build with pnpm build.`);
|
|
168
|
+
}
|
|
169
|
+
const plat = platform();
|
|
170
|
+
if (plat === 'darwin') {
|
|
171
|
+
installDaemonDarwin(sulalaHome, packageRoot);
|
|
172
|
+
console.log('Daemon installed. Agent will run at login. Logs: ~/.sulala/logs/');
|
|
173
|
+
}
|
|
174
|
+
else if (plat === 'linux') {
|
|
175
|
+
installDaemonLinux(sulalaHome, packageRoot);
|
|
176
|
+
console.log('Daemon installed. Agent is running. Logs: ~/.sulala/logs/');
|
|
177
|
+
}
|
|
178
|
+
else {
|
|
179
|
+
throw new Error(`Daemon install is not supported on ${plat}. Use macOS or Linux.`);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
/** Stop the agent daemon (does not remove it; use onboard --uninstall-daemon to remove). */
|
|
183
|
+
export function stopDaemon() {
|
|
184
|
+
const plat = platform();
|
|
185
|
+
if (plat === 'darwin') {
|
|
186
|
+
const plistPath = join(homedir(), 'Library', 'LaunchAgents', 'com.sulala.agent.plist');
|
|
187
|
+
if (existsSync(plistPath)) {
|
|
188
|
+
execSync(`launchctl unload "${plistPath}"`, { stdio: 'inherit' });
|
|
189
|
+
console.log('Sulala agent stopped.');
|
|
190
|
+
}
|
|
191
|
+
else {
|
|
192
|
+
console.log('No daemon found. Agent may not be installed (sulala onboard --install-daemon).');
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
else if (plat === 'linux') {
|
|
196
|
+
const unitPath = join(homedir(), '.config', 'systemd', 'user', 'sulala-agent.service');
|
|
197
|
+
if (existsSync(unitPath)) {
|
|
198
|
+
execSync('systemctl --user stop sulala-agent.service', { stdio: 'inherit' });
|
|
199
|
+
console.log('Sulala agent stopped.');
|
|
200
|
+
}
|
|
201
|
+
else {
|
|
202
|
+
console.log('No daemon found. Agent may not be installed (sulala onboard --install-daemon).');
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
else {
|
|
206
|
+
throw new Error(`Stop is not supported on ${plat}. Use macOS or Linux.`);
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
/** Start the agent daemon (must be installed first with sulala onboard --install-daemon). */
|
|
210
|
+
export function startDaemon() {
|
|
211
|
+
const plat = platform();
|
|
212
|
+
if (plat === 'darwin') {
|
|
213
|
+
const plistPath = join(homedir(), 'Library', 'LaunchAgents', 'com.sulala.agent.plist');
|
|
214
|
+
if (existsSync(plistPath)) {
|
|
215
|
+
execSync(`launchctl load "${plistPath}"`, { stdio: 'inherit' });
|
|
216
|
+
console.log('Sulala agent started.');
|
|
217
|
+
}
|
|
218
|
+
else {
|
|
219
|
+
console.log('No daemon found. Run: sulala onboard --install-daemon');
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
else if (plat === 'linux') {
|
|
223
|
+
const unitPath = join(homedir(), '.config', 'systemd', 'user', 'sulala-agent.service');
|
|
224
|
+
if (existsSync(unitPath)) {
|
|
225
|
+
execSync('systemctl --user start sulala-agent.service', { stdio: 'inherit' });
|
|
226
|
+
console.log('Sulala agent started.');
|
|
227
|
+
}
|
|
228
|
+
else {
|
|
229
|
+
console.log('No daemon found. Run: sulala onboard --install-daemon');
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
else {
|
|
233
|
+
throw new Error(`Start is not supported on ${plat}. Use macOS or Linux.`);
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
export function uninstallDaemon() {
|
|
237
|
+
const plat = platform();
|
|
238
|
+
if (plat === 'darwin') {
|
|
239
|
+
const plistPath = join(homedir(), 'Library', 'LaunchAgents', 'com.sulala.agent.plist');
|
|
240
|
+
if (existsSync(plistPath)) {
|
|
241
|
+
execSync(`launchctl unload "${plistPath}"`, { stdio: 'inherit' });
|
|
242
|
+
unlinkSync(plistPath);
|
|
243
|
+
console.log('Daemon uninstalled.');
|
|
244
|
+
}
|
|
245
|
+
else {
|
|
246
|
+
console.log('No daemon plist found.');
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
else if (plat === 'linux') {
|
|
250
|
+
const unitPath = join(homedir(), '.config', 'systemd', 'user', 'sulala-agent.service');
|
|
251
|
+
if (existsSync(unitPath)) {
|
|
252
|
+
execSync('systemctl --user stop sulala-agent.service', { stdio: 'ignore' });
|
|
253
|
+
execSync('systemctl --user disable sulala-agent.service', { stdio: 'ignore' });
|
|
254
|
+
unlinkSync(unitPath);
|
|
255
|
+
execSync('systemctl --user daemon-reload', { stdio: 'ignore' });
|
|
256
|
+
console.log('Daemon uninstalled.');
|
|
257
|
+
}
|
|
258
|
+
else {
|
|
259
|
+
console.log('No systemd unit found.');
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
else {
|
|
263
|
+
throw new Error(`Daemon uninstall is not supported on ${plat}.`);
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
//# sourceMappingURL=onboard.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"onboard.js","sourceRoot":"","sources":["../src/onboard.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,9 +1,22 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sulala/agent",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.3",
|
|
4
4
|
"description": "Local AI orchestration platform — file watcher, task scheduler, AI layer, plugins",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "src/index.ts",
|
|
7
|
+
"scripts": {
|
|
8
|
+
"start": "tsx src/index.ts",
|
|
9
|
+
"gateway": "tsx src/gateway/server.ts",
|
|
10
|
+
"dashboard": "cd dashboard && npm run dev",
|
|
11
|
+
"dashboard:build": "cd dashboard && npm run build",
|
|
12
|
+
"cli": "tsx src/cli.ts",
|
|
13
|
+
"dev": "tsx watch src/index.ts",
|
|
14
|
+
"build": "tsc && cp src/db/schema.sql dist/db/",
|
|
15
|
+
"test": "vitest run",
|
|
16
|
+
"test:watch": "vitest",
|
|
17
|
+
"lint": "eslint src",
|
|
18
|
+
"release:check": "npm pack --dry-run"
|
|
19
|
+
},
|
|
7
20
|
"engines": {
|
|
8
21
|
"node": ">=18"
|
|
9
22
|
},
|
|
@@ -15,15 +28,9 @@
|
|
|
15
28
|
"local"
|
|
16
29
|
],
|
|
17
30
|
"license": "MIT",
|
|
18
|
-
"files": [
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
"src/db/schema.sql"
|
|
22
|
-
],
|
|
23
|
-
"prepublishOnly": "pnpm run build",
|
|
24
|
-
"publishConfig": {
|
|
25
|
-
"access": "public"
|
|
26
|
-
},
|
|
31
|
+
"files": ["dist", "bin", "src/db/schema.sql", "registry", "context", "dashboard/dist"],
|
|
32
|
+
"prepublishOnly": "pnpm run build && pnpm run dashboard:build",
|
|
33
|
+
"publishConfig": { "access": "public" },
|
|
27
34
|
"bin": {
|
|
28
35
|
"sulala": "bin/sulala.mjs"
|
|
29
36
|
},
|
|
@@ -52,17 +59,5 @@
|
|
|
52
59
|
"tsx": "^4.21.0",
|
|
53
60
|
"typescript": "^5.9.3",
|
|
54
61
|
"vitest": "^4.0.18"
|
|
55
|
-
},
|
|
56
|
-
"scripts": {
|
|
57
|
-
"start": "tsx src/index.ts",
|
|
58
|
-
"gateway": "tsx src/gateway/server.ts",
|
|
59
|
-
"dashboard": "cd dashboard && npm run dev",
|
|
60
|
-
"dashboard:build": "cd dashboard && npm run build",
|
|
61
|
-
"cli": "tsx src/cli.ts",
|
|
62
|
-
"dev": "tsx watch src/index.ts",
|
|
63
|
-
"build": "tsc && cp src/db/schema.sql dist/db/",
|
|
64
|
-
"test": "vitest run",
|
|
65
|
-
"test:watch": "vitest",
|
|
66
|
-
"lint": "eslint src"
|
|
67
62
|
}
|
|
68
|
-
}
|
|
63
|
+
}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: apple-notes
|
|
3
|
+
description: Manage Apple Notes via the `memo` CLI on macOS. Use when the user asks to add a note, list notes, search notes, or manage note folders.
|
|
4
|
+
homepage: https://github.com/antoniorodr/memo
|
|
5
|
+
metadata:
|
|
6
|
+
{
|
|
7
|
+
"sulala":
|
|
8
|
+
{
|
|
9
|
+
"emoji": "📝",
|
|
10
|
+
"os": ["darwin"],
|
|
11
|
+
"requires": { "bins": ["memo"] },
|
|
12
|
+
"install":
|
|
13
|
+
[
|
|
14
|
+
{
|
|
15
|
+
"id": "brew",
|
|
16
|
+
"kind": "brew",
|
|
17
|
+
"formula": "antoniorodr/memo/memo",
|
|
18
|
+
"bins": ["memo"],
|
|
19
|
+
"label": "Install memo via Homebrew",
|
|
20
|
+
},
|
|
21
|
+
],
|
|
22
|
+
},
|
|
23
|
+
}
|
|
24
|
+
---
|
|
25
|
+
|
|
26
|
+
# Apple Notes CLI
|
|
27
|
+
|
|
28
|
+
Use `memo notes` to manage Apple Notes directly from the terminal. Create, view, edit, delete, search, move notes between folders, and export to HTML/Markdown.
|
|
29
|
+
|
|
30
|
+
Setup
|
|
31
|
+
|
|
32
|
+
- Install (Homebrew): `brew tap antoniorodr/memo && brew install antoniorodr/memo/memo`
|
|
33
|
+
- Manual (pip): `pip install .` (after cloning the repo)
|
|
34
|
+
- macOS-only; if prompted, grant Automation access to Notes.app.
|
|
35
|
+
|
|
36
|
+
View Notes
|
|
37
|
+
|
|
38
|
+
- List all notes: `memo notes`
|
|
39
|
+
- Filter by folder: `memo notes -f "Folder Name"`
|
|
40
|
+
- Search notes (fuzzy): `memo notes -s "query"`
|
|
41
|
+
|
|
42
|
+
Create Notes
|
|
43
|
+
|
|
44
|
+
- **Add a note (prefer AppleScript):** For "add a note titled X", use **run_command** with `binary: "osascript"` and the AppleScript in the section below. Do not use memo for non-interactive add.
|
|
45
|
+
- Add via memo (interactive only): `memo notes -a -f "FolderName"` — **requires** `-f` (folder). Opens the user's editor; memo does **not** accept title or body as arguments. Use only as fallback if osascript is unavailable.
|
|
46
|
+
|
|
47
|
+
Edit Notes
|
|
48
|
+
|
|
49
|
+
- Edit existing note: `memo notes -e`
|
|
50
|
+
- Interactive selection of note to edit.
|
|
51
|
+
|
|
52
|
+
Delete Notes
|
|
53
|
+
|
|
54
|
+
- Delete a note: `memo notes -d`
|
|
55
|
+
- Interactive selection of note to delete.
|
|
56
|
+
|
|
57
|
+
Move Notes
|
|
58
|
+
|
|
59
|
+
- Move note to folder: `memo notes -m`
|
|
60
|
+
- Interactive selection of note and destination folder.
|
|
61
|
+
|
|
62
|
+
Export Notes
|
|
63
|
+
|
|
64
|
+
- Export to HTML/Markdown: `memo notes -ex`
|
|
65
|
+
- Exports selected note; uses Mistune for markdown processing.
|
|
66
|
+
|
|
67
|
+
Limitations
|
|
68
|
+
|
|
69
|
+
- Cannot edit notes containing images or attachments.
|
|
70
|
+
- Interactive prompts may require terminal access.
|
|
71
|
+
|
|
72
|
+
Notes
|
|
73
|
+
|
|
74
|
+
- macOS-only.
|
|
75
|
+
- Requires Apple Notes.app to be accessible.
|
|
76
|
+
- For automation, grant permissions in System Settings > Privacy & Security > Automation.
|
|
77
|
+
|
|
78
|
+
---
|
|
79
|
+
|
|
80
|
+
## When the user asks you to add a note
|
|
81
|
+
|
|
82
|
+
Your goal is to get the note into Apple Notes. Use **run_command** only (no skill-specific tools). Add osascript and memo to ALLOWED_BINARIES.
|
|
83
|
+
|
|
84
|
+
**Prefer AppleScript for "add a note titled X".** Use the osascript one-liner below so the agent creates the note in one shot instead of trying memo (which is interactive and does not accept title as an argument).
|
|
85
|
+
|
|
86
|
+
1. **Adding a note directly (macOS)**
|
|
87
|
+
- Use **run_command** with `binary: "osascript"` and one `-e` argument containing this AppleScript. Replace TITLE and BODY with the user's note title and optional body; escape any double-quote in TITLE or BODY as backslash-quote (`\"`).
|
|
88
|
+
- Script (use `\n` for newlines in the string you pass):
|
|
89
|
+
`tell application "Notes"\ntell account "iCloud"\ntell folder "Notes"\nmake new note with properties {name:"TITLE", body:"BODY"}\nend tell\nend tell\nend tell`
|
|
90
|
+
- Example for "add apple note called buy saisai": args = `["-e", "tell application \"Notes\"\ntell account \"iCloud\"\ntell folder \"Notes\"\nmake new note with properties {name:\"buy saisai\", body:\"\"}\nend tell\nend tell\nend tell"]`
|
|
91
|
+
- On success, confirm: "Done — I added an Apple Note titled \"…\"."
|
|
92
|
+
- If osascript is not in ALLOWED_BINARIES or the command errors: give the user the note text and steps to run `memo notes -a -f "Notes"` in the terminal and paste the content when the editor opens.
|
|
93
|
+
|
|
94
|
+
2. **List and search**
|
|
95
|
+
- **run_command** with `binary: "memo"`, `args: ["notes"]` for list; `args: ["notes", "-s", "query"]` for search.
|
|
96
|
+
|
|
97
|
+
3. **Do not**
|
|
98
|
+
- Do not use run_command with memo `["notes", "-a", "title"]`; memo rejects extra arguments.
|
|
99
|
+
- Do not leave the user without a path to success: either add via osascript (run_command) or give text + steps for memo notes -a, or explain why (e.g. add osascript to ALLOWED_BINARIES).
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: bluesky
|
|
3
|
+
description: Post to Bluesky (AT Protocol). Use when the user asks to post a tweet/thread to Bluesky, share content on Bluesky, or post news/headlines from a source (URL or text) to Bluesky.
|
|
4
|
+
homepage: https://bsky.app
|
|
5
|
+
metadata:
|
|
6
|
+
{
|
|
7
|
+
"sulala": {
|
|
8
|
+
"emoji": "🦋",
|
|
9
|
+
"requires": { "bins": ["curl", "python3", "sh"], "env": ["BSKY_HANDLE", "BSKY_APP_PASSWORD"] },
|
|
10
|
+
"primaryEnv": "BSKY_APP_PASSWORD"
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
# Bluesky Posting
|
|
16
|
+
|
|
17
|
+
Post to Bluesky via the AT Protocol. Use **run_command** with `curl`, `python3`, and `sh`. Add `curl`, `python3`, and `sh` to ALLOWED_BINARIES.
|
|
18
|
+
|
|
19
|
+
Requires `BSKY_HANDLE` (e.g. `your.bsky.social`) and `BSKY_APP_PASSWORD` (app password from bsky.app → Settings → App Passwords). Set them in `.env` or in the skill config (`~/.sulala/config.json` or `.sulala/config.json` in the project; see Skill config in the dashboard). Config is re-read when the file changes; enable/disable takes effect without restart.
|
|
20
|
+
|
|
21
|
+
**IMPORTANT:** `run_command` does not run a shell — `$BSKY_HANDLE` and `$BSKY_APP_PASSWORD` will not expand if you call curl directly. Always use `binary: "sh"` and `args: ["-c", "curl ... $BSKY_HANDLE ... $BSKY_APP_PASSWORD ..."]` so the env vars expand.
|
|
22
|
+
|
|
23
|
+
## When to Use
|
|
24
|
+
|
|
25
|
+
- "Post this to Bluesky"
|
|
26
|
+
- "Share [content] on Bluesky"
|
|
27
|
+
- "Post news from [URL] to Bluesky"
|
|
28
|
+
- "Post a headline about [topic]"
|
|
29
|
+
|
|
30
|
+
## Post Flow (non-interactive)
|
|
31
|
+
|
|
32
|
+
### 1. Ensure credentials
|
|
33
|
+
|
|
34
|
+
```
|
|
35
|
+
BSKY_HANDLE="${BSKY_HANDLE:?Set BSKY_HANDLE}"
|
|
36
|
+
BSKY_APP_PASSWORD="${BSKY_APP_PASSWORD:?Set BSKY_APP_PASSWORD}"
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
If empty, read from Sulala config:
|
|
40
|
+
|
|
41
|
+
```
|
|
42
|
+
CONFIG_PATH="${SULALA_CONFIG_PATH:-$HOME/.sulala/config.json}"
|
|
43
|
+
# If using workspace config, use: CONFIG_PATH=".sulala/config.json" (when run from project root)
|
|
44
|
+
BSKY_HANDLE=$(cat "$CONFIG_PATH" 2>/dev/null | python3 -c "import sys,json; d=json.load(sys.stdin); e=d.get('skills',{}).get('entries',{}).get('bluesky',{}); print(e.get('handle','') or e.get('apiKey',''))" 2>/dev/null)
|
|
45
|
+
BSKY_APP_PASSWORD=$(cat "$CONFIG_PATH" 2>/dev/null | python3 -c "import sys,json; d=json.load(sys.stdin); e=d.get('skills',{}).get('entries',{}).get('bluesky',{}); print(e.get('apiKey','') or e.get('password',''))" 2>/dev/null)
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
(Config may use `handle` + `apiKey` for Bluesky. Adjust field names if your config differs.)
|
|
49
|
+
|
|
50
|
+
### 2. Create session (login)
|
|
51
|
+
|
|
52
|
+
**Use `run_command` with `binary: "sh"` and `args: ["-c", "..."]`** so `$BSKY_HANDLE` and `$BSKY_APP_PASSWORD` expand from the environment. Do NOT call curl directly.
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
sh -c 'SESSION=$(curl -s -X POST "https://bsky.social/xrpc/com.atproto.server.createSession" -H "Content-Type: application/json" -d "{\"identifier\": \"$BSKY_HANDLE\", \"password\": \"$BSKY_APP_PASSWORD\"}"); echo "$SESSION"'
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
Then parse the JSON output with `python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('accessJwt',''), d.get('did',''))"` to get `ACCESS_TOKEN` and `DID`.
|
|
59
|
+
|
|
60
|
+
If the session response contains `"error"` or `"AuthenticationRequired"`, report: "Bluesky login failed. Check BSKY_HANDLE and BSKY_APP_PASSWORD in .env."
|
|
61
|
+
|
|
62
|
+
### 3. Create post
|
|
63
|
+
|
|
64
|
+
Text must be JSON-escaped. Use python to build the payload. **Use `run_command` with `binary: "sh"`** so `$ACCESS_TOKEN`, `$DID`, and other vars expand.
|
|
65
|
+
|
|
66
|
+
Run a single `sh -c` that chains steps 2 and 3 so SESSION, ACCESS_TOKEN, DID persist in the same shell:
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
sh -c '
|
|
70
|
+
SESSION=$(curl -s -X POST "https://bsky.social/xrpc/com.atproto.server.createSession" \
|
|
71
|
+
-H "Content-Type: application/json" \
|
|
72
|
+
-d "{\"identifier\": \"$BSKY_HANDLE\", \"password\": \"$BSKY_APP_PASSWORD\"}");
|
|
73
|
+
ACCESS_TOKEN=$(echo "$SESSION" | python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get(\"accessJwt\",\"\"))");
|
|
74
|
+
DID=$(echo "$SESSION" | python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get(\"did\",\"\"))");
|
|
75
|
+
if [ -z "$ACCESS_TOKEN" ]; then echo "Login failed"; exit 1; fi;
|
|
76
|
+
POST_TEXT="Your post content here";
|
|
77
|
+
POST_JSON=$(python3 -c "import sys,json; print(json.dumps(sys.argv[1]))" "$POST_TEXT");
|
|
78
|
+
NOW=$(date -u +"%Y-%m-%dT%H:%M:%S.000Z");
|
|
79
|
+
curl -s -X POST "https://bsky.social/xrpc/com.atproto.repo.createRecord" \
|
|
80
|
+
-H "Authorization: Bearer $ACCESS_TOKEN" \
|
|
81
|
+
-H "Content-Type: application/json" \
|
|
82
|
+
-d "{\"repo\": \"$DID\", \"collection\": \"app.bsky.feed.post\", \"record\": {\"\\$type\": \"app.bsky.feed.post\", \"text\": $POST_JSON, \"createdAt\": \"$NOW\"}}"
|
|
83
|
+
'
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
Replace `Your post content here` with the user's post text.
|
|
87
|
+
|
|
88
|
+
If the response contains `"uri"`, the post succeeded. Otherwise report the error.
|
|
89
|
+
|
|
90
|
+
## Posting from a news source
|
|
91
|
+
|
|
92
|
+
When the user wants to post **news** or **content from a URL**:
|
|
93
|
+
|
|
94
|
+
1. **Fetch the content** (e.g. with `curl -s <URL>` or read a file).
|
|
95
|
+
2. **Summarize** if the content is long — Bluesky posts are max 300 characters. Write a short headline or summary.
|
|
96
|
+
3. **Post** using the flow above. Optionally append the source URL if it fits within 300 chars.
|
|
97
|
+
|
|
98
|
+
Example for "post news from https://example.com/article":
|
|
99
|
+
- Fetch the page, extract title/lead.
|
|
100
|
+
- Build post: "Headline here — https://example.com/article"
|
|
101
|
+
- Post via the createRecord flow above.
|
|
102
|
+
|
|
103
|
+
## Character limit
|
|
104
|
+
|
|
105
|
+
Bluesky posts are limited to 300 characters. If the user's text or your summary exceeds that, truncate or split into multiple posts (thread). For a thread, create each post separately; the API does not natively support threads — you would need to reference the previous post's URI if linking them (advanced).
|
|
106
|
+
|
|
107
|
+
## Notes
|
|
108
|
+
|
|
109
|
+
- Use an **app password**, not the main account password. Create at bsky.app → Settings → App Passwords.
|
|
110
|
+
- `bsky.social` is the default PDS; custom PDS users would need a different host.
|
|
111
|
+
- Do not embed real credentials in the skill or in replies — use env or config.
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: files
|
|
3
|
+
description: Basic file operations via run_command. Use when the user asks to list files, show file contents, search within files, or inspect directories.
|
|
4
|
+
metadata:
|
|
5
|
+
{
|
|
6
|
+
"sulala": {
|
|
7
|
+
"requires": { "bins": [] }
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
# File operations
|
|
13
|
+
|
|
14
|
+
Use **run_command** with common shell tools to read and inspect files. Add required binaries to ALLOWED_BINARIES (e.g. ls, cat, head, tail, wc, grep, find).
|
|
15
|
+
|
|
16
|
+
## List files
|
|
17
|
+
|
|
18
|
+
- `ls` — list directory contents
|
|
19
|
+
- `ls -la` — long format with hidden files
|
|
20
|
+
|
|
21
|
+
## Read files
|
|
22
|
+
|
|
23
|
+
- `cat path/to/file` — output entire file
|
|
24
|
+
- `head -n 20 path/to/file` — first 20 lines
|
|
25
|
+
- `tail -n 50 path/to/file` — last 50 lines
|
|
26
|
+
|
|
27
|
+
## Search
|
|
28
|
+
|
|
29
|
+
- `grep -r "pattern" path/` — search in files recursively
|
|
30
|
+
- `find path -name "*.md"` — find files by name
|
package/registry/git.md
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: git
|
|
3
|
+
description: Basic Git operations via run_command. Use when the user asks to check status, diff, log, branch, or perform simple git commands.
|
|
4
|
+
metadata:
|
|
5
|
+
{
|
|
6
|
+
"sulala": {
|
|
7
|
+
"requires": { "bins": ["git"] }
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
# Git operations
|
|
13
|
+
|
|
14
|
+
Use **run_command** with `binary: "git"` and appropriate args. Add `git` to ALLOWED_BINARIES.
|
|
15
|
+
|
|
16
|
+
## Status and diff
|
|
17
|
+
|
|
18
|
+
- `git status`
|
|
19
|
+
- `git diff`
|
|
20
|
+
- `git diff --staged`
|
|
21
|
+
- `git log -n 10 --oneline`
|
|
22
|
+
|
|
23
|
+
## Branches
|
|
24
|
+
|
|
25
|
+
- `git branch`
|
|
26
|
+
- `git branch -a`
|
|
27
|
+
- `git checkout -b new-branch`
|
|
28
|
+
|
|
29
|
+
## Info
|
|
30
|
+
|
|
31
|
+
- `git show HEAD:path/to/file` — show file at HEAD
|
|
32
|
+
- `git rev-parse --abbrev-ref HEAD` — current branch name
|
|
33
|
+
|
|
34
|
+
## Limits
|
|
35
|
+
|
|
36
|
+
- Do not run destructive commands (reset --hard, push --force) without explicit user confirmation.
|
|
37
|
+
- Prefer read-only commands when the user only asks to inspect.
|