atris 3.16.1 → 3.17.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 (58) hide show
  1. package/README.md +32 -7
  2. package/atris/skills/atris/SKILL.md +15 -2
  3. package/atris/skills/atris-feedback/SKILL.md +7 -0
  4. package/atris/skills/design/SKILL.md +29 -2
  5. package/atris/skills/engines/SKILL.md +44 -0
  6. package/atris/skills/flow/SKILL.md +1 -1
  7. package/atris/skills/wake/SKILL.md +37 -0
  8. package/atris/skills/youtube/SKILL.md +13 -39
  9. package/atris/team/validator/MEMBER.md +1 -0
  10. package/atris/wiki/concepts/agent-activation-contract.md +3 -3
  11. package/atris/wiki/concepts/workspace-initialization-contract.md +3 -3
  12. package/atris/wiki/index.md +1 -0
  13. package/atris.md +43 -19
  14. package/bin/atris.js +400 -30
  15. package/commands/agent-spawn.js +480 -0
  16. package/commands/analytics.js +6 -3
  17. package/commands/apps.js +11 -0
  18. package/commands/autopilot.js +42 -18
  19. package/commands/brain.js +74 -7
  20. package/commands/brainstorm.js +9 -58
  21. package/commands/clean.js +1 -4
  22. package/commands/compile.js +9 -4
  23. package/commands/console.js +8 -3
  24. package/commands/deck.js +135 -0
  25. package/commands/init.js +22 -11
  26. package/commands/lesson.js +76 -0
  27. package/commands/member.js +252 -48
  28. package/commands/mission.js +405 -13
  29. package/commands/now.js +4 -2
  30. package/commands/probe.js +105 -27
  31. package/commands/pulse.js +504 -0
  32. package/commands/radar.js +1 -0
  33. package/commands/recap.js +55 -25
  34. package/commands/run.js +615 -22
  35. package/commands/slop.js +173 -0
  36. package/commands/spaceship.js +39 -0
  37. package/commands/sync.js +0 -2
  38. package/commands/task.js +429 -37
  39. package/commands/verify.js +7 -3
  40. package/lib/activity-stream.js +166 -0
  41. package/lib/auto-accept-certified.js +23 -1
  42. package/lib/context-gatherer.js +170 -0
  43. package/lib/escape-regexp.js +13 -0
  44. package/lib/file-ops.js +6 -3
  45. package/lib/journal.js +1 -1
  46. package/lib/lesson-contradiction.js +113 -0
  47. package/lib/policy-lessons.js +3 -2
  48. package/lib/pulse.js +401 -0
  49. package/lib/runner-command.js +156 -0
  50. package/lib/slides-deck.js +236 -0
  51. package/lib/state-detection.js +1 -4
  52. package/lib/task-db.js +101 -4
  53. package/lib/task-proof.js +1 -1
  54. package/lib/todo-fallback.js +2 -1
  55. package/lib/todo-sections.js +33 -0
  56. package/package.json +1 -2
  57. package/utils/api.js +14 -2
  58. package/atris/atrisDev.md +0 -717
