atris 3.15.56 → 3.16.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.
Files changed (44) hide show
  1. package/AGENTS.md +2 -2
  2. package/GETTING_STARTED.md +1 -1
  3. package/PERSONA.md +4 -4
  4. package/README.md +11 -11
  5. package/atris/skills/copy-editor/SKILL.md +30 -4
  6. package/atris/skills/improve/SKILL.md +18 -20
  7. package/atris/wiki/concepts/agent-activation-contract.md +5 -3
  8. package/atris/wiki/concepts/workspace-initialization-contract.md +4 -4
  9. package/atris/wiki/index.md +1 -0
  10. package/ax +522 -73
  11. package/bin/atris.js +32 -31
  12. package/commands/align.js +0 -14
  13. package/commands/apps.js +102 -1
  14. package/commands/autopilot.js +197 -22
  15. package/commands/brain.js +219 -34
  16. package/commands/brainstorm.js +0 -829
  17. package/commands/computer.js +45 -83
  18. package/commands/improve.js +501 -0
  19. package/commands/integrations.js +228 -0
  20. package/commands/lesson.js +44 -0
  21. package/commands/member.js +4498 -226
  22. package/commands/mission.js +302 -27
  23. package/commands/now.js +89 -1
  24. package/commands/radar.js +181 -56
  25. package/commands/skill.js +37 -6
  26. package/commands/soul.js +0 -4
  27. package/commands/task.js +5582 -517
  28. package/commands/terminal.js +14 -10
  29. package/commands/wiki.js +87 -1
  30. package/commands/workflow.js +288 -73
  31. package/commands/worktree.js +52 -15
  32. package/commands/xp.js +41 -65
  33. package/lib/auto-accept-certified.js +294 -0
  34. package/lib/file-ops.js +0 -184
  35. package/lib/member-alive.js +232 -0
  36. package/lib/policy-lessons.js +280 -0
  37. package/lib/receipt-evidence.js +64 -0
  38. package/lib/state-detection.js +34 -0
  39. package/lib/task-db.js +568 -16
  40. package/lib/task-proof.js +43 -0
  41. package/package.json +1 -1
  42. package/utils/auth.js +13 -4
  43. package/commands/research.js +0 -52
  44. package/lib/section-merge.js +0 -196
@@ -8,6 +8,7 @@
8
8
  * atris calendar yesterday - Show yesterday's events
9
9
  * atris calendar week - Show this week's events
10
10
  * atris calendar date YYYY-MM-DD - Show events on a date
11
+ * atris calendar search <query> [--days 30] [--limit 20] [--timeout-ms 10000] [--json] - Search upcoming events
11
12
  * atris twitter post - Post a tweet (interactive)
12
13
  * atris slack channels - List Slack channels
13
14
  * atris slack messages <channel> [--limit 20] - Read recent messages
@@ -21,6 +22,8 @@ const os = require('os');
21
22
  const path = require('path');
22
23
  const { spawnSync } = require('child_process');
23
24
 
