compass-ai 1.0.0 → 1.1.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/README.md ADDED
@@ -0,0 +1,207 @@
1
+ # Compass
2
+
3
+ **Compass** is an AI chief of staff for startups. It listens to your Telegram conversations, extracts tasks, decisions, blockers, and commitments, stores them in structured files, and posts clear summaries to Slack — automatically.
4
+
5
+ Compass is not a chatbot. It is your execution memory and reporting layer.
6
+
7
+ Built on [OpenClaw](https://openclaw.ai).
8
+
9
+ ---
10
+
11
+ ## What Compass does
12
+
13
+ | Capability | Detail |
14
+ |---|---|
15
+ | Listens to Telegram | Reads group and DM conversations passively |
16
+ | Extracts signal | Tasks, decisions, blockers, commitments, metrics |
17
+ | Maintains memory | Writes structured state to local files |
18
+ | Answers questions | Responds when mentioned directly (`@Compass`) |
19
+ | Posts to Slack | Daily standups and weekly reviews on schedule |
20
+ | Stays quiet | Silent unless mentioned or scheduled |
21
+
22
+ ---
23
+
24
+ ## Prerequisites
25
+
26
+ | Requirement | Detail |
27
+ |---|---|
28
+ | Linux (Ubuntu 20.04+ / Debian 11+) | Cron scheduling requires Linux |
29
+ | Node.js 18 or higher | Runtime |
30
+ | A Telegram bot token | From [@BotFather](https://t.me/BotFather) on Telegram |
31
+ | Your Telegram user ID | From [@userinfobot](https://t.me/userinfobot) on Telegram |
32
+ | A Slack bot token | From your Slack app settings (starts with `xoxb-`) |
33
+ | An LLM API key | Anthropic or OpenAI |
34
+
35
+ ---
36
+
37
+ ## Installation
38
+
39
+ ```bash
40
+ npm install -g compass-ai
41
+ ```
42
+
43
+ This installs the `compass` CLI and automatically installs OpenClaw as a dependency.
44
+
45
+ ---
46
+
47
+ ## Setup (run once)
48
+
49
+ ```bash
50
+ compass init
51
+ ```
52
+
53
+ You will be asked:
54
+
55
+ ```
56
+ LLM provider (anthropic / openai) [anthropic]:
57
+ Anthropic API key:
58
+ Telegram bot token:
59
+ Your Telegram user ID:
60
+ Slack bot token:
61
+ Slack report channel [#standup]:
62
+ Daily standup time (HH:MM, 24h) [09:00]:
63
+ Weekly review day (MON-SUN) [MON]:
64
+ Weekly review time (HH:MM, 24h) [08:00]:
65
+ Your name (for Compass memory):
66
+ ```
67
+
68
+ Press Enter to accept any default shown in brackets.
69
+
70
+ **What `compass init` creates on your machine:**
71
+
72
+ ```
73
+ ~/.openclaw/
74
+ ├── agent.json ← Compass identity
75
+ ├── hooks.json ← boot, logger, session memory
76
+ ├── channels.json ← Telegram + Slack connection
77
+ ├── llm.json ← your LLM provider and key
78
+ └── compass/
79
+ ├── boot.md ← Compass system prompt (generated from your answers)
80
+ ├── user.md ← your profile and preferences
81
+ ├── soul.md ← Compass personality
82
+ ├── identity.md ← Compass role definition
83
+ ├── tools.md ← allowed tools
84
+ ├── skills/ ← extract and report skills
85
+ └── state/
86
+ ├── tasks.md
87
+ ├── decisions.md
88
+ ├── blockers.md
89
+ ├── commitments.md
90
+ ├── metrics.md
91
+ ├── daily-summaries/
92
+ └── weekly-summaries/
93
+ ```
94
+
95
+ On Linux, cron jobs are configured automatically for daily and weekly reports.
96
+
97
+ ---
98
+
99
+ ## Start
100
+
101
+ ```bash
102
+ compass start
103
+ ```
104
+
105
+ Compass connects to Telegram and Slack and runs in the foreground. To run it as a background service, use a process manager like `pm2` or `systemd`.
106
+
107
+ ---
108
+
109
+ ## Commands
110
+
111
+ ```bash
112
+ compass init # First-time setup
113
+ compass start # Start the agent
114
+ compass report --type daily # Trigger a daily standup report now
115
+ compass report --type weekly # Trigger a weekly review report now
116
+ compass # Show help
117
+ ```
118
+
119
+ ---
120
+
121
+ ## Using Compass in Telegram
122
+
123
+ Compass is silent by default. Mention it directly to interact:
124
+
125
+ ```
126
+ @Compass what's blocked?
127
+ @Compass who owns the landing page?
128
+ @Compass what did we decide about the API?
129
+ @Compass show this week's metrics
130
+ @Compass extract from this conversation
131
+ ```
132
+
133
+ ---
134
+
135
+ ## Slack reports
136
+
137
+ Compass posts automatically on your configured schedule:
138
+
139
+ - **Daily standup** — every day at your configured time
140
+ - **Weekly review** — every Monday (or your configured day) morning
141
+
142
+ To trigger a report immediately:
143
+
144
+ ```bash
145
+ compass report --type daily
146
+ compass report --type weekly
147
+ ```
148
+
149
+ ---
150
+
151
+ ## State files
152
+
153
+ All extracted information is stored as plain markdown files in `~/.openclaw/compass/state/`. You can read, edit, or back them up like any file.
154
+
155
+ | File | What it stores |
156
+ |---|---|
157
+ | `tasks.md` | Active tasks with owner, status, deadline, priority |
158
+ | `decisions.md` | Decision log with what, why, alternatives |
159
+ | `blockers.md` | Open blockers with age tracking |
160
+ | `commitments.md` | Commitments and deadlines |
161
+ | `metrics.md` | Key metrics log |
162
+
163
+ ---
164
+
165
+ ## Re-running setup
166
+
167
+ `compass init` is safe to re-run. It overwrites existing config with your new answers.
168
+
169
+ ```bash
170
+ compass init
171
+ ```
172
+
173
+ ---
174
+
175
+ ## Updating
176
+
177
+ ```bash
178
+ npm install -g compass-ai@latest
179
+ ```
180
+
181
+ ---
182
+
183
+ ## Troubleshooting
184
+
185
+ | Problem | Fix |
186
+ |---|---|
187
+ | `compass: command not found` | Run `npm install -g compass-ai` again |
188
+ | Telegram bot not responding | Send `/start` to your bot first, then `hi` |
189
+ | Slack not posting | Verify your `SLACK_BOT_TOKEN` and that the bot is added to the channel |
190
+ | Cron not firing | Run `crontab -l` to verify entries; check server timezone |
191
+ | OpenClaw not found after install | Run `npm install -g openclaw` manually |
192
+
193
+ ---
194
+
195
+ ## What Compass will not do (Phase 1)
196
+
197
+ - No sending emails
198
+ - No publishing content externally
199
+ - No spending money
200
+ - No modifying outside systems
201
+ - No autonomous decisions without your approval
202
+
203
+ ---
204
+
205
+ ## License
206
+
207
+ MIT
package/bin/compass.js CHANGED
@@ -7,20 +7,27 @@ const fs = require('fs');
7
7
  const path = require('path');
8
8
  const os = require('os');
9
9
 
10
- const PACKAGE_DIR = path.join(__dirname, '..');
11
- const OPENCLAW_DIR = path.join(os.homedir(), '.openclaw');
10
+ const migrations = require('../migrations/index.js');
11
+ const pkg = require('../package.json');
12
+
13
+ const PACKAGE_DIR = path.join(__dirname, '..');
14
+ const OPENCLAW_DIR = path.join(os.homedir(), '.openclaw');
12
15
  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
+ const STATE_DIR = path.join(WORKSPACE_DIR, 'state');
17
+ const BOOT_MD = path.join(WORKSPACE_DIR, 'boot.md');
18
+ const SKILLS_SRC = path.join(PACKAGE_DIR, 'skills');
16
19
 
17
20
  const cmd = process.argv[2];
18
21
 
19
22
  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;
23
+ case 'init': init().catch(err => { console.error('\n Error:', err.message); process.exit(1); }); break;
24
+ case 'start': start(); break;
25
+ case 'report': report(); break;
26
+ case 'migrate': runMigrate(); break;
27
+ case 'rollback': migrations.rollback(); break;
28
+ case 'doctor': migrations.doctor(); break;
29
+ case 'version': showVersion(); break;
30
+ default: help(); break;
24
31
  }
25
32
 
26
33
  // ── helpers ──────────────────────────────────────────────────────────────────
@@ -75,7 +82,7 @@ async function init() {
75
82
  const dailyTime = await ask(rl, 'Daily standup time (HH:MM, 24h)', '09:00');
76
83
  const weeklyDay = await ask(rl, 'Weekly review day (MON-SUN)', 'MON');
77
84
  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');
85
+ const founderName = await ask(rl, 'Your name (for Compass memory)');
79
86
 
80
87
  rl.close();
81
88
 
@@ -197,6 +204,10 @@ Phase 1: read Telegram, extract signal, maintain state files, report to Slack.
197
204
 
198
205
  copyDir(SKILLS_SRC, path.join(WORKSPACE_DIR, 'skills'));
199
206
 
207
+ // ── Write version file ────────────────────────────────────────────────────
208
+
209
+ migrations.initVersion(pkg.version);
210
+
200
211
  // ── Cron jobs (Linux only) ────────────────────────────────────────────────
201
212
 
202
213
  if (process.platform === 'linux') {
@@ -207,17 +218,20 @@ Phase 1: read Telegram, extract signal, maintain state files, report to Slack.
207
218
 
208
219
  console.log(' ✅ Config written to', OPENCLAW_DIR);
209
220
  console.log(' ✅ State files created in', STATE_DIR);
221
+ console.log(' ✅ Schema version:', migrations.CURRENT_SCHEMA);
210
222
  console.log('\n─────────────────────────────────────────');
211
223
  console.log(' Compass is ready.\n');
212
224
  console.log(' Start: compass start');
213
225
  console.log(' Daily report: compass report --type daily');
214
226
  console.log(' Weekly report: compass report --type weekly');
227
+ console.log(' Health check: compass doctor');
215
228
  console.log('\n Next: open Telegram → find your bot → send /start\n');
216
229
  }
217
230
 
218
231
  // ── start ─────────────────────────────────────────────────────────────────────
219
232
 
220
233
  function start() {
234
+ migrations.checkVersion(); // warn if schema is outdated, then continue
221
235
  console.log('🧭 Starting Compass...');
222
236
  runOpenClaw('start', '--headless');
223
237
  }
@@ -234,6 +248,30 @@ function report() {
234
248
  runOpenClaw('run', 'compass-report', '--type', type);
235
249
  }
236
250
 
251
+ // ── migrate ───────────────────────────────────────────────────────────────────
252
+
253
+ function runMigrate() {
254
+ const dry = process.argv.includes('--dry');
255
+ migrations.migrate({ dry }).catch(err => {
256
+ console.error('\n Migration error:', err.message, '\n');
257
+ process.exit(1);
258
+ });
259
+ }
260
+
261
+ // ── version ───────────────────────────────────────────────────────────────────
262
+
263
+ function showVersion() {
264
+ const versionData = migrations.readVersion();
265
+ console.log(`\n🧭 compass-ai v${pkg.version}`);
266
+ console.log(` Schema: v${versionData ? versionData.schemaVersion : '?'} / current v${migrations.CURRENT_SCHEMA}`);
267
+ console.log(` State: ${STATE_DIR}`);
268
+ console.log(` Config: ${OPENCLAW_DIR}`);
269
+ if (versionData?.lastMigratedAt) {
270
+ console.log(` Migrated: ${versionData.lastMigratedAt}`);
271
+ }
272
+ console.log('');
273
+ }
274
+
237
275
  // ── help ──────────────────────────────────────────────────────────────────────
238
276
 
239
277
  function help() {
@@ -245,6 +283,13 @@ Usage:
245
283
  compass start Start the Compass agent
246
284
  compass report --type daily Trigger a daily standup report
247
285
  compass report --type weekly Trigger a weekly review report
286
+
287
+ Maintenance:
288
+ compass migrate Run pending schema migrations
289
+ compass migrate --dry Preview migrations without applying
290
+ compass rollback Restore state from the last backup
291
+ compass doctor Full health check
292
+ compass version Show version and schema info
248
293
  `);
249
294
  }
250
295
 
@@ -318,13 +363,14 @@ Slack:
318
363
  `;
319
364
  }
320
365
 
321
- function copyDir(src, dest) {
366
+ function copyDir(src, dest, exclude = []) {
322
367
  if (!fs.existsSync(src)) return;
323
368
  fs.mkdirSync(dest, { recursive: true });
324
369
  for (const entry of fs.readdirSync(src, { withFileTypes: true })) {
370
+ if (exclude.includes(entry.name)) continue;
325
371
  const srcPath = path.join(src, entry.name);
326
372
  const destPath = path.join(dest, entry.name);
327
- if (entry.isDirectory()) copyDir(srcPath, destPath);
373
+ if (entry.isDirectory()) copyDir(srcPath, destPath, exclude);
328
374
  else fs.copyFileSync(srcPath, destPath);
329
375
  }
330
376
  }
@@ -0,0 +1,25 @@
1
+ 'use strict';
2
+
3
+ // Schema v1 — baseline. No transformations needed.
4
+ // This migration exists only to mark the starting point.
5
+ // All installs using compass init will start at schema v1.
6
+ //
7
+ // How to add the next migration:
8
+ // 1. Create 002-your-description.js with the same shape
9
+ // 2. Bump CURRENT_SCHEMA in migrations/index.js to 2
10
+ // 3. Implement up() to transform state files from v1 → v2
11
+ //
12
+ // Example for adding a column to tasks.md:
13
+ //
14
+ // up: async (stateDir) => {
15
+ // const file = path.join(stateDir, 'tasks.md');
16
+ // const content = fs.readFileSync(file, 'utf8');
17
+ // const updated = addColumn(content, 'Tags', '');
18
+ // fs.writeFileSync(file, updated, 'utf8');
19
+ // }
20
+
21
+ module.exports = {
22
+ version: 1,
23
+ description: 'Initial schema baseline',
24
+ up: async () => {},
25
+ };
@@ -0,0 +1,316 @@
1
+ 'use strict';
2
+
3
+ const fs = require('fs');
4
+ const path = require('path');
5
+ const os = require('os');
6
+ const crypto = require('crypto');
7
+
8
+ // ── Schema version ────────────────────────────────────────────────────────────
9
+ // Bump ONLY when state file structure changes. NOT on every package release.
10
+ const CURRENT_SCHEMA = 1;
11
+
12
+ // ── Paths ─────────────────────────────────────────────────────────────────────
13
+ const WORKSPACE_DIR = path.join(os.homedir(), '.openclaw', 'compass');
14
+ const STATE_DIR = path.join(WORKSPACE_DIR, 'state');
15
+ const VERSION_FILE = path.join(WORKSPACE_DIR, 'version.json');
16
+ const BACKUPS_DIR = path.join(WORKSPACE_DIR, 'backups');
17
+ const BOOT_MD = path.join(WORKSPACE_DIR, 'boot.md');
18
+
19
+ // ── Discover and load migration files ────────────────────────────────────────
20
+ // Files must match NNN-description.js (e.g. 002-add-priority.js)
21
+ const MIGRATIONS = fs.readdirSync(__dirname)
22
+ .filter(f => /^\d{3}-.+\.js$/.test(f))
23
+ .sort()
24
+ .map(f => require(path.join(__dirname, f)));
25
+
26
+ // ── Version file ──────────────────────────────────────────────────────────────
27
+
28
+ function readVersion() {
29
+ if (!fs.existsSync(VERSION_FILE)) return null;
30
+ try { return JSON.parse(fs.readFileSync(VERSION_FILE, 'utf8')); } catch { return null; }
31
+ }
32
+
33
+ function writeVersion(data) {
34
+ fs.mkdirSync(path.dirname(VERSION_FILE), { recursive: true });
35
+ fs.writeFileSync(VERSION_FILE, JSON.stringify(data, null, 2) + '\n', 'utf8');
36
+ }
37
+
38
+ // ── Hashing ───────────────────────────────────────────────────────────────────
39
+
40
+ function hashFile(filePath) {
41
+ if (!fs.existsSync(filePath)) return null;
42
+ const content = fs.readFileSync(filePath, 'utf8');
43
+ return crypto.createHash('sha256').update(content).digest('hex').slice(0, 16);
44
+ }
45
+
46
+ // ── Backup ────────────────────────────────────────────────────────────────────
47
+
48
+ function backup() {
49
+ const ts = new Date().toISOString().replace(/[:.]/g, '-');
50
+ const backupDir = path.join(BACKUPS_DIR, `backup-${ts}`);
51
+
52
+ copyDir(WORKSPACE_DIR, backupDir, ['backups']); // never recurse into backups
53
+
54
+ // Keep only the last 5 backups — prune oldest
55
+ if (fs.existsSync(BACKUPS_DIR)) {
56
+ const all = fs.readdirSync(BACKUPS_DIR).sort();
57
+ if (all.length > 5) {
58
+ all.slice(0, all.length - 5).forEach(b => rmDir(path.join(BACKUPS_DIR, b)));
59
+ }
60
+ }
61
+
62
+ return backupDir;
63
+ }
64
+
65
+ function latestBackup() {
66
+ if (!fs.existsSync(BACKUPS_DIR)) return null;
67
+ const all = fs.readdirSync(BACKUPS_DIR).sort();
68
+ return all.length ? path.join(BACKUPS_DIR, all[all.length - 1]) : null;
69
+ }
70
+
71
+ // ── Migrate ───────────────────────────────────────────────────────────────────
72
+
73
+ async function migrate(options = {}) {
74
+ const { dry = false } = options;
75
+
76
+ const versionData = readVersion();
77
+
78
+ // Legacy install: has state files but no version.json — treat as schema v0
79
+ const fromSchema = versionData ? versionData.schemaVersion : 0;
80
+
81
+ if (fromSchema === CURRENT_SCHEMA) {
82
+ console.log('\n Already up to date. Schema version:', CURRENT_SCHEMA, '\n');
83
+ return;
84
+ }
85
+
86
+ const pending = MIGRATIONS.filter(m => m.version > fromSchema && m.version <= CURRENT_SCHEMA);
87
+
88
+ if (pending.length === 0) {
89
+ console.log('\n No migrations to run.\n');
90
+ return;
91
+ }
92
+
93
+ console.log(`\n Pending migrations (schema v${fromSchema} → v${CURRENT_SCHEMA}):\n`);
94
+ pending.forEach(m => console.log(` • v${m.version}: ${m.description}`));
95
+
96
+ if (dry) {
97
+ console.log('\n Dry run complete — no changes made.\n');
98
+ return;
99
+ }
100
+
101
+ // Backup before touching anything
102
+ console.log('\n Creating backup...');
103
+ const backupDir = backup();
104
+ console.log(' Backed up to:', backupDir, '\n');
105
+
106
+ // Run each migration in sequence — abort and rollback on first failure
107
+ let lastApplied = fromSchema;
108
+
109
+ for (const migration of pending) {
110
+ process.stdout.write(` v${migration.version}: ${migration.description}... `);
111
+ try {
112
+ await migration.up(STATE_DIR, WORKSPACE_DIR);
113
+ lastApplied = migration.version;
114
+ console.log('✅');
115
+ } catch (err) {
116
+ console.log('❌');
117
+ console.error('\n Migration failed:', err.message);
118
+ console.error(' Rolling back from backup...');
119
+ restoreFromDir(backupDir);
120
+ console.error(' Rollback complete. Schema unchanged.\n');
121
+ process.exit(1);
122
+ }
123
+ }
124
+
125
+ // Update version file
126
+ writeVersion({
127
+ ...(versionData || {}),
128
+ schemaVersion: lastApplied,
129
+ packageVersion: require('../package.json').version,
130
+ lastMigratedAt: new Date().toISOString(),
131
+ migrationsApplied: [
132
+ ...((versionData || {}).migrationsApplied || []),
133
+ ...pending.map(m => m.version),
134
+ ],
135
+ bootMdHash: hashFile(BOOT_MD),
136
+ });
137
+
138
+ console.log(`\n Migration complete. Schema is now v${lastApplied}.\n`);
139
+ }
140
+
141
+ // ── Rollback ──────────────────────────────────────────────────────────────────
142
+
143
+ function rollback() {
144
+ const backupDir = latestBackup();
145
+ if (!backupDir) {
146
+ console.error('\n No backups found in', BACKUPS_DIR, '\n');
147
+ process.exit(1);
148
+ }
149
+ console.log('\n Restoring from:', backupDir);
150
+ restoreFromDir(backupDir);
151
+ console.log(' Rollback complete.\n');
152
+ }
153
+
154
+ function restoreFromDir(backupDir) {
155
+ copyDir(backupDir, WORKSPACE_DIR, []);
156
+ }
157
+
158
+ // ── Version check (called on compass start) ───────────────────────────────────
159
+
160
+ function checkVersion() {
161
+ const versionData = readVersion();
162
+
163
+ // Legacy install: state files exist but no version.json
164
+ if (!versionData) {
165
+ if (fs.existsSync(path.join(STATE_DIR, 'tasks.md'))) {
166
+ console.warn('\n ⚠️ Existing state found but no schema version recorded.');
167
+ console.warn(' Run: compass migrate\n');
168
+ }
169
+ return;
170
+ }
171
+
172
+ if (versionData.schemaVersion < CURRENT_SCHEMA) {
173
+ console.warn(`\n ⚠️ State files are schema v${versionData.schemaVersion}, current is v${CURRENT_SCHEMA}.`);
174
+ console.warn(' Run: compass migrate\n');
175
+ }
176
+ }
177
+
178
+ // ── Init version (called after compass init) ──────────────────────────────────
179
+
180
+ function initVersion(packageVersion) {
181
+ const existing = readVersion();
182
+ writeVersion({
183
+ schemaVersion: CURRENT_SCHEMA,
184
+ packageVersion,
185
+ initializedAt: existing?.initializedAt || new Date().toISOString(),
186
+ lastMigratedAt: null,
187
+ migrationsApplied: [],
188
+ bootMdHash: hashFile(BOOT_MD),
189
+ });
190
+ }
191
+
192
+ // ── Doctor ────────────────────────────────────────────────────────────────────
193
+
194
+ function doctor() {
195
+ const pkg = require('../package.json');
196
+ const versionData = readVersion();
197
+
198
+ const check = (label, ok, detail = '') => {
199
+ const icon = ok ? '✅' : '❌';
200
+ const suffix = detail ? ` ${detail}` : '';
201
+ console.log(` ${icon} ${label}${suffix}`);
202
+ return ok;
203
+ };
204
+
205
+ console.log(`\n🧭 Compass — Doctor\n`);
206
+ console.log(` Package: compass-ai v${pkg.version}`);
207
+ console.log(` Schema: v${versionData ? versionData.schemaVersion : '?'} / current v${CURRENT_SCHEMA}`);
208
+ console.log('');
209
+
210
+ // Runtime
211
+ console.log(' Runtime');
212
+ const nodeOk = parseInt(process.version.slice(1)) >= 18;
213
+ check('Node.js', nodeOk, process.version);
214
+
215
+ let oclawOk = false;
216
+ try {
217
+ const { execSync } = require('child_process');
218
+ const oclawPath = execSync('which openclaw 2>/dev/null || command -v openclaw', { encoding: 'utf8' }).trim();
219
+ oclawOk = !!oclawPath;
220
+ check('OpenClaw', oclawOk, oclawPath);
221
+ } catch {
222
+ check('OpenClaw', false, 'not found — run: npm install -g openclaw');
223
+ }
224
+
225
+ // Config files
226
+ console.log('\n Config');
227
+ const configDir = path.join(os.homedir(), '.openclaw');
228
+ ['agent.json', 'channels.json', 'llm.json', 'hooks.json'].forEach(f => {
229
+ check(f, fs.existsSync(path.join(configDir, f)));
230
+ });
231
+
232
+ // Schema
233
+ console.log('\n Schema');
234
+ if (!versionData) {
235
+ check('version.json', false, 'missing — run: compass migrate');
236
+ } else {
237
+ const current = versionData.schemaVersion === CURRENT_SCHEMA;
238
+ check('version.json', current,
239
+ current ? `v${versionData.schemaVersion} (current)` : `v${versionData.schemaVersion} — run: compass migrate`);
240
+ }
241
+
242
+ // State files
243
+ console.log('\n State files');
244
+ ['tasks.md', 'decisions.md', 'blockers.md', 'commitments.md', 'metrics.md'].forEach(f => {
245
+ check(f, fs.existsSync(path.join(STATE_DIR, f)));
246
+ });
247
+
248
+ // Connections
249
+ console.log('\n Connections');
250
+ try {
251
+ const channels = JSON.parse(fs.readFileSync(path.join(configDir, 'channels.json'), 'utf8'));
252
+ check('Telegram', !!channels.telegram?.token, channels.telegram?.token ? 'configured' : 'missing token');
253
+ check('Slack', !!channels.slack?.token, channels.slack?.token ? 'configured' : 'missing token');
254
+ } catch {
255
+ check('channels.json', false, 'could not read');
256
+ }
257
+
258
+ // Cron (Linux only)
259
+ if (process.platform === 'linux') {
260
+ console.log('\n Cron');
261
+ try {
262
+ const { execSync } = require('child_process');
263
+ const crontab = execSync('crontab -l 2>/dev/null || true', { encoding: 'utf8' });
264
+ check('Daily report', crontab.includes('compass report --type daily'));
265
+ check('Weekly report', crontab.includes('compass report --type weekly'));
266
+ } catch {
267
+ check('Cron', false, 'could not read crontab');
268
+ }
269
+ }
270
+
271
+ // Backups
272
+ console.log('\n Backups');
273
+ const latest = latestBackup();
274
+ if (latest) {
275
+ const name = path.basename(latest);
276
+ check('Latest backup', true, name);
277
+ } else {
278
+ console.log(' — No backups yet (created automatically before each migration)');
279
+ }
280
+
281
+ console.log('');
282
+ }
283
+
284
+ // ── Utilities ─────────────────────────────────────────────────────────────────
285
+
286
+ function copyDir(src, dest, exclude = []) {
287
+ if (!fs.existsSync(src)) return;
288
+ fs.mkdirSync(dest, { recursive: true });
289
+ for (const entry of fs.readdirSync(src, { withFileTypes: true })) {
290
+ if (exclude.includes(entry.name)) continue;
291
+ const srcPath = path.join(src, entry.name);
292
+ const destPath = path.join(dest, entry.name);
293
+ if (entry.isDirectory()) copyDir(srcPath, destPath, exclude);
294
+ else fs.copyFileSync(srcPath, destPath);
295
+ }
296
+ }
297
+
298
+ function rmDir(dir) {
299
+ if (!fs.existsSync(dir)) return;
300
+ for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
301
+ const p = path.join(dir, entry.name);
302
+ if (entry.isDirectory()) rmDir(p);
303
+ else fs.unlinkSync(p);
304
+ }
305
+ fs.rmdirSync(dir);
306
+ }
307
+
308
+ module.exports = {
309
+ migrate,
310
+ rollback,
311
+ checkVersion,
312
+ initVersion,
313
+ doctor,
314
+ CURRENT_SCHEMA,
315
+ readVersion,
316
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "compass-ai",
3
- "version": "1.0.0",
3
+ "version": "1.1.0",
4
4
  "description": "AI chief of staff for startups — built on OpenClaw",
5
5
  "bin": {
6
6
  "compass": "./bin/compass.js"
@@ -8,7 +8,7 @@
8
8
  "files": [
9
9
  "bin/",
10
10
  "skills/",
11
- "compass-boot.md"
11
+ "migrations/"
12
12
  ],
13
13
  "dependencies": {
14
14
  "openclaw": "latest"