@@ -0,0 +1,135 @@
1
+ // atris deck — generate a premium, on-brand Google Slides deck from a plain
2
+ // content spec, using the Atris deck engine (lib/slides-deck.js). The pitch:
3
+ // describe the deck, get the design system for free. No Arial-on-white slop.
4
+ //
5
+ // Usage:
6
+ // atris deck themes list design themes
7
+ // atris deck build <spec.json> [--title T] [--theme terminal|paper] [--update ID]
8
+ // atris deck sample [--theme paper] print a starter spec to stdout
9
+ //
10
+ // A spec is JSON: { theme, brand:{name,accent}, slides:[ {type,...} ] }.
11
+ // Slide types: title, statement, columns, panel, chips, bignumber, close.
12
+
13
+ const fs = require('fs');
14
+ const https = require('https');
15
+ const os = require('os');
16
+ const { buildDeck, THEMES } = require('../lib/slides-deck');
17
+
18
+ const BASE = 'api.atris.ai';
19
+ const PFX = '/api/integrations/google-slides';
20
+
21
+ function token() {
22
+ try { return require(os.homedir() + '/.atris/credentials.json').token; }
23
+ catch { return null; }
24
+ }
25
+ function api(method, path, body, tok) {
26
+ return new Promise((resolve, reject) => {
27
+ const data = body ? JSON.stringify(body) : null;
28
+ const req = https.request({ host: BASE, path: PFX + path, method,
29
+ headers: { Authorization: 'Bearer ' + tok, 'Content-Type': 'application/json',
30
+ ...(data ? { 'Content-Length': Buffer.byteLength(data) } : {}) } },
31
+ (res) => { let b = ''; res.on('data', (c) => (b += c)); res.on('end', () => {
32
+ let j; try { j = JSON.parse(b); } catch { j = b; }
33
+ if (res.statusCode >= 300) reject(new Error('HTTP ' + res.statusCode + ': ' + (typeof j === 'string' ? j : JSON.stringify(j)).slice(0, 600)));
34
+ else resolve(j); }); });
35
+ req.on('error', reject); if (data) req.write(data); req.end();
36
+ });
37
+ }
38
+
39
+ const SAMPLE = {
40
+ theme: 'terminal',
41
+ brand: { name: 'Sentinel', accent: '.' },
42
+ slides: [
43
+ { type: 'title', headline: 'Read your incidents in the **dark.**',
44
+ sub: "On-call shouldn't mean panic. Every alert ranked by blast radius, in one calm view.",
45
+ panel: { header: { title: 'Active incidents', meta: 'updated 12s ago' },
46
+ rows: [
47
+ { title: 'Checkout latency spike', sub: 'api-gateway · us-east-1', value: '42%', valueSub: 'of traffic', sev: 0, active: true },
48
+ { title: 'Stale read replica', sub: 'orders-db · eu-west-2', value: '8%', valueSub: 'of traffic', sev: 1 },
49
+ { title: 'Elevated 4xx on search', sub: 'search-svc · global', value: '1.2%', valueSub: 'of traffic', sev: 2 },
50
+ ], footer: { left: '3 active, 1 worth a page', right: 'View all' } } },
51
+ { type: 'statement', text: "On-call shouldn't mean **panic.**",
52
+ sub: 'So the console is calm by default. One screen, ranked by real impact.' },
53
+ { type: 'columns', heading: 'What makes it calm', columns: [
54
+ { h: 'Ranked by impact', b: 'Severity comes from real blast radius, so the top of the list is the thing to fix.' },
55
+ { h: 'Quiet by default', b: 'One page-worthy signal per incident. The rest stays in the log until you ask.' },
56
+ { h: 'Built for 3am', b: 'High contrast, keyboard-first, and readable before you are fully awake.' } ] },
57
+ { type: 'bignumber', number: '11 min', label: 'median time to first action', sub: 'down from 47 minutes before Sentinel.' },
58
+ { type: 'close', tagline: 'Read your incidents in the dark.',
59
+ buttons: [{ label: 'Open the console', primary: true }, { label: 'Read the docs' }], footer: 'sentinel.sh · 2026' },
60
+ ],
61
+ };
62
+
63
+ async function run(argv) {
64
+ const sub = argv[0];
65
+
66
+ if (sub === 'themes') {
67
+ console.log('\n atris deck themes:\n');
68
+ for (const [name, t] of Object.entries(THEMES)) {
69
+ console.log(` ${name.padEnd(10)} ${t.fonts.display} + ${t.fonts.body} · accent ${t.color.accent} bg ${t.color.bg}`);
70
+ }
71
+ console.log('\n slide types: title, statement, columns, panel, chips, bignumber, close\n');
72
+ return 0;
73
+ }
74
+
75
+ if (sub === 'sample') {
76
+ const theme = flag(argv, '--theme') || 'terminal';
77
+ console.log(JSON.stringify({ ...SAMPLE, theme }, null, 2));
78
+ return 0;
79
+ }
80
+
81
+ if (sub === 'build') {
82
+ const specPath = argv.slice(1).find((a) => !a.startsWith('-'));
83
+ if (!specPath) { console.error(' usage: atris deck build <spec.json> [--title T] [--theme x] [--update ID]'); return 2; }
84
+ let spec;
85
+ try { spec = JSON.parse(fs.readFileSync(specPath, 'utf8')); }
86
+ catch (e) { console.error(` cannot read spec: ${e.message}`); return 2; }
87
+ const themeOverride = flag(argv, '--theme'); if (themeOverride) spec.theme = themeOverride;
88
+ if (!THEMES[spec.theme]) { console.error(` unknown theme "${spec.theme}". try: ${Object.keys(THEMES).join(', ')}`); return 2; }
89
+ const title = flag(argv, '--title') || `${(spec.brand && spec.brand.name) || 'Atris'} deck`;
90
+
91
+ const tok = token();
92
+ if (!tok) { console.error(' no credentials at ~/.atris/credentials.json — run `atris login` and connect Google Drive.'); return 1; }
93
+
94
+ const { requests } = buildDeck(spec);
95
+
96
+ let id, firstSlide;
97
+ const updateId = flag(argv, '--update');
98
+ if (updateId) {
99
+ id = updateId;
100
+ const got = await api('GET', `/presentations/${id}`, null, tok);
101
+ const slides = got.slides || (got.presentation && got.presentation.slides) || [];
102
+ firstSlide = slides[0] && slides[0].objectId;
103
+ } else {
104
+ const pres = await api('POST', '/presentations', { title }, tok);
105
+ id = pres.presentationId || pres.id || (pres.presentation && pres.presentation.presentationId);
106
+ const slides = pres.slides || (pres.presentation && pres.presentation.slides) || [];
107
+ firstSlide = slides[0] && slides[0].objectId;
108
+ }
109
+
110
+ const reqs = firstSlide ? [...requests, { deleteObject: { objectId: firstSlide } }] : requests;
111
+ console.log(` building ${spec.slides.length} slides (${spec.theme}) · ${reqs.length} ops...`);
112
+ await api('POST', `/presentations/${id}/batch-update`, { requests: reqs }, tok);
113
+
114
+ const url = `https://docs.google.com/presentation/d/${id}/edit`;
115
+ console.log(`\n ✓ deck ready: ${url}\n`);
116
+ return 0;
117
+ }
118
+
119
+ console.log(`
120
+ atris deck — premium Google Slides from a plain content spec
121
+
122
+ atris deck sample [--theme paper] > my.json start from a sample spec
123
+ atris deck build my.json [--title "Q3 review"] create the deck, print the URL
124
+ atris deck build my.json --update <id> rebuild into an existing deck
125
+ atris deck themes list design themes
126
+
127
+ Design system is baked in: distinctive fonts, one accent, real data panels,
128
+ and no AI tells (em dashes sanitized, sentence-case labels, no gradient text).
129
+ `);
130
+ return 0;
131
+ }
132
+
133
+ function flag(argv, name) { const i = argv.indexOf(name); return i !== -1 ? argv[i + 1] : null; }
134
+
135
+ module.exports = { run, SAMPLE };
package/commands/init.js CHANGED
@@ -42,6 +42,26 @@ function detectProjectContext(projectRoot = process.cwd()) {
42
42
  }