25
+ const CALENDAR_CACHE_PATH = path.join(os.homedir(), '.atris', 'calendar-events-cache.json');
26
+
24
27
  async function getAuth() {
25
28
  const ensured = await ensureValidCredentials(apiRequestJson);
26
29
  const creds = ensured.error ? null : ensured.credentials;
@@ -209,6 +212,166 @@ function formatCalendarEvents(label, events) {
209
212
  console.log('─'.repeat(50));
210
213
  }
211
214
 
215
+ function parseCalendarSearchArgs(args = []) {
216
+ const values = [];
217
+ const options = { days: 30, limit: 20, timeoutMs: 10000, json: false };
218
+ for (let i = 0; i < args.length; i += 1) {
219
+ const arg = args[i];
220
+ if (arg === '--json') {
221
+ options.json = true;
222
+ } else if (arg === '--days' || arg === '-d') {
223
+ const parsed = Number.parseInt(args[i + 1], 10);
224
+ if (Number.isFinite(parsed) && parsed > 0) options.days = Math.min(parsed, 365);
225
+ i += 1;
226
+ } else if (arg === '--limit' || arg === '-n') {
227
+ const parsed = Number.parseInt(args[i + 1], 10);
228
+ if (Number.isFinite(parsed) && parsed > 0) options.limit = Math.min(parsed, 100);
229
+ i += 1;
230
+ } else if (arg === '--timeout-ms' || arg === '--timeout') {
231
+ const parsed = Number.parseInt(args[i + 1], 10);
232
+ if (Number.isFinite(parsed) && parsed > 0) options.timeoutMs = Math.min(parsed, 60000);
233
+ i += 1;
234
+ } else {
235
+ values.push(arg);
236
+ }
237
+ }
238
+ return { query: values.join(' ').trim(), ...options };
239
+ }
240
+
241
+ function calendarAttendeesText(attendees) {
242
+ if (!Array.isArray(attendees)) return '';
243
+ return attendees
244
+ .map((attendee) => {
245
+ if (typeof attendee === 'string') return attendee;
246
+ return [
247
+ attendee.email,
248
+ attendee.displayName,
249
+ attendee.name,
250
+ attendee.responseStatus,
251
+ ].filter(Boolean).join(' ');
252
+ })
253
+ .filter(Boolean)
254
+ .join(' ');
255
+ }
256
+
257
+ function calendarSearchText(event = {}) {
258
+ return [
259
+ event.summary,
260
+ event.title,
261
+ event.location,
262
+ event.description,
263
+ calendarAttendeesText(event.attendees),
264
+ ].filter(Boolean).join(' ').toLowerCase();
265
+ }
266
+
267
+ function filterCalendarEventsForSearch(events = [], query = '', limit = 20) {
268
+ const terms = String(query || '').toLowerCase().split(/\s+/).filter(Boolean);
269
+ if (!terms.length) return [];
270
+ return events
271
+ .filter((event) => {
272
+ const haystack = calendarSearchText(event);
273
+ return terms.every((term) => haystack.includes(term));
274
+ })
275
+ .slice(0, limit);
276
+ }
277
+
278
+ function compactCalendarEvent(event = {}) {
279
+ return {
280
+ id: event.id || event.event_id || '',
281
+ start: calendarEventTimeValue(event.start || event.start_time || event.startTime),
282
+ end: calendarEventTimeValue(event.end || event.end_time || event.endTime),
283
+ summary: event.summary || event.title || '(no title)',
284
+ location: event.location || '',
285
+ htmlLink: event.htmlLink || event.link || '',
286
+ };
287
+ }
288
+
289
+ function calendarCachePacket(events = [], meta = {}) {
290
+ return {
291
+ ok: true,
292
+ updatedAt: new Date().toISOString(),
293
+ source: meta.source || 'live',
294
+ days: meta.days || null,
295
+ count: events.length,
296
+ events: events.map(compactCalendarEvent),
297
+ };
298
+ }
299
+
300
+ function writeCalendarEventsCache(events = [], meta = {}) {
301
+ const rows = Array.isArray(events) ? events : [];
302
+ try {
303
+ fs.mkdirSync(path.dirname(CALENDAR_CACHE_PATH), { recursive: true });
304
+ fs.writeFileSync(CALENDAR_CACHE_PATH, `${JSON.stringify(calendarCachePacket(rows, meta), null, 2)}\n`, 'utf8');
305
+ return CALENDAR_CACHE_PATH;
306
+ } catch {
307
+ return '';
308
+ }
309
+ }
310
+
311
+ function readCalendarEventsCache() {
312
+ try {
313
+ const packet = JSON.parse(fs.readFileSync(CALENDAR_CACHE_PATH, 'utf8'));
314
+ const events = Array.isArray(packet.events) ? packet.events : [];
315
+ return {
316
+ ok: true,
317
+ path: CALENDAR_CACHE_PATH,
318
+ updatedAt: packet.updatedAt || '',
319
+ events,
320
+ };
321
+ } catch {
322
+ return { ok: false, path: CALENDAR_CACHE_PATH, updatedAt: '', events: [] };
323
+ }
324
+ }
325
+
326
+ function formatCalendarSearchResults(query, matches, {
327
+ json = false,
328
+ total = matches.length,
329
+ source = 'live',
330
+ liveOk = true,
331
+ cacheUpdatedAt = '',
332
+ error = '',
333
+ } = {}) {
334
+ if (json) {
335
+ console.log(JSON.stringify({
336
+ ok: true,
337
+ query,
338
+ source,
339
+ liveOk,
340
+ cacheUpdatedAt,
341
+ error,
342
+ totalEvents: total,
343
+ matchCount: matches.length,
344
+ matches: matches.map(compactCalendarEvent),
345
+ }, null, 2));
346
+ return;
347
+ }
348
+
349
+ if (!matches.length) {
350
+ console.log(`No upcoming events matched "${query}".`);
351
+ return;
352
+ }
353
+
354
+ console.log(`Found ${matches.length} upcoming event(s) matching "${query}":\n`);
355
+ formatCalendarEvents('matching your search', matches);
356
+ }
357
+
358
+ function formatCalendarSearchError(query, message, { json = false, status = 0 } = {}) {
359
+ if (json) {
360
+ console.log(JSON.stringify({
361
+ ok: false,
362
+ query,
363
+ status,
364
+ error: message || 'Failed to fetch events',
365
+ totalEvents: 0,
366
+ matchCount: 0,
367
+ matches: [],
368
+ }, null, 2));
369
+ return;
370
+ }
371
+ console.error(`Error: ${message || 'Failed to fetch events'}`);
372
+ process.exit(1);
373
+ }
374
+
212
375
  async function calendarRange(label, { timeMin, timeMax, days } = {}) {
213
376
  const { token, email } = await getAuth();
214
377
 
@@ -236,9 +399,63 @@ async function calendarRange(label, { timeMin, timeMax, days } = {}) {
236
399
  }
237
400
 
238
401
  const events = result.data?.events || result.data || [];
402
+ writeCalendarEventsCache(events, { source: 'calendarRange', days: days || null });
239
403
  formatCalendarEvents(label.toLowerCase(), events);
240
404
  }
241
405
 
406
+ async function calendarSearch(args = []) {
407
+ const { query, days, limit, timeoutMs, json } = parseCalendarSearchArgs(args);
408
+ if (!query) {
409
+ console.error('Usage: atris calendar search <query> [--days 30] [--limit 20] [--timeout-ms 10000] [--json]');
410
+ process.exit(1);
411
+ }
412
+
413
+ const credentials = loadCredentials();
414
+ const token = credentials && credentials.token;
415
+ const email = credentials && credentials.email || 'unknown';
416
+ if (!token) {
417
+ formatCalendarSearchError(query, 'Not logged in. Run: atris login', { json, status: 401 });
418
+ return;
419
+ }
420
+ const result = await apiRequestJson(`/integrations/google-calendar/events?days=${encodeURIComponent(String(days))}`, {
421
+ method: 'GET',
422
+ token,
423
+ timeoutMs,
424
+ retries: 0,
425
+ });
426
+
427
+ if (!result.ok) {
428
+ const cache = readCalendarEventsCache();
429
+ if (json && cache.ok) {
430
+ const matches = filterCalendarEventsForSearch(cache.events, query, limit);
431
+ formatCalendarSearchResults(query, matches, {
432
+ json,
433
+ total: cache.events.length,
434
+ source: 'cache',
435
+ liveOk: false,
436
+ cacheUpdatedAt: cache.updatedAt,
437
+ error: result.error || 'Failed to fetch events',
438
+ });
439
+ return;
440
+ }
441
+ if (result.status === 400 || result.status === 401) {
442
+ formatCalendarSearchError(
443
+ query,
444
+ `Calendar not connected for ${email}. Connect at: https://atris.ai/dashboard/settings`,
445
+ { json, status: result.status },
446
+ );
447
+ } else {
448
+ formatCalendarSearchError(query, result.error || 'Failed to fetch events', { json, status: result.status || 0 });
449
+ }
450
+ return;
451
+ }
452
+
453
+ const events = result.data?.events || result.data || [];
454
+ writeCalendarEventsCache(events, { source: 'calendarSearch', days });
455
+ const matches = filterCalendarEventsForSearch(events, query, limit);
456
+ formatCalendarSearchResults(query, matches, { json, total: events.length, source: 'live', liveOk: true });
457
+ }
458
+
242
459
  async function calendarToday() {
243
460
  await calendarRange("Today's events", { days: 1 });
244
461
  }
@@ -272,12 +489,16 @@ async function calendarCommand(subcommand, ...args) {
272
489
  case 'date':
273
490
  await calendarDate(args[0]);
274
491
  break;
492
+ case 'search':
493
+ await calendarSearch(args);
494
+ break;
275
495
  default:
276
496
  console.log('Calendar commands:');
277
497
  console.log(' atris calendar today - Show today\'s events');
278
498
  console.log(' atris calendar yesterday - Show yesterday\'s events');
279
499
  console.log(' atris calendar week - Show this week\'s events');
280
500
  console.log(' atris calendar date YYYY-MM-DD - Show events on a date');
501
+ console.log(' atris calendar search <query> [--days 30] [--limit 20] [--timeout-ms 10000] [--json] - Search upcoming events');
281
502
  }
282
503
  }
283
504
 
@@ -1233,4 +1454,11 @@ module.exports = {
1233
1454
  imessageCommand,
1234
1455
  imessageDoctor,
1235
1456
  integrationsStatus,
1457
+ parseCalendarSearchArgs,
1458
+ filterCalendarEventsForSearch,
1459
+ formatCalendarSearchResults,
1460
+ formatCalendarSearchError,
1461
+ writeCalendarEventsCache,
1462
+ readCalendarEventsCache,
1463
+ CALENDAR_CACHE_PATH,
1236
1464
  };
@@ -2,6 +2,44 @@ const fs = require('fs');
2
2
  const path = require('path');
3
3
  const { writeLesson } = require('./autopilot');
4
4
 
5
+ function mineLessons(args) {
6
+ const { loadHistory, mineProofPolicy, writePolicyLessons, syncLessonsMd } = require('../lib/policy-lessons');
7
+ const root = process.cwd();
8
+ const json = args.includes('--json');
9
+ const dryRun = args.includes('--dry-run');
10
+ const history = loadHistory(root);
11
+ const mined = mineProofPolicy(history);
12
+ let statePath = null;
13
+ let lessonsMd = { path: null, written: [] };
14
+ if (!dryRun) {
15
+ statePath = writePolicyLessons(root, mined);
16
+ lessonsMd = syncLessonsMd(root, mined);
17
+ }
18
+ if (json) {
19
+ console.log(JSON.stringify({
20
+ ok: true,
21
+ action: 'lesson_mine',
22
+ dry_run: dryRun,
23
+ state_path: statePath,
24
+ lessons_md_path: lessonsMd.path,
25
+ lessons_md_written: lessonsMd.written,
26
+ ...mined,
27
+ }, null, 2));
28
+ return;
29
+ }
30
+ const { sources } = mined;
31
+ console.log(`mined ${mined.lessons.length} policy lesson(s) from ${sources.career_xp_receipts} receipts / ${sources.task_episodes} episodes / ${sources.scorecards} scorecards (${sources.human_reviewed_episodes} human-reviewed)`);
32
+ for (const lesson of mined.lessons) {
33
+ console.log(`- policy-${lesson.id}: ${lesson.lesson}`);
34
+ }
35
+ if (dryRun) {
36
+ console.log('(dry-run: nothing written)');
37
+ } else {
38
+ console.log(`state: ${statePath}`);
39
+ console.log(`lessons.md: ${lessonsMd.written.length ? lessonsMd.written.map((id) => `policy-${id}`).join(', ') : 'no entries written'}`);
40
+ }
41
+ }
42
+
5
43
  function lessonAtris(subcommand, ...args) {
6
44
  const atrisDir = path.join(process.cwd(), 'atris');
7
45
  if (!fs.existsSync(atrisDir)) {
@@ -9,9 +47,15 @@ function lessonAtris(subcommand, ...args) {
9
47
  process.exit(1);
10
48
  }
11
49
 
50
+ if (subcommand === 'mine') {
51
+ mineLessons(args);
52
+ return;
53
+ }
54
+
12
55
  if (subcommand !== 'add') {
13
56
  console.log('');
14
57
  console.log(' Usage: atris lesson add <slug> <pass|fail> "<text>"');
58
+ console.log(' atris lesson mine [--json] [--dry-run]');
15
59
  console.log('');
16
60
  process.exit(subcommand ? 1 : 0);
17
61
  }