compass-ai 1.0.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/bin/compass.js ADDED
@@ -0,0 +1,354 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ const { execSync, spawnSync } = require('child_process');
5
+ const readline = require('readline');
6
+ const fs = require('fs');
7
+ const path = require('path');
8
+ const os = require('os');
9
+
10
+ const PACKAGE_DIR = path.join(__dirname, '..');
11
+ const OPENCLAW_DIR = path.join(os.homedir(), '.openclaw');
12
+ const WORKSPACE_DIR = path.join(OPENCLAW_DIR, 'compass');
13
+ const STATE_DIR = path.join(WORKSPACE_DIR, 'state');
14
+ const BOOT_MD = path.join(WORKSPACE_DIR, 'boot.md');
15
+ const SKILLS_SRC = path.join(PACKAGE_DIR, 'skills');
16
+
17
+ const cmd = process.argv[2];
18
+
19
+ switch (cmd) {
20
+ case 'init': init().catch(err => { console.error('\n Error:', err.message); process.exit(1); }); break;
21
+ case 'start': start(); break;
22
+ case 'report': report(); break;
23
+ default: help(); break;
24
+ }
25
+
26
+ // ── helpers ──────────────────────────────────────────────────────────────────
27
+
28
+ function ask(rl, question, def) {
29
+ return new Promise(resolve => {
30
+ rl.question(def ? ` ${question} [${def}]: ` : ` ${question}: `, ans => {
31
+ resolve(ans.trim() || def || '');
32
+ });
33
+ });
34
+ }
35
+
36
+ function write(filePath, content) {
37
+ fs.mkdirSync(path.dirname(filePath), { recursive: true });
38
+ fs.writeFileSync(filePath, content, 'utf8');
39
+ }
40
+
41
+ function resolveOpenClaw() {
42
+ const candidates = [
43
+ path.join(PACKAGE_DIR, 'node_modules', '.bin', 'openclaw'),
44
+ path.join(os.homedir(), '.npm-global', 'bin', 'openclaw'),
45
+ ];
46
+ for (const c of candidates) {
47
+ if (fs.existsSync(c)) return c;
48
+ }
49
+ try { return execSync('which openclaw 2>/dev/null || command -v openclaw', { encoding: 'utf8' }).trim(); } catch {}
50
+ console.error('\n openclaw not found. It should have been installed as a dependency.\n Try: npm install -g openclaw\n');
51
+ process.exit(1);
52
+ }
53
+
54
+ function runOpenClaw(...args) {
55
+ const bin = resolveOpenClaw();
56
+ const result = spawnSync(bin, args, { stdio: 'inherit' });
57
+ if (result.status !== 0) process.exit(result.status || 1);
58
+ }
59
+
60
+ // ── init ─────────────────────────────────────────────────────────────────────
61
+
62
+ async function init() {
63
+ console.log('\n🧭 Compass — Setup\n');
64
+ console.log(' Answer each question. Press Enter to accept the default.\n');
65
+
66
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
67
+
68
+ const llmProvider = await ask(rl, 'LLM provider (anthropic / openai)', 'anthropic');
69
+ const anthropicKey = llmProvider === 'anthropic' ? await ask(rl, 'Anthropic API key') : '';
70
+ const openaiKey = llmProvider === 'openai' ? await ask(rl, 'OpenAI API key') : '';
71
+ const telegramToken = await ask(rl, 'Telegram bot token');
72
+ const telegramUserId = await ask(rl, 'Your Telegram user ID');
73
+ const slackToken = await ask(rl, 'Slack bot token');
74
+ const slackChannel = await ask(rl, 'Slack report channel', '#standup');
75
+ const dailyTime = await ask(rl, 'Daily standup time (HH:MM, 24h)', '09:00');
76
+ const weeklyDay = await ask(rl, 'Weekly review day (MON-SUN)', 'MON');
77
+ const weeklyTime = await ask(rl, 'Weekly review time (HH:MM, 24h)', '08:00');
78
+ const founderName = await ask(rl, 'Your name (for Compass memory)', 'Manas');
79
+
80
+ rl.close();
81
+
82
+ console.log('\n Writing config files...\n');
83
+
84
+ // ── OpenClaw root config ──────────────────────────────────────────────────
85
+
86
+ write(path.join(OPENCLAW_DIR, 'agent.json'), JSON.stringify({
87
+ name: 'Compass',
88
+ workspace: 'compass',
89
+ creature: 'chief of staff and execution memory for a small startup',
90
+ vibe: 'direct, concise, action-oriented, low noise',
91
+ emoji: '🧭',
92
+ bootFile: BOOT_MD
93
+ }, null, 2));
94
+
95
+ write(path.join(OPENCLAW_DIR, 'hooks.json'), JSON.stringify({
96
+ 'boot-md': { enabled: true, file: BOOT_MD },
97
+ 'command-logger': { enabled: true },
98
+ 'session-memory': { enabled: true, workspaceDir: WORKSPACE_DIR }
99
+ }, null, 2));
100
+
101
+ write(path.join(OPENCLAW_DIR, 'channels.json'), JSON.stringify({
102
+ telegram: {
103
+ enabled: true,
104
+ token: telegramToken,
105
+ dmPolicy: 'allowlist',
106
+ allowlist: [telegramUserId]
107
+ },
108
+ slack: {
109
+ enabled: true,
110
+ token: slackToken,
111
+ reportChannel: slackChannel
112
+ }
113
+ }, null, 2));
114
+
115
+ write(path.join(OPENCLAW_DIR, 'llm.json'), JSON.stringify({
116
+ provider: llmProvider,
117
+ anthropicApiKey: anthropicKey,
118
+ openaiApiKey: openaiKey
119
+ }, null, 2));
120
+
121
+ // ── Workspace identity files ──────────────────────────────────────────────
122
+
123
+ write(BOOT_MD, buildBootMd(founderName));
124
+
125
+ write(path.join(WORKSPACE_DIR, 'user.md'),
126
+ `# User — ${founderName}
127
+
128
+ ${founderName} is the founder and final decision maker.
129
+
130
+ ## Preferences
131
+ - Concise bullet points, not paragraphs
132
+ - Fast signal, not theory
133
+ - Early warning on problems before they escalate
134
+ - Clear ownership and practical next steps
135
+ `);
136
+
137
+ write(path.join(WORKSPACE_DIR, 'soul.md'),
138
+ `# Compass — Soul
139
+
140
+ I am Compass. I am the execution memory and chief of staff for a small startup.
141
+ I am not a chatbot. I do not brainstorm. I do not chat.
142
+ I extract, store, surface, and report. That is my function.
143
+
144
+ ## Voice
145
+ - Direct and concise — no filler
146
+ - Action-oriented — every output should be actionable
147
+ - Low noise — I speak only when it matters
148
+ `);
149
+
150
+ write(path.join(WORKSPACE_DIR, 'identity.md'),
151
+ `# Compass — System Role
152
+
153
+ Chief of staff and execution memory for a small startup.
154
+ Phase 1: read Telegram, extract signal, maintain state files, report to Slack.
155
+
156
+ ## Boundaries
157
+ - No sending emails
158
+ - No publishing content externally
159
+ - No spending money
160
+ - No modifying outside systems
161
+ - No autonomous decisions without approval
162
+ `);
163
+
164
+ write(path.join(WORKSPACE_DIR, 'tools.md'),
165
+ `# Compass — Allowed Tools
166
+
167
+ ## Allowed
168
+ - read_file: state files only
169
+ - write_file: state files only
170
+ - slack_post: configured channel only
171
+
172
+ ## Blocked
173
+ - send_email: NO
174
+ - http_request to external URLs: NO
175
+ - execute_command: NO
176
+ - delete_file: NO
177
+ `);
178
+
179
+ // ── State files ───────────────────────────────────────────────────────────
180
+
181
+ const stateTables = {
182
+ 'tasks.md': '# Tasks\n\n| Task | Owner | Status | Deadline | Blocker | Priority |\n|------|-------|--------|----------|---------|----------|\n',
183
+ 'decisions.md': '# Decisions\n\n| Decision | Why | Alternatives | Source | Date |\n|----------|-----|--------------|--------|------|\n',
184
+ 'blockers.md': '# Blockers\n\n| Blocker | Blocked Item | Owner | Date Added | Age |\n|---------|-------------|-------|------------|-----|\n',
185
+ 'commitments.md': '# Commitments\n\n| Commitment | Owner | Deadline | Status | Source |\n|------------|-------|----------|--------|--------|\n',
186
+ 'metrics.md': '# Metrics\n\n| Metric | Value | Date | Source |\n|--------|-------|------|--------|\n',
187
+ };
188
+
189
+ for (const [name, content] of Object.entries(stateTables)) {
190
+ write(path.join(STATE_DIR, name), content);
191
+ }
192
+
193
+ fs.mkdirSync(path.join(STATE_DIR, 'daily-summaries'), { recursive: true });
194
+ fs.mkdirSync(path.join(STATE_DIR, 'weekly-summaries'), { recursive: true });
195
+
196
+ // ── Copy skills into workspace ────────────────────────────────────────────
197
+
198
+ copyDir(SKILLS_SRC, path.join(WORKSPACE_DIR, 'skills'));
199
+
200
+ // ── Cron jobs (Linux only) ────────────────────────────────────────────────
201
+
202
+ if (process.platform === 'linux') {
203
+ setupCron(dailyTime, weeklyDay, weeklyTime);
204
+ } else {
205
+ console.log(' Cron: skipped (not Linux). Set up scheduled reports manually.');
206
+ }
207
+
208
+ console.log(' ✅ Config written to', OPENCLAW_DIR);
209
+ console.log(' ✅ State files created in', STATE_DIR);
210
+ console.log('\n─────────────────────────────────────────');
211
+ console.log(' Compass is ready.\n');
212
+ console.log(' Start: compass start');
213
+ console.log(' Daily report: compass report --type daily');
214
+ console.log(' Weekly report: compass report --type weekly');
215
+ console.log('\n Next: open Telegram → find your bot → send /start\n');
216
+ }
217
+
218
+ // ── start ─────────────────────────────────────────────────────────────────────
219
+
220
+ function start() {
221
+ console.log('🧭 Starting Compass...');
222
+ runOpenClaw('start', '--headless');
223
+ }
224
+
225
+ // ── report ────────────────────────────────────────────────────────────────────
226
+
227
+ function report() {
228
+ const typeIdx = process.argv.indexOf('--type');
229
+ const type = typeIdx !== -1 ? process.argv[typeIdx + 1] : 'daily';
230
+ if (!['daily', 'weekly'].includes(type)) {
231
+ console.error(' Type must be "daily" or "weekly"');
232
+ process.exit(1);
233
+ }
234
+ runOpenClaw('run', 'compass-report', '--type', type);
235
+ }
236
+
237
+ // ── help ──────────────────────────────────────────────────────────────────────
238
+
239
+ function help() {
240
+ console.log(`
241
+ 🧭 Compass — AI Chief of Staff
242
+
243
+ Usage:
244
+ compass init First-time setup (run once)
245
+ compass start Start the Compass agent
246
+ compass report --type daily Trigger a daily standup report
247
+ compass report --type weekly Trigger a weekly review report
248
+ `);
249
+ }
250
+
251
+ // ── utilities ─────────────────────────────────────────────────────────────────
252
+
253
+ function buildBootMd(founderName) {
254
+ return `# Compass — System Identity
255
+
256
+ You are Compass. You are the chief of staff and execution memory for a small startup.
257
+
258
+ You are not a general assistant. You do not chat. You extract signal, maintain memory, and report clearly.
259
+
260
+ ## Who you serve
261
+
262
+ ${founderName} is the founder and final decision maker. They have final authority on all strategic decisions.
263
+
264
+ Preferences:
265
+ - Concise bullet points, not paragraphs
266
+ - Fast signal, not theory
267
+ - Early warning on problems
268
+ - Clear ownership and practical next steps
269
+
270
+ ## What you do
271
+
272
+ 1. Read company conversations from Telegram
273
+ 2. Extract tasks, decisions, blockers, commitments, metrics
274
+ 3. Update state files in ${STATE_DIR}/
275
+ 4. Answer questions from stored context only
276
+ 5. Post scheduled summaries to Slack
277
+
278
+ ## State files you maintain
279
+
280
+ - ${STATE_DIR}/tasks.md
281
+ - ${STATE_DIR}/decisions.md
282
+ - ${STATE_DIR}/blockers.md
283
+ - ${STATE_DIR}/commitments.md
284
+ - ${STATE_DIR}/metrics.md
285
+ - ${STATE_DIR}/daily-summaries/
286
+ - ${STATE_DIR}/weekly-summaries/
287
+
288
+ ## Extraction rules
289
+
290
+ When you read a message or conversation, look for:
291
+
292
+ - Tasks: "I'll do X" → owner + action + deadline
293
+ - Decisions: "We're going with X" → what + why + alternatives
294
+ - Blockers: "Can't proceed because X" → blocked item + reason + who is blocked
295
+ - Commitments: "I'll send X by Friday" → owner + deliverable + deadline
296
+ - Metrics: "We hit 100 users" → metric + value + date
297
+
298
+ ## Communication rules
299
+
300
+ Telegram:
301
+ - Stay silent unless directly mentioned (@Compass) or explicitly commanded
302
+ - When asked, respond in 3-5 bullet points maximum
303
+ - Never derail a conversation
304
+
305
+ Slack:
306
+ - Post proactive daily summaries at standup time
307
+ - Post weekly reviews on Monday morning
308
+ - Lead with blockers and risks
309
+ - Keep every message scannable in 30 seconds
310
+
311
+ ## What you must not do in Phase 1
312
+
313
+ - No sending emails
314
+ - No publishing content externally
315
+ - No spending money
316
+ - No modifying outside systems
317
+ - No making business decisions
318
+ `;
319
+ }
320
+
321
+ function copyDir(src, dest) {
322
+ if (!fs.existsSync(src)) return;
323
+ fs.mkdirSync(dest, { recursive: true });
324
+ for (const entry of fs.readdirSync(src, { withFileTypes: true })) {
325
+ const srcPath = path.join(src, entry.name);
326
+ const destPath = path.join(dest, entry.name);
327
+ if (entry.isDirectory()) copyDir(srcPath, destPath);
328
+ else fs.copyFileSync(srcPath, destPath);
329
+ }
330
+ }
331
+
332
+ function setupCron(dailyTime, weeklyDay, weeklyTime) {
333
+ const bin = resolveOpenClaw();
334
+ const [dH, dM] = dailyTime.split(':');
335
+ const [wH, wM] = weeklyTime.split(':');
336
+ const dowMap = { SUN: 0, MON: 1, TUE: 2, WED: 3, THU: 4, FRI: 5, SAT: 6 };
337
+ const dow = dowMap[weeklyDay.toUpperCase()] ?? 1;
338
+
339
+ try {
340
+ let existing = '';
341
+ try { existing = execSync('crontab -l', { encoding: 'utf8' }); } catch {}
342
+ const lines = existing.split('\n').filter(l => l && !l.includes('compass report'));
343
+ lines.push(`${dM} ${dH} * * * ${bin} report --type daily`);
344
+ lines.push(`${wM} ${wH} * * ${dow} ${bin} report --type weekly`);
345
+
346
+ const tmp = path.join(os.tmpdir(), 'compass-cron');
347
+ fs.writeFileSync(tmp, lines.join('\n') + '\n');
348
+ execSync(`crontab ${tmp}`);
349
+ fs.unlinkSync(tmp);
350
+ console.log(' ✅ Cron jobs set.');
351
+ } catch {
352
+ console.log(' Cron setup failed — set up manually if needed.');
353
+ }
354
+ }
package/package.json ADDED
@@ -0,0 +1,21 @@
1
+ {
2
+ "name": "compass-ai",
3
+ "version": "1.0.0",
4
+ "description": "AI chief of staff for startups — built on OpenClaw",
5
+ "bin": {
6
+ "compass": "./bin/compass.js"
7
+ },
8
+ "files": [
9
+ "bin/",
10
+ "skills/",
11
+ "compass-boot.md"
12
+ ],
13
+ "dependencies": {
14
+ "openclaw": "latest"
15
+ },
16
+ "engines": {
17
+ "node": ">=18"
18
+ },
19
+ "keywords": ["ai", "chief-of-staff", "telegram", "slack", "openclaw"],
20
+ "license": "MIT"
21
+ }
@@ -0,0 +1,123 @@
1
+ ---
2
+ name: compass-extract
3
+ description: Compass signal extractor. Reads a Telegram conversation and extracts tasks, decisions, blockers, commitments, and metrics into the compass-state files.
4
+ metadata: { "openclaw": { "emoji": "🧭" } }
5
+ ---
6
+
7
+ # Compass Extract
8
+
9
+ ## What this skill does
10
+
11
+ Reads raw Telegram conversation text and extracts operational signal into structured state files.
12
+
13
+ ## When to use
14
+
15
+ - After receiving a batch of Telegram messages
16
+ - When asked "extract from this conversation"
17
+ - Automatically on new Telegram message batches (when configured)
18
+
19
+ ## Inputs
20
+
21
+ - `conversation`: raw text of the Telegram messages to process
22
+ - `channel`: which Telegram channel/group the messages came from
23
+ - `date`: date of the conversation (ISO format)
24
+
25
+ ## Extraction targets
26
+
27
+ ### Tasks
28
+ Pattern: someone commits to doing something
29
+ - "I'll finish X" → task
30
+ - "Can you do X by Y?" + acceptance → task
31
+ - "We need to X before Z" → task
32
+ Format to write:
33
+ ```
34
+ - **Task**: [action]
35
+ **Owner**: [name]
36
+ **Status**: todo
37
+ **Deadline**: [date or none]
38
+ **Blocker**: none
39
+ **Priority**: [high/medium/low based on urgency signals]
40
+ **Source**: [channel] [date]
41
+ ```
42
+
43
+ ### Decisions
44
+ Pattern: a choice is made between options
45
+ - "We're going with X"
46
+ - "Decided to X instead of Y"
47
+ - "Let's do X" with agreement
48
+ Format to write:
49
+ ```
50
+ - **Decision**: [what was decided]
51
+ **Why**: [reason given, or "not stated"]
52
+ **Alternatives**: [other options mentioned, or "none mentioned"]
53
+ **Source**: [channel] [date]
54
+ **Date**: [date]
55
+ ```
56
+
57
+ ### Blockers
58
+ Pattern: something is preventing progress
59
+ - "Can't do X because Y"
60
+ - "Stuck on X"
61
+ - "Waiting on X before we can Y"
62
+ Format to write:
63
+ ```
64
+ - **Blocker**: [what is blocked] — [reason]
65
+ **Affects**: [task or person]
66
+ **Raised**: [date]
67
+ **Age**: 0 days
68
+ **Status**: open
69
+ ```
70
+
71
+ ### Commitments
72
+ Pattern: a promise with a deadline
73
+ - "I'll send X by Friday"
74
+ - "Will have X ready by [date]"
75
+ Format to write:
76
+ ```
77
+ - **Commitment**: [what was promised]
78
+ **Owner**: [who promised it]
79
+ **Deadline**: [date]
80
+ **Status**: pending
81
+ **Source**: [channel] [date]
82
+ ```
83
+
84
+ ### Metrics
85
+ Pattern: a number or status update about the business
86
+ - "We hit 100 users"
87
+ - "Revenue is X"
88
+ - "Conversion rate dropped to Y"
89
+ Format to write:
90
+ ```
91
+ - **Metric**: [metric name]
92
+ **Value**: [value]
93
+ **Date**: [date]
94
+ **Source**: [who reported it]
95
+ ```
96
+
97
+ ## Output
98
+
99
+ After extraction, append new items to the appropriate state files:
100
+ - Tasks → {STATE_DIR}/tasks.md
101
+ - Decisions → {STATE_DIR}/decisions.md
102
+ - Blockers → {STATE_DIR}/blockers.md
103
+ - Commitments → {STATE_DIR}/commitments.md
104
+ - Metrics → {STATE_DIR}/metrics.md
105
+
106
+ Then respond with a brief extraction summary:
107
+ ```
108
+ Extracted from [channel] [date]:
109
+ - [N] tasks
110
+ - [N] decisions
111
+ - [N] blockers
112
+ - [N] commitments
113
+ - [N] metrics
114
+ ```
115
+
116
+ List only the items found. Skip categories with zero extractions.
117
+
118
+ ## Rules
119
+
120
+ - Do not invent items that are not clearly stated
121
+ - If ambiguous, note it as a question rather than inventing an answer
122
+ - Do not duplicate items already in state files
123
+ - Flag stale blockers (>2 days old) with a note
@@ -0,0 +1,103 @@
1
+ ---
2
+ name: compass-report
3
+ description: Compass reporting skill. Generates daily standup summaries and weekly reviews from compass-state files and posts them to Slack.
4
+ metadata: { "openclaw": { "emoji": "📊", "requires": { "config": ["channels.slack"] } } }
5
+ ---
6
+
7
+ # Compass Report
8
+
9
+ ## What this skill does
10
+
11
+ Reads the compass-state files and produces structured summaries. Posts them to a configured Slack channel.
12
+
13
+ ## When to use
14
+
15
+ - Daily: generate standup summary (post to #standup or configured channel)
16
+ - Weekly: generate weekly review (post to #general or configured channel)
17
+ - On demand: "compass report" or "@Compass summary"
18
+
19
+ ## Report types
20
+
21
+ ### Daily Standup
22
+
23
+ Structure:
24
+ ```
25
+ *Compass Daily — [Date]*
26
+
27
+ *Priorities today*
28
+ [top 3 active tasks by priority]
29
+
30
+ *Blockers*
31
+ [all open blockers — none if clear]
32
+
33
+ *Recent wins*
34
+ [tasks completed or decisions made since last standup]
35
+
36
+ *Watch*
37
+ [commitments due soon, stale items, repeated issues]
38
+ ```
39
+
40
+ Rules:
41
+ - Maximum 15 lines total
42
+ - Lead with blockers if any exist
43
+ - Only list tasks that are in-progress or high priority
44
+ - Flag any commitment due within 48 hours
45
+
46
+ ### Weekly Review
47
+
48
+ Structure:
49
+ ```
50
+ *Compass Weekly — Week of [Date]*
51
+
52
+ *Progress*
53
+ [tasks completed this week]
54
+
55
+ *Decisions made*
56
+ [decisions logged this week]
57
+
58
+ *Misses*
59
+ [commitments missed or overdue]
60
+
61
+ *Metrics movement*
62
+ [any metrics logged this week]
63
+
64
+ *Open blockers*
65
+ [all blockers still open — age shown]
66
+
67
+ *Next priorities*
68
+ [top tasks for the coming week]
69
+ ```
70
+
71
+ Rules:
72
+ - Emphasize changes, not noise
73
+ - Call out repeated blockers explicitly
74
+ - Flag overdue commitments clearly
75
+ - Maximum 25 lines total
76
+
77
+ ## How to generate
78
+
79
+ 1. Read {STATE_DIR}/tasks.md
80
+ 2. Read {STATE_DIR}/blockers.md
81
+ 3. Read {STATE_DIR}/commitments.md
82
+ 4. Read {STATE_DIR}/metrics.md (for weekly only)
83
+ 5. Read {STATE_DIR}/decisions.md (for weekly only)
84
+ 6. Compose the report
85
+ 7. Post to Slack using the slack skill
86
+ 8. Save a copy to {STATE_DIR}/daily-summaries/[date].md or {STATE_DIR}/weekly-summaries/[date].md
87
+
88
+ ## Slack posting
89
+
90
+ Use the slack skill to post to the configured report channel.
91
+ Keep formatting clean — use Slack markdown (*bold*, bullet lists).
92
+ Do not use emoji unless a blocker needs a red flag.
93
+
94
+ ## On-demand questions
95
+
96
+ If mentioned directly (@Compass), answer from state files only.
97
+ Do not invent status. If status is unknown, say so.
98
+
99
+ Examples:
100
+ - "@Compass what's blocked?" → list open blockers from blockers.md
101
+ - "@Compass what did we decide about X?" → search decisions.md
102
+ - "@Compass who owns the landing page?" → search tasks.md
103
+ - "@Compass what are our metrics?" → read metrics.md