43
43
  }
44
44
 
45
+ // Python frameworks may be declared in requirements.txt OR pyproject.toml
46
+ // (the modern standard). Read whichever exist so a pyproject-only Django/FastAPI
47
+ // project isn't mislabeled as plain "python".
48
+ const detectPythonFramework = () => {
49
+ try {
50
+ let text = '';
51
+ for (const f of ['requirements.txt', 'pyproject.toml']) {
52
+ const p = path.join(projectRoot, f);
53
+ if (fs.existsSync(p)) text += fs.readFileSync(p, 'utf8') + '\n';
54
+ }
55
+ const lower = text.toLowerCase();
56
+ if (lower.includes('django')) return 'django';
57
+ if (lower.includes('flask')) return 'flask';
58
+ if (lower.includes('fastapi')) return 'fastapi';
59
+ return 'python';
60
+ } catch (e) {
61
+ return 'python';
62
+ }
63
+ };
64
+
45
65
  // Check for framework indicators
46
66
  const frameworks = {
47
67
  'package.json': () => {
@@ -62,17 +82,8 @@ function detectProjectContext(projectRoot = process.cwd()) {
62
82
  return 'nodejs';
63
83
  }
64
84
  },
65
- 'requirements.txt': () => {
66
- try {
67
- const req = fs.readFileSync(path.join(projectRoot, 'requirements.txt'), 'utf8');
68
- if (req.includes('django')) return 'django';
69
- if (req.includes('flask')) return 'flask';
70
- if (req.includes('fastapi')) return 'fastapi';
71
- return 'python';
72
- } catch (e) {
73
- return 'python';
74
- }
75
- },
85
+ 'requirements.txt': detectPythonFramework,
86
+ 'pyproject.toml': detectPythonFramework,
76
87
  'Gemfile': () => {
77
88
  try {
78
89
  const gemfile = fs.readFileSync(path.join(projectRoot, 'Gemfile'), 'utf8');
@@ -1,6 +1,76 @@
1
1
  const fs = require('fs');
2
2
  const path = require('path');
3
3
  const { writeLesson } = require('./autopilot');
4
+ const { detectLessonContradictions } = require('../lib/lesson-contradiction');
5
+ const taskDb = require('../lib/task-db');
6
+
7
+ function sweepLessons(args) {
8
+ const cwd = process.cwd();
9
+ const json = args.includes('--json');
10
+ const dryRun = args.includes('--dry-run');
11
+
12
+ const contradictions = detectLessonContradictions(cwd);
13
+
14
+ if (json) {
15
+ console.log(JSON.stringify({
16
+ ok: true,
17
+ action: 'lesson_sweep',
18
+ dry_run: dryRun,
19
+ contradictions_found: contradictions.length,
20
+ tasks_created: dryRun ? 0 : contradictions.length,
21
+ contradictions: contradictions.map(c => ({
22
+ type: c.type,
23
+ slug: c.slug,
24
+ evidence: c.evidence,
25
+ })),
26
+ }, null, 2));
27
+ return;
28
+ }
29
+
30
+ if (dryRun) {
31
+ console.log(`found ${contradictions.length} lesson contradiction(s):`);
32
+ for (const c of contradictions) {
33
+ console.log(`- [${c.type}] ${c.slug}: ${c.evidence}`);
34
+ }
35
+ console.log('(dry-run: no tasks created)');
36
+ return;
37
+ }
38
+
39
+ // Create idempotent dissolve tasks for each contradiction
40
+ let created = 0;
41
+ const db = taskDb.open();
42
+ try {
43
+ const workspaceRoot = taskDb.workspaceRoot(cwd);
44
+ for (const contradiction of contradictions) {
45
+ const sourceKey = `lesson-contradiction-${contradiction.slug}`;
46
+ const title = `dissolve lesson: ${contradiction.slug} (${contradiction.type})`;
47
+
48
+ const result = db.prepare(
49
+ 'SELECT id FROM tasks WHERE workspace_root = ? AND source_key = ?'
50
+ ).get(workspaceRoot, sourceKey);
51
+
52
+ if (!result) {
53
+ taskDb.addTask(db, {
54
+ title,
55
+ tag: 'lesson-contradiction',
56
+ workspaceRoot,
57
+ sourceKey,
58
+ metadata: {
59
+ lesson_slug: contradiction.slug,
60
+ contradiction_type: contradiction.type,
61
+ evidence: contradiction.evidence,
62
+ },
63
+ status: 'open',
64
+ });
65
+ created++;
66
+ }
67
+ }
68
+ } finally {
69
+ db.close();
70
+ }
71
+
72
+ console.log(`created ${created} idempotent dissolve task(s) from ${contradictions.length} contradiction(s)`);
73
+ }
4
74
 
5
75
  function mineLessons(args) {
6
76
  const { loadHistory, mineProofPolicy, writePolicyLessons, syncLessonsMd } = require('../lib/policy-lessons');
@@ -52,10 +122,16 @@ function lessonAtris(subcommand, ...args) {
52
122
  return;
53
123
  }
54
124
 
125
+ if (subcommand === 'sweep') {
126
+ sweepLessons(args);
127
+ return;
128
+ }
129
+
55
130
  if (subcommand !== 'add') {
56
131
  console.log('');
57
132
  console.log(' Usage: atris lesson add <slug> <pass|fail> "<text>"');
58
133
  console.log(' atris lesson mine [--json] [--dry-run]');
134
+ console.log(' atris lesson sweep [--json] [--dry-run]');
59
135
  console.log('');
60
136
  process.exit(subcommand ? 1 : 0);
61
137
  }