atris 3.15.13 → 3.15.22
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/AGENTS.md +84 -8
- package/README.md +5 -1
- package/atris/AGENTS.md +46 -1
- package/atris/CLAUDE.md +36 -1
- package/atris/GEMINI.md +14 -1
- package/atris/atris.md +12 -1
- package/atris/atrisDev.md +3 -2
- package/atris/context/README.md +11 -0
- package/atris/features/company-brain-sync/validate.md +5 -5
- package/atris/learnings.jsonl +1 -0
- package/atris/policies/atris-design.md +2 -0
- package/atris/skills/aeo/SKILL.md +2 -2
- package/atris/skills/atris/SKILL.md +15 -62
- package/atris/skills/design/SKILL.md +2 -0
- package/atris/skills/imessage/SKILL.md +19 -2
- package/atris/skills/loop/SKILL.md +6 -5
- package/atris/skills/magic-inbox/SKILL.md +1 -1
- package/atris/team/_template/MEMBER.md +23 -1
- package/atris/team/brainstormer/START_HERE.md +6 -0
- package/atris/team/executor/MEMBER.md +13 -0
- package/atris/team/executor/START_HERE.md +6 -0
- package/atris/team/launcher/START_HERE.md +6 -0
- package/atris/team/mission-lead/MEMBER.md +39 -0
- package/atris/team/mission-lead/MISSION.md +33 -0
- package/atris/team/mission-lead/START_HERE.md +6 -0
- package/atris/team/navigator/MEMBER.md +11 -0
- package/atris/team/navigator/START_HERE.md +6 -0
- package/atris/team/opus-overnight/MEMBER.md +39 -0
- package/atris/team/opus-overnight/MISSION.md +61 -0
- package/atris/team/opus-overnight/START_HERE.md +6 -0
- package/atris/team/opus-overnight/STEERING.md +35 -0
- package/atris/team/researcher/START_HERE.md +6 -0
- package/atris/team/validator/MEMBER.md +26 -6
- package/atris/team/validator/START_HERE.md +6 -0
- package/atris/wiki/concepts/agent-activation-contract.md +79 -0
- package/atris/wiki/concepts/workspace-initialization-contract.md +73 -0
- package/atris/wiki/index.md +27 -0
- package/atris/wiki/sources/atris-labs-2026-05-10.txt +17 -0
- package/atris/wiki/sources/atris-labs-goals-2026-05-10.txt +15 -0
- package/atris/wiki/sources/atrisos-generative-ui-product-surface-2026-05-10.txt +10 -0
- package/atris/wiki/sources/jack-dorsey-2026-05-10.txt +12 -0
- package/atris.md +49 -13
- package/bin/atris.js +660 -22
- package/commands/activate.js +12 -3
- package/commands/aeo.js +1 -1
- package/commands/align.js +10 -10
- package/commands/analytics.js +9 -4
- package/commands/app.js +2 -0
- package/commands/apps.js +276 -0
- package/commands/auth.js +1 -1
- package/commands/autopilot.js +74 -5
- package/commands/brain.js +536 -61
- package/commands/brainstorm.js +12 -12
- package/commands/business-sync.js +142 -24
- package/commands/clean.js +9 -6
- package/commands/codex-goal.js +311 -0
- package/commands/errors.js +11 -1
- package/commands/feedback.js +55 -17
- package/commands/fork.js +2 -2
- package/commands/gm.js +376 -0
- package/commands/init.js +80 -3
- package/commands/integrations.js +524 -0
- package/commands/learn.js +25 -16
- package/commands/lesson.js +41 -0
- package/commands/lifecycle.js +2 -2
- package/commands/member.js +2416 -9
- package/commands/mission.js +1776 -0
- package/commands/now.js +48 -7
- package/commands/play.js +425 -0
- package/commands/publish.js +2 -1
- package/commands/pull.js +72 -29
- package/commands/push.js +199 -17
- package/commands/review.js +51 -13
- package/commands/skill.js +2 -2
- package/commands/soul.js +19 -13
- package/commands/status.js +6 -1
- package/commands/sync.js +5 -4
- package/commands/task.js +1041 -147
- package/commands/terminal.js +5 -5
- package/commands/verify.js +7 -5
- package/commands/visualize.js +7 -0
- package/commands/wiki.js +53 -16
- package/commands/workflow.js +298 -54
- package/commands/workspace-clean.js +1 -1
- package/commands/worktree.js +468 -0
- package/commands/xp.js +1608 -0
- package/lib/manifest.js +34 -4
- package/lib/scorecard.js +3 -2
- package/lib/task-db.js +408 -27
- package/lib/todo-fallback.js +28 -2
- package/lib/todo.js +5 -3
- package/package.json +23 -2
- package/utils/update-check.js +51 -1
package/commands/integrations.js
CHANGED
|
@@ -418,6 +418,520 @@ function imessageRecent(handle, options = {}) {
|
|
|
418
418
|
console.log(result.stdout.trim() || 'No recent messages found.');
|
|
419
419
|
}
|
|
420
420
|
|
|
421
|
+
function escapeSqlString(value) {
|
|
422
|
+
return String(value || '').replace(/'/g, "''");
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
function normalizeLookupKey(value) {
|
|
426
|
+
return String(value || '')
|
|
427
|
+
.toLowerCase()
|
|
428
|
+
.replace(/[^a-z0-9@+]+/g, ' ')
|
|
429
|
+
.replace(/\s+/g, ' ')
|
|
430
|
+
.trim();
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
function normalizeImessageHandle(value) {
|
|
434
|
+
const raw = String(value || '').trim();
|
|
435
|
+
if (!raw) return '';
|
|
436
|
+
if (raw.includes('@')) return raw;
|
|
437
|
+
const digits = raw.replace(/[^\d]/g, '');
|
|
438
|
+
if (digits.length === 10) return `+1${digits}`;
|
|
439
|
+
if (digits.length === 11 && digits.startsWith('1')) return `+${digits}`;
|
|
440
|
+
return raw;
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
function normalizeContactLabel(value) {
|
|
444
|
+
return String(value || '')
|
|
445
|
+
.replace(/^_\$!<|>!\$_$/g, '')
|
|
446
|
+
.replace(/^\$!<|>!\$$/g, '')
|
|
447
|
+
.trim() || 'other';
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
function imessageLookupCachePath() {
|
|
451
|
+
return path.join(os.homedir(), '.atris', 'cache', 'imessage-contacts.json');
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
function readImessageLookupCache() {
|
|
455
|
+
const cachePath = imessageLookupCachePath();
|
|
456
|
+
try {
|
|
457
|
+
return JSON.parse(fs.readFileSync(cachePath, 'utf8'));
|
|
458
|
+
} catch {
|
|
459
|
+
return { version: 1, entries: {} };
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
function writeImessageLookupCache(cache) {
|
|
464
|
+
const cachePath = imessageLookupCachePath();
|
|
465
|
+
fs.mkdirSync(path.dirname(cachePath), { recursive: true });
|
|
466
|
+
fs.writeFileSync(cachePath, JSON.stringify(cache, null, 2), 'utf8');
|
|
467
|
+
return cachePath;
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
function sleepMs(ms) {
|
|
471
|
+
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms);
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
function readLatestOutgoingImessage(handle, sinceMs) {
|
|
475
|
+
const chatDb = path.join(os.homedir(), 'Library', 'Messages', 'chat.db');
|
|
476
|
+
const sinceUnix = Math.max(0, Math.floor(Number(sinceMs || Date.now()) / 1000) - 5);
|
|
477
|
+
const sql = `
|
|
478
|
+
SELECT m.rowid,
|
|
479
|
+
datetime(m.date/1000000000 + 978307200, 'unixepoch', 'localtime') AS ts,
|
|
480
|
+
m.is_sent,
|
|
481
|
+
m.is_delivered,
|
|
482
|
+
m.is_finished,
|
|
483
|
+
COALESCE(m.error, 0) AS error,
|
|
484
|
+
length(COALESCE(m.text,'')) AS text_len
|
|
485
|
+
FROM message m
|
|
486
|
+
JOIN handle h ON h.rowid = m.handle_id
|
|
487
|
+
WHERE h.id = '${escapeSqlString(handle)}'
|
|
488
|
+
AND m.is_from_me = 1
|
|
489
|
+
AND (m.date/1000000000 + 978307200) >= ${sinceUnix}
|
|
490
|
+
ORDER BY m.date DESC
|
|
491
|
+
LIMIT 1;
|
|
492
|
+
`;
|
|
493
|
+
const result = spawnSync('sqlite3', ['-readonly', chatDb, sql], { encoding: 'utf8' });
|
|
494
|
+
if (result.status !== 0) {
|
|
495
|
+
return {
|
|
496
|
+
matched: false,
|
|
497
|
+
error: (result.stderr || 'Failed to verify latest outgoing iMessage.').trim(),
|
|
498
|
+
};
|
|
499
|
+
}
|
|
500
|
+
const row = String(result.stdout || '').trim();
|
|
501
|
+
if (!row) {
|
|
502
|
+
return {
|
|
503
|
+
matched: false,
|
|
504
|
+
error: 'No outgoing Messages row found after send.',
|
|
505
|
+
};
|
|
506
|
+
}
|
|
507
|
+
const [rowid, ts, isSent, isDelivered, isFinished, messageError, textLen] = row.split('|');
|
|
508
|
+
return {
|
|
509
|
+
matched: true,
|
|
510
|
+
rowid,
|
|
511
|
+
timestamp: ts,
|
|
512
|
+
is_sent: Number(isSent) === 1,
|
|
513
|
+
is_delivered: Number(isDelivered) === 1,
|
|
514
|
+
is_finished: Number(isFinished) === 1,
|
|
515
|
+
message_error: Number(messageError) || 0,
|
|
516
|
+
text_readable: Number(textLen) > 0,
|
|
517
|
+
};
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
function imessageVerifyLatestOutgoing(handle, sinceMs, options = {}) {
|
|
521
|
+
const timeoutMs = Math.max(0, Number(options.timeoutMs || 2000));
|
|
522
|
+
const intervalMs = Math.max(25, Number(options.intervalMs || 150));
|
|
523
|
+
const startedAt = Date.now();
|
|
524
|
+
let latest = readLatestOutgoingImessage(handle, sinceMs);
|
|
525
|
+
while (
|
|
526
|
+
latest.matched
|
|
527
|
+
&& latest.message_error === 0
|
|
528
|
+
&& !(latest.is_sent || latest.is_delivered || latest.is_finished)
|
|
529
|
+
&& Date.now() - startedAt < timeoutMs
|
|
530
|
+
) {
|
|
531
|
+
sleepMs(intervalMs);
|
|
532
|
+
latest = readLatestOutgoingImessage(handle, sinceMs);
|
|
533
|
+
}
|
|
534
|
+
return {
|
|
535
|
+
...latest,
|
|
536
|
+
settled: Boolean(latest.matched && latest.message_error === 0 && (latest.is_sent || latest.is_delivered || latest.is_finished)),
|
|
537
|
+
waited_ms: Date.now() - startedAt,
|
|
538
|
+
};
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
function parseImessageLookupArgs(args) {
|
|
542
|
+
const options = {
|
|
543
|
+
json: false,
|
|
544
|
+
refresh: false,
|
|
545
|
+
name: '',
|
|
546
|
+
maxAgeMs: 7 * 24 * 60 * 60 * 1000,
|
|
547
|
+
};
|
|
548
|
+
const positional = [];
|
|
549
|
+
for (let i = 0; i < args.length; i += 1) {
|
|
550
|
+
const arg = args[i];
|
|
551
|
+
if (arg === '--json') {
|
|
552
|
+
options.json = true;
|
|
553
|
+
} else if (arg === '--refresh') {
|
|
554
|
+
options.refresh = true;
|
|
555
|
+
} else if (arg === '--name' || arg === '--query') {
|
|
556
|
+
options.name = args[i + 1] || '';
|
|
557
|
+
i += 1;
|
|
558
|
+
} else if (arg === '--max-age-minutes') {
|
|
559
|
+
options.maxAgeMs = Math.max(0, Number(args[i + 1] || 0) * 60 * 1000);
|
|
560
|
+
i += 1;
|
|
561
|
+
} else {
|
|
562
|
+
positional.push(arg);
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
if (!options.name && positional.length) options.name = positional.join(' ');
|
|
566
|
+
options.name = String(options.name || '').trim();
|
|
567
|
+
return options;
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
const CONTACT_LOOKUP_SCRIPT = `
|
|
571
|
+
function run(argv) {
|
|
572
|
+
const query = String(argv[0] || '').trim();
|
|
573
|
+
const Contacts = Application('Contacts');
|
|
574
|
+
const selfAlias = /^(me|myself|self|my number|my phone|my contact)$/i.test(query);
|
|
575
|
+
function safe(fn) {
|
|
576
|
+
try {
|
|
577
|
+
const value = fn();
|
|
578
|
+
return value === null || value === undefined ? '' : String(value);
|
|
579
|
+
} catch (error) {
|
|
580
|
+
return '';
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
function propertyRows(rows) {
|
|
584
|
+
const out = [];
|
|
585
|
+
try {
|
|
586
|
+
const list = rows();
|
|
587
|
+
for (let i = 0; i < list.length; i += 1) {
|
|
588
|
+
const item = list[i];
|
|
589
|
+
out.push({
|
|
590
|
+
label: safe(function () { return item.label(); }),
|
|
591
|
+
value: safe(function () { return item.value(); })
|
|
592
|
+
});
|
|
593
|
+
}
|
|
594
|
+
} catch (error) {}
|
|
595
|
+
return out.filter(function (row) { return row.value; });
|
|
596
|
+
}
|
|
597
|
+
function contactRow(person) {
|
|
598
|
+
return {
|
|
599
|
+
id: safe(function () { return person.id(); }),
|
|
600
|
+
name: safe(function () { return person.name(); }),
|
|
601
|
+
first_name: safe(function () { return person.firstName(); }),
|
|
602
|
+
last_name: safe(function () { return person.lastName(); }),
|
|
603
|
+
organization: safe(function () { return person.organization(); }),
|
|
604
|
+
phones: propertyRows(function () { return person.phones(); }),
|
|
605
|
+
emails: propertyRows(function () { return person.emails(); })
|
|
606
|
+
};
|
|
607
|
+
}
|
|
608
|
+
let people = [];
|
|
609
|
+
if (selfAlias) {
|
|
610
|
+
if (Contacts.myCard.exists()) people = [Contacts.myCard];
|
|
611
|
+
} else {
|
|
612
|
+
people = Contacts.people.whose({ name: { _contains: query } })();
|
|
613
|
+
if (!people.length && query.indexOf(' ') > -1) {
|
|
614
|
+
people = Contacts.people.whose({ name: { _contains: query.split(/\\s+/)[0] } })();
|
|
615
|
+
}
|
|
616
|
+
}
|
|
617
|
+
const rows = [];
|
|
618
|
+
const limit = Math.min(20, people.length);
|
|
619
|
+
for (let i = 0; i < limit; i += 1) rows.push(contactRow(people[i]));
|
|
620
|
+
return JSON.stringify({ ok: true, query: query, self_alias: selfAlias, contacts_count: people.length, matches: rows });
|
|
621
|
+
}
|
|
622
|
+
`;
|
|
623
|
+
|
|
624
|
+
function scoreImessageContact(query, match, selfAlias = false) {
|
|
625
|
+
if (selfAlias) return 100;
|
|
626
|
+
const q = normalizeLookupKey(query);
|
|
627
|
+
const name = normalizeLookupKey(match.name);
|
|
628
|
+
if (!q || !name) return 0;
|
|
629
|
+
if (name === q) return 95;
|
|
630
|
+
if (name.includes(q)) return 80;
|
|
631
|
+
const tokens = q.split(' ').filter(Boolean);
|
|
632
|
+
const nameTokens = new Set(name.split(' ').filter(Boolean));
|
|
633
|
+
if (tokens.length && tokens.every((token) => nameTokens.has(token))) return 75;
|
|
634
|
+
if (tokens.length === 1 && nameTokens.has(tokens[0])) return 65;
|
|
635
|
+
return 40;
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
function shapeImessageLookupPayload(raw, options, cached = false, cachedAt = null) {
|
|
639
|
+
const query = options.name;
|
|
640
|
+
const selfAlias = Boolean(raw.self_alias);
|
|
641
|
+
const matches = (raw.matches || [])
|
|
642
|
+
.map((match) => {
|
|
643
|
+
const phones = (match.phones || [])
|
|
644
|
+
.map((phone) => ({
|
|
645
|
+
label: normalizeContactLabel(phone.label),
|
|
646
|
+
value: String(phone.value || '').trim(),
|
|
647
|
+
handle: normalizeImessageHandle(phone.value),
|
|
648
|
+
}))
|
|
649
|
+
.filter((phone) => phone.handle);
|
|
650
|
+
const emails = (match.emails || [])
|
|
651
|
+
.map((email) => ({
|
|
652
|
+
label: normalizeContactLabel(email.label),
|
|
653
|
+
value: String(email.value || '').trim(),
|
|
654
|
+
handle: String(email.value || '').trim(),
|
|
655
|
+
}))
|
|
656
|
+
.filter((email) => email.handle);
|
|
657
|
+
const handles = [
|
|
658
|
+
...phones.map((phone) => ({ type: 'phone', label: phone.label, handle: phone.handle })),
|
|
659
|
+
...emails.map((email) => ({ type: 'email', label: email.label, handle: email.handle })),
|
|
660
|
+
];
|
|
661
|
+
return {
|
|
662
|
+
id: match.id || '',
|
|
663
|
+
name: match.name || [match.first_name, match.last_name].filter(Boolean).join(' '),
|
|
664
|
+
phones,
|
|
665
|
+
emails,
|
|
666
|
+
handles,
|
|
667
|
+
primary_handle: handles[0]?.handle || '',
|
|
668
|
+
score: scoreImessageContact(query, match, selfAlias),
|
|
669
|
+
};
|
|
670
|
+
})
|
|
671
|
+
.filter((match) => match.primary_handle)
|
|
672
|
+
.sort((a, b) => b.score - a.score || a.name.localeCompare(b.name));
|
|
673
|
+
const exactMatches = matches.filter((match) => normalizeLookupKey(match.name) === normalizeLookupKey(query));
|
|
674
|
+
const primary = matches.length === 1
|
|
675
|
+
? matches[0]
|
|
676
|
+
: exactMatches.length === 1
|
|
677
|
+
? exactMatches[0]
|
|
678
|
+
: null;
|
|
679
|
+
return {
|
|
680
|
+
ok: true,
|
|
681
|
+
action: 'imessage_lookup',
|
|
682
|
+
provider: 'local_contacts',
|
|
683
|
+
query,
|
|
684
|
+
cached,
|
|
685
|
+
cached_at: cachedAt,
|
|
686
|
+
cache_path: imessageLookupCachePath(),
|
|
687
|
+
match_count: matches.length,
|
|
688
|
+
unique: Boolean(primary),
|
|
689
|
+
ambiguous: matches.length > 1 && !primary,
|
|
690
|
+
primary: primary ? {
|
|
691
|
+
name: primary.name,
|
|
692
|
+
handle: primary.primary_handle,
|
|
693
|
+
handles: primary.handles,
|
|
694
|
+
} : null,
|
|
695
|
+
matches,
|
|
696
|
+
};
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
function printImessageLookupPayload(payload, json = false) {
|
|
700
|
+
if (json) {
|
|
701
|
+
console.log(JSON.stringify(payload, null, 2));
|
|
702
|
+
return;
|
|
703
|
+
}
|
|
704
|
+
if (!payload.ok) {
|
|
705
|
+
console.error(payload.error || 'Contact lookup failed.');
|
|
706
|
+
return;
|
|
707
|
+
}
|
|
708
|
+
if (!payload.matches.length) {
|
|
709
|
+
console.log(`No iMessage contacts found for "${payload.query}".`);
|
|
710
|
+
return;
|
|
711
|
+
}
|
|
712
|
+
if (payload.primary) {
|
|
713
|
+
console.log(`${payload.primary.name}: ${payload.primary.handle}${payload.cached ? ' (cached)' : ''}`);
|
|
714
|
+
return;
|
|
715
|
+
}
|
|
716
|
+
console.log(`Multiple matches for "${payload.query}":`);
|
|
717
|
+
for (const match of payload.matches.slice(0, 8)) {
|
|
718
|
+
console.log(` - ${match.name}: ${match.primary_handle}`);
|
|
719
|
+
}
|
|
720
|
+
}
|
|
721
|
+
|
|
722
|
+
function imessageLookup(args = []) {
|
|
723
|
+
const options = parseImessageLookupArgs(args);
|
|
724
|
+
if (!options.name) {
|
|
725
|
+
printImessageLookupPayload({
|
|
726
|
+
ok: false,
|
|
727
|
+
action: 'imessage_lookup',
|
|
728
|
+
error: 'Usage: atris imessage lookup --name <contact-name> [--json] [--refresh]',
|
|
729
|
+
}, options.json);
|
|
730
|
+
process.exit(1);
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
const key = normalizeLookupKey(options.name);
|
|
734
|
+
const cache = readImessageLookupCache();
|
|
735
|
+
const entry = cache.entries?.[key];
|
|
736
|
+
if (!options.refresh && entry && Date.now() - Number(entry.cached_at || 0) <= options.maxAgeMs) {
|
|
737
|
+
printImessageLookupPayload(shapeImessageLookupPayload(entry.raw, options, true, entry.cached_at), options.json);
|
|
738
|
+
return;
|
|
739
|
+
}
|
|
740
|
+
|
|
741
|
+
const result = spawnSync('osascript', ['-l', 'JavaScript', '-e', CONTACT_LOOKUP_SCRIPT, options.name], {
|
|
742
|
+
encoding: 'utf8',
|
|
743
|
+
timeout: 6000,
|
|
744
|
+
});
|
|
745
|
+
if (result.status !== 0) {
|
|
746
|
+
printImessageLookupPayload({
|
|
747
|
+
ok: false,
|
|
748
|
+
action: 'imessage_lookup',
|
|
749
|
+
query: options.name,
|
|
750
|
+
error: (result.stderr || 'Contacts lookup failed. Grant Contacts automation permission to this terminal or Atris.').trim(),
|
|
751
|
+
}, options.json);
|
|
752
|
+
process.exit(1);
|
|
753
|
+
}
|
|
754
|
+
|
|
755
|
+
let raw;
|
|
756
|
+
try {
|
|
757
|
+
raw = JSON.parse(String(result.stdout || '{}'));
|
|
758
|
+
} catch {
|
|
759
|
+
printImessageLookupPayload({
|
|
760
|
+
ok: false,
|
|
761
|
+
action: 'imessage_lookup',
|
|
762
|
+
query: options.name,
|
|
763
|
+
error: 'Contacts lookup returned invalid JSON.',
|
|
764
|
+
}, options.json);
|
|
765
|
+
process.exit(1);
|
|
766
|
+
}
|
|
767
|
+
|
|
768
|
+
const cachedAt = Date.now();
|
|
769
|
+
const latestCache = readImessageLookupCache();
|
|
770
|
+
latestCache.version = 1;
|
|
771
|
+
latestCache.entries = latestCache.entries || {};
|
|
772
|
+
latestCache.entries[key] = { cached_at: cachedAt, raw };
|
|
773
|
+
writeImessageLookupCache(latestCache);
|
|
774
|
+
printImessageLookupPayload(shapeImessageLookupPayload(raw, options, false, cachedAt), options.json);
|
|
775
|
+
}
|
|
776
|
+
|
|
777
|
+
function parseImessageSendArgs(args) {
|
|
778
|
+
const options = {
|
|
779
|
+
approved: false,
|
|
780
|
+
json: false,
|
|
781
|
+
receipt: false,
|
|
782
|
+
to: '',
|
|
783
|
+
text: '',
|
|
784
|
+
};
|
|
785
|
+
const positional = [];
|
|
786
|
+
for (let i = 0; i < args.length; i += 1) {
|
|
787
|
+
const arg = args[i];
|
|
788
|
+
if (arg === '--approved' || arg === '--confirm-approved') {
|
|
789
|
+
options.approved = true;
|
|
790
|
+
} else if (arg === '--json') {
|
|
791
|
+
options.json = true;
|
|
792
|
+
} else if (arg === '--receipt') {
|
|
793
|
+
options.receipt = true;
|
|
794
|
+
} else if (arg === '--to' || arg === '--handle') {
|
|
795
|
+
options.to = args[i + 1] || '';
|
|
796
|
+
i += 1;
|
|
797
|
+
} else if (arg === '--text' || arg === '--message') {
|
|
798
|
+
options.text = args[i + 1] || '';
|
|
799
|
+
i += 1;
|
|
800
|
+
} else {
|
|
801
|
+
positional.push(arg);
|
|
802
|
+
}
|
|
803
|
+
}
|
|
804
|
+
if (!options.to && positional.length) options.to = positional.shift() || '';
|
|
805
|
+
if (!options.text && positional.length) options.text = positional.join(' ');
|
|
806
|
+
options.to = normalizeImessageHandle(options.to);
|
|
807
|
+
options.text = String(options.text || '').trim();
|
|
808
|
+
return options;
|
|
809
|
+
}
|
|
810
|
+
|
|
811
|
+
function printImessageSendPayload(payload, json = false) {
|
|
812
|
+
if (json) {
|
|
813
|
+
console.log(JSON.stringify(payload, null, 2));
|
|
814
|
+
return;
|
|
815
|
+
}
|
|
816
|
+
if (payload.ok) {
|
|
817
|
+
console.log(`Sent iMessage to ${payload.to}.`);
|
|
818
|
+
if (payload.receipt_path) console.log(`Receipt: ${payload.receipt_path}`);
|
|
819
|
+
} else {
|
|
820
|
+
console.error(payload.error || 'Failed to send iMessage.');
|
|
821
|
+
}
|
|
822
|
+
}
|
|
823
|
+
|
|
824
|
+
function writeImessageSendReceipt(payload) {
|
|
825
|
+
const atrisDir = path.join(process.cwd(), 'atris');
|
|
826
|
+
if (!fs.existsSync(atrisDir)) return '';
|
|
827
|
+
const runsDir = path.join(atrisDir, 'runs');
|
|
828
|
+
fs.mkdirSync(runsDir, { recursive: true });
|
|
829
|
+
const stamp = new Date().toISOString().replace(/[:.]/g, '-');
|
|
830
|
+
const receiptPath = path.join(runsDir, `imessage-send-${stamp}.md`);
|
|
831
|
+
const lines = [
|
|
832
|
+
'# iMessage Send Receipt',
|
|
833
|
+
'',
|
|
834
|
+
`- Sent at: ${payload.sent_at}`,
|
|
835
|
+
`- Provider: ${payload.provider}`,
|
|
836
|
+
`- To: ${payload.to}`,
|
|
837
|
+
`- Text: ${payload.text}`,
|
|
838
|
+
`- Doctor connected: ${payload.doctor?.connected === true}`,
|
|
839
|
+
`- Send exit: ${payload.osascript?.status}`,
|
|
840
|
+
`- DB verified: ${payload.db_verification?.matched === true}`,
|
|
841
|
+
`- DB settled: ${payload.db_verification?.settled === true}`,
|
|
842
|
+
];
|
|
843
|
+
fs.writeFileSync(receiptPath, `${lines.join('\n')}\n`, 'utf8');
|
|
844
|
+
return receiptPath;
|
|
845
|
+
}
|
|
846
|
+
|
|
847
|
+
function imessageSend(args = []) {
|
|
848
|
+
const options = parseImessageSendArgs(args);
|
|
849
|
+
const basePayload = {
|
|
850
|
+
ok: false,
|
|
851
|
+
action: 'imessage_send',
|
|
852
|
+
provider: 'local_imessage',
|
|
853
|
+
to: options.to,
|
|
854
|
+
text: options.text,
|
|
855
|
+
approved: options.approved,
|
|
856
|
+
};
|
|
857
|
+
|
|
858
|
+
if (!options.to || !options.text) {
|
|
859
|
+
printImessageSendPayload({
|
|
860
|
+
...basePayload,
|
|
861
|
+
error: 'Usage: atris imessage send --to <phone-or-email> --text <message> --approved [--json] [--receipt]',
|
|
862
|
+
}, options.json);
|
|
863
|
+
process.exit(1);
|
|
864
|
+
}
|
|
865
|
+
|
|
866
|
+
if (!options.approved) {
|
|
867
|
+
printImessageSendPayload({
|
|
868
|
+
...basePayload,
|
|
869
|
+
error: 'Refusing to send without --approved after the exact recipient and exact text are confirmed.',
|
|
870
|
+
}, options.json);
|
|
871
|
+
process.exit(1);
|
|
872
|
+
}
|
|
873
|
+
|
|
874
|
+
const doctor = imessageDoctor();
|
|
875
|
+
if (!doctor.connected) {
|
|
876
|
+
printImessageSendPayload({
|
|
877
|
+
...basePayload,
|
|
878
|
+
doctor,
|
|
879
|
+
error: 'iMessage is not available on this Mac.',
|
|
880
|
+
}, options.json);
|
|
881
|
+
process.exit(1);
|
|
882
|
+
}
|
|
883
|
+
|
|
884
|
+
const sendStartedAt = Date.now();
|
|
885
|
+
const result = spawnSync('osascript', [
|
|
886
|
+
'-e', 'on run argv',
|
|
887
|
+
'-e', 'set targetHandle to item 1 of argv',
|
|
888
|
+
'-e', 'set messageText to item 2 of argv',
|
|
889
|
+
'-e', 'tell application "Messages"',
|
|
890
|
+
'-e', 'set targetService to 1st service whose service type = iMessage',
|
|
891
|
+
'-e', 'set targetBuddy to buddy targetHandle of targetService',
|
|
892
|
+
'-e', 'send messageText to targetBuddy',
|
|
893
|
+
'-e', 'end tell',
|
|
894
|
+
'-e', 'return targetHandle',
|
|
895
|
+
'-e', 'end run',
|
|
896
|
+
options.to,
|
|
897
|
+
options.text,
|
|
898
|
+
], {
|
|
899
|
+
encoding: 'utf8',
|
|
900
|
+
timeout: 10000,
|
|
901
|
+
});
|
|
902
|
+
|
|
903
|
+
const payload = {
|
|
904
|
+
...basePayload,
|
|
905
|
+
ok: result.status === 0,
|
|
906
|
+
sent_at: new Date().toISOString(),
|
|
907
|
+
doctor: {
|
|
908
|
+
connected: doctor.connected,
|
|
909
|
+
checks: doctor.checks,
|
|
910
|
+
},
|
|
911
|
+
osascript: {
|
|
912
|
+
status: result.status,
|
|
913
|
+
signal: result.signal || null,
|
|
914
|
+
stdout: String(result.stdout || '').trim(),
|
|
915
|
+
stderr: String(result.stderr || '').trim(),
|
|
916
|
+
},
|
|
917
|
+
};
|
|
918
|
+
|
|
919
|
+
if (payload.ok) {
|
|
920
|
+
payload.db_verification = imessageVerifyLatestOutgoing(options.to, sendStartedAt);
|
|
921
|
+
}
|
|
922
|
+
|
|
923
|
+
if (payload.ok && options.receipt) {
|
|
924
|
+
payload.receipt_path = writeImessageSendReceipt(payload);
|
|
925
|
+
}
|
|
926
|
+
|
|
927
|
+
if (!payload.ok) {
|
|
928
|
+
payload.error = payload.osascript.stderr || 'Messages AppleScript send failed.';
|
|
929
|
+
}
|
|
930
|
+
|
|
931
|
+
printImessageSendPayload(payload, options.json);
|
|
932
|
+
if (!payload.ok) process.exit(1);
|
|
933
|
+
}
|
|
934
|
+
|
|
421
935
|
async function imessageCommand(subcommand, ...args) {
|
|
422
936
|
switch (subcommand) {
|
|
423
937
|
case 'doctor': {
|
|
@@ -434,10 +948,20 @@ async function imessageCommand(subcommand, ...args) {
|
|
|
434
948
|
imessageRecent(handle, { limit, json: args.includes('--json') });
|
|
435
949
|
break;
|
|
436
950
|
}
|
|
951
|
+
case 'lookup': {
|
|
952
|
+
imessageLookup(args);
|
|
953
|
+
break;
|
|
954
|
+
}
|
|
955
|
+
case 'send': {
|
|
956
|
+
imessageSend(args);
|
|
957
|
+
break;
|
|
958
|
+
}
|
|
437
959
|
default:
|
|
438
960
|
console.log('iMessage commands:');
|
|
439
961
|
console.log(' atris imessage doctor [--json] - Check local Messages access');
|
|
962
|
+
console.log(' atris imessage lookup --name <name> [--json] [--refresh]');
|
|
440
963
|
console.log(' atris imessage recent <handle> - Read recent local messages');
|
|
964
|
+
console.log(' atris imessage send --to <handle> --text <text> --approved [--json] [--receipt]');
|
|
441
965
|
}
|
|
442
966
|
}
|
|
443
967
|
|
package/commands/learn.js
CHANGED
|
@@ -68,7 +68,7 @@ function showSearch(query) {
|
|
|
68
68
|
}
|
|
69
69
|
|
|
70
70
|
console.log('');
|
|
71
|
-
console.log(` Search: "${query}" — ${results.length} result
|
|
71
|
+
console.log(` Search: "${query}" — ${results.length} ${results.length === 1 ? 'result' : 'results'}`);
|
|
72
72
|
console.log('');
|
|
73
73
|
for (const e of results) {
|
|
74
74
|
const conf = e._effectiveConfidence;
|
|
@@ -316,7 +316,7 @@ function harvestFromJournals() {
|
|
|
316
316
|
}
|
|
317
317
|
|
|
318
318
|
console.log('');
|
|
319
|
-
console.log(` Found ${fresh.length} new note
|
|
319
|
+
console.log(` Found ${fresh.length} new ${fresh.length === 1 ? 'note' : 'notes'} to harvest:`);
|
|
320
320
|
console.log('');
|
|
321
321
|
for (let i = 0; i < fresh.length; i++) {
|
|
322
322
|
const c = fresh[i];
|
|
@@ -347,7 +347,29 @@ function getLearningCount() {
|
|
|
347
347
|
/**
|
|
348
348
|
* Main entry point for `atris learn [subcommand] [args]`
|
|
349
349
|
*/
|
|
350
|
+
function showLearnHelp() {
|
|
351
|
+
console.log('');
|
|
352
|
+
console.log(' Usage: atris learn [command]');
|
|
353
|
+
console.log('');
|
|
354
|
+
console.log(' Commands:');
|
|
355
|
+
console.log(' (none) Show recent learnings');
|
|
356
|
+
console.log(' add Add a learning interactively');
|
|
357
|
+
console.log(' log <json> Add programmatically (for agents)');
|
|
358
|
+
console.log(' search <q> Search learnings by keyword');
|
|
359
|
+
console.log(' harvest Extract learnings from journal Notes');
|
|
360
|
+
console.log(' prune Check for stale/contradictory entries');
|
|
361
|
+
console.log(' stats Show learning statistics');
|
|
362
|
+
console.log(' export Export as markdown');
|
|
363
|
+
console.log(' count Print learning count (for integrations)');
|
|
364
|
+
console.log('');
|
|
365
|
+
}
|
|
366
|
+
|
|
350
367
|
function learnAtris(subcommand, ...args) {
|
|
368
|
+
if (subcommand === 'help' || subcommand === '--help' || subcommand === '-h' || args.includes('--help') || args.includes('-h')) {
|
|
369
|
+
showLearnHelp();
|
|
370
|
+
return;
|
|
371
|
+
}
|
|
372
|
+
|
|
351
373
|
const atrisDir = path.join(process.cwd(), 'atris');
|
|
352
374
|
if (!fs.existsSync(atrisDir)) {
|
|
353
375
|
console.error(' ✗ atris/ folder not found. Run "atris init" first.');
|
|
@@ -384,20 +406,7 @@ function learnAtris(subcommand, ...args) {
|
|
|
384
406
|
harvestFromJournals();
|
|
385
407
|
break;
|
|
386
408
|
default:
|
|
387
|
-
|
|
388
|
-
console.log(' Usage: atris learn [command]');
|
|
389
|
-
console.log('');
|
|
390
|
-
console.log(' Commands:');
|
|
391
|
-
console.log(' (none) Show recent learnings');
|
|
392
|
-
console.log(' add Add a learning interactively');
|
|
393
|
-
console.log(' log <json> Add programmatically (for agents)');
|
|
394
|
-
console.log(' search <q> Search learnings by keyword');
|
|
395
|
-
console.log(' harvest Extract learnings from journal Notes');
|
|
396
|
-
console.log(' prune Check for stale/contradictory entries');
|
|
397
|
-
console.log(' stats Show learning statistics');
|
|
398
|
-
console.log(' export Export as markdown');
|
|
399
|
-
console.log(' count Print learning count (for integrations)');
|
|
400
|
-
console.log('');
|
|
409
|
+
showLearnHelp();
|
|
401
410
|
break;
|
|
402
411
|
}
|
|
403
412
|
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const { writeLesson } = require('./autopilot');
|
|
4
|
+
|
|
5
|
+
function lessonAtris(subcommand, ...args) {
|
|
6
|
+
const atrisDir = path.join(process.cwd(), 'atris');
|
|
7
|
+
if (!fs.existsSync(atrisDir)) {
|
|
8
|
+
console.error(' ✗ atris/ folder not found. Run "atris init" first.');
|
|
9
|
+
process.exit(1);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
if (subcommand !== 'add') {
|
|
13
|
+
console.log('');
|
|
14
|
+
console.log(' Usage: atris lesson add <slug> <pass|fail> "<text>"');
|
|
15
|
+
console.log('');
|
|
16
|
+
process.exit(subcommand ? 1 : 0);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const [slug, status, ...messageParts] = args;
|
|
20
|
+
const explanation = messageParts.join(' ').trim();
|
|
21
|
+
|
|
22
|
+
if (!slug || !/^[a-z0-9-]+$/.test(slug)) {
|
|
23
|
+
console.error(' ✗ slug must be kebab-case');
|
|
24
|
+
process.exit(1);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
if (!['pass', 'fail'].includes(status)) {
|
|
28
|
+
console.error(' ✗ status must be "pass" or "fail"');
|
|
29
|
+
process.exit(1);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
if (!explanation) {
|
|
33
|
+
console.error(' ✗ explanation is required');
|
|
34
|
+
process.exit(1);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
writeLesson(process.cwd(), slug, status, explanation);
|
|
38
|
+
console.log(`✓ lesson added: ${slug} (${status})`);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
module.exports = lessonAtris;
|
package/commands/lifecycle.js
CHANGED
|
@@ -29,7 +29,7 @@ function resolveSlug() {
|
|
|
29
29
|
async function sleepAtris() {
|
|
30
30
|
const slug = resolveSlug();
|
|
31
31
|
|
|
32
|
-
if (!slug || slug === '--help') {
|
|
32
|
+
if (!slug || slug === '--help' || slug === '-h' || slug === 'help') {
|
|
33
33
|
console.log('Usage: atris sleep [business]');
|
|
34
34
|
console.log('');
|
|
35
35
|
console.log(' Pause a workspace to save compute. Storage only.');
|
|
@@ -60,7 +60,7 @@ async function sleepAtris() {
|
|
|
60
60
|
async function wakeAtris() {
|
|
61
61
|
const slug = resolveSlug();
|
|
62
62
|
|
|
63
|
-
if (!slug || slug === '--help') {
|
|
63
|
+
if (!slug || slug === '--help' || slug === '-h' || slug === 'help') {
|
|
64
64
|
console.log('Usage: atris wake [business]');
|
|
65
65
|
console.log('');
|
|
66
66
|
console.log(' Wake a sleeping workspace. Agents resume automatically.');
|