atris 3.16.1 → 3.22.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 (65) 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 +413 -31
  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 +184 -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 +71 -25
  34. package/commands/run.js +615 -22
  35. package/commands/site.js +48 -0
  36. package/commands/slop.js +307 -0
  37. package/commands/spaceship.js +39 -0
  38. package/commands/sync.js +0 -2
  39. package/commands/task.js +429 -37
  40. package/commands/theme.js +217 -0
  41. package/commands/verify.js +7 -3
  42. package/lib/activity-stream.js +166 -0
  43. package/lib/auto-accept-certified.js +23 -1
  44. package/lib/context-gatherer.js +170 -0
  45. package/lib/deck-from-md.js +110 -0
  46. package/lib/escape-regexp.js +13 -0
  47. package/lib/file-ops.js +6 -3
  48. package/lib/html-render.js +257 -0
  49. package/lib/journal.js +1 -1
  50. package/lib/lesson-contradiction.js +113 -0
  51. package/lib/memory-view.js +95 -0
  52. package/lib/policy-lessons.js +3 -2
  53. package/lib/pulse.js +401 -0
  54. package/lib/runner-command.js +156 -0
  55. package/lib/site.js +114 -0
  56. package/lib/slides-deck.js +237 -0
  57. package/lib/state-detection.js +1 -4
  58. package/lib/task-db.js +101 -4
  59. package/lib/task-proof.js +1 -1
  60. package/lib/theme.js +264 -0
  61. package/lib/todo-fallback.js +2 -1
  62. package/lib/todo-sections.js +33 -0
  63. package/package.json +1 -2
  64. package/utils/api.js +14 -2
  65. package/atris/atrisDev.md +0 -717
@@ -0,0 +1,184 @@
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
+ const { parseMarkdownToSpec } = require('../lib/deck-from-md');
18
+ const { mergedThemes } = require('../lib/theme');
19
+
20
+ const BASE = 'api.atris.ai';
21
+ const PFX = '/api/integrations/google-slides';
22
+
23
+ function token() {
24
+ try { return require(os.homedir() + '/.atris/credentials.json').token; }
25
+ catch { return null; }
26
+ }
27
+ function api(method, path, body, tok) {
28
+ return new Promise((resolve, reject) => {
29
+ const data = body ? JSON.stringify(body) : null;
30
+ const req = https.request({ host: BASE, path: PFX + path, method,
31
+ headers: { Authorization: 'Bearer ' + tok, 'Content-Type': 'application/json',
32
+ ...(data ? { 'Content-Length': Buffer.byteLength(data) } : {}) } },
33
+ (res) => { let b = ''; res.on('data', (c) => (b += c)); res.on('end', () => {
34
+ let j; try { j = JSON.parse(b); } catch { j = b; }
35
+ if (res.statusCode >= 300) reject(new Error('HTTP ' + res.statusCode + ': ' + (typeof j === 'string' ? j : JSON.stringify(j)).slice(0, 600)));
36
+ else resolve(j); }); });
37
+ req.on('error', reject); if (data) req.write(data); req.end();
38
+ });
39
+ }
40
+
41
+ const SAMPLE = {
42
+ theme: 'terminal',
43
+ brand: { name: 'Sentinel', accent: '.' },
44
+ slides: [
45
+ { type: 'title', headline: 'Read your incidents in the **dark.**',
46
+ sub: "On-call shouldn't mean panic. Every alert ranked by blast radius, in one calm view.",
47
+ panel: { header: { title: 'Active incidents', meta: 'updated 12s ago' },
48
+ rows: [
49
+ { title: 'Checkout latency spike', sub: 'api-gateway · us-east-1', value: '42%', valueSub: 'of traffic', sev: 0, active: true },
50
+ { title: 'Stale read replica', sub: 'orders-db · eu-west-2', value: '8%', valueSub: 'of traffic', sev: 1 },
51
+ { title: 'Elevated 4xx on search', sub: 'search-svc · global', value: '1.2%', valueSub: 'of traffic', sev: 2 },
52
+ ], footer: { left: '3 active, 1 worth a page', right: 'View all' } } },
53
+ { type: 'statement', text: "On-call shouldn't mean **panic.**",
54
+ sub: 'So the console is calm by default. One screen, ranked by real impact.' },
55
+ { type: 'columns', heading: 'What makes it calm', columns: [
56
+ { h: 'Ranked by impact', b: 'Severity comes from real blast radius, so the top of the list is the thing to fix.' },
57
+ { h: 'Quiet by default', b: 'One page-worthy signal per incident. The rest stays in the log until you ask.' },
58
+ { h: 'Built for 3am', b: 'High contrast, keyboard-first, and readable before you are fully awake.' } ] },
59
+ { type: 'bignumber', number: '11 min', label: 'median time to first action', sub: 'down from 47 minutes before Sentinel.' },
60
+ { type: 'close', tagline: 'Read your incidents in the dark.',
61
+ buttons: [{ label: 'Open the console', primary: true }, { label: 'Read the docs' }], footer: 'sentinel.sh · 2026' },
62
+ ],
63
+ };
64
+
65
+ // shared: spec -> live deck. Returns the URL.
66
+ async function publishDeck(spec, { title, updateId, tok }) {
67
+ const { requests } = buildDeck(spec, { themes: mergedThemes(THEMES) });
68
+ let id, firstSlide;
69
+ if (updateId) {
70
+ id = updateId;
71
+ const got = await api('GET', `/presentations/${id}`, null, tok);
72
+ const slides = got.slides || (got.presentation && got.presentation.slides) || [];
73
+ firstSlide = slides[0] && slides[0].objectId;
74
+ } else {
75
+ const pres = await api('POST', '/presentations', { title }, tok);
76
+ id = pres.presentationId || pres.id || (pres.presentation && pres.presentation.presentationId);
77
+ const slides = pres.slides || (pres.presentation && pres.presentation.slides) || [];
78
+ firstSlide = slides[0] && slides[0].objectId;
79
+ }
80
+ const reqs = firstSlide ? [...requests, { deleteObject: { objectId: firstSlide } }] : requests;
81
+ console.log(` building ${spec.slides.length} slides (${spec.theme}) · ${reqs.length} ops...`);
82
+ await api('POST', `/presentations/${id}/batch-update`, { requests: reqs }, tok);
83
+ return `https://docs.google.com/presentation/d/${id}/edit`;
84
+ }
85
+
86
+ // beautiful HTML output (page or AppBlock JSON) from a content spec
87
+ function outputHtml(spec, argv, srcLabel) {
88
+ const { renderHtml, renderBlock, THEMES: HTML_THEMES } = require('../lib/html-render');
89
+ const themes = mergedThemes(HTML_THEMES);
90
+ if (!themes[spec.theme]) spec.theme = 'atris';
91
+ const title = flag(argv, '--title');
92
+ if (hasFlag(argv, '--block')) {
93
+ console.log(JSON.stringify(renderBlock(spec, { title, themes }), null, 2));
94
+ return 0;
95
+ }
96
+ const html = renderHtml(spec, { title, themes });
97
+ const out = flag(argv, '--out');
98
+ if (out) { fs.writeFileSync(out, html); console.log(`\n ✓ html written: ${out}${srcLabel ? ` (from ${srcLabel})` : ''}\n`); }
99
+ else process.stdout.write(html + '\n');
100
+ return 0;
101
+ }
102
+
103
+ async function run(argv) {
104
+ const sub = argv[0];
105
+
106
+ if (sub === 'from') {
107
+ const docPath = argv.slice(1).find((a) => !a.startsWith('-'));
108
+ if (!docPath) { console.error(' usage: atris deck from <doc.md> [--theme x] [--brand Name] [--build] [--title T]'); return 2; }
109
+ let md;
110
+ try { md = fs.readFileSync(docPath, 'utf8'); }
111
+ catch (e) { console.error(` cannot read doc: ${e.message}`); return 2; }
112
+ const spec = parseMarkdownToSpec(md, { theme: flag(argv, '--theme'), brandName: flag(argv, '--brand') });
113
+ if (hasFlag(argv, '--html') || hasFlag(argv, '--block')) return outputHtml(spec, argv, docPath);
114
+ { const dt = mergedThemes(THEMES); if (!dt[spec.theme]) { console.error(` unknown theme "${spec.theme}". try: ${Object.keys(dt).join(', ')}`); return 2; } }
115
+ if (!hasFlag(argv, '--build')) {
116
+ // default: print the spec so the PM can tweak before building
117
+ console.log(JSON.stringify(spec, null, 2));
118
+ return 0;
119
+ }
120
+ const tok = token();
121
+ if (!tok) { console.error(' no credentials at ~/.atris/credentials.json — run `atris login` and connect Google Drive.'); return 1; }
122
+ const title = flag(argv, '--title') || `${(spec.brand && spec.brand.name) || 'Atris'} deck`;
123
+ const url = await publishDeck(spec, { title, updateId: flag(argv, '--update'), tok });
124
+ console.log(`\n ✓ deck from ${docPath} ready: ${url}\n`);
125
+ return 0;
126
+ }
127
+
128
+ if (sub === 'themes') {
129
+ console.log('\n atris deck themes:\n');
130
+ for (const [name, t] of Object.entries(THEMES)) {
131
+ console.log(` ${name.padEnd(10)} ${t.fonts.display} + ${t.fonts.body} · accent ${t.color.accent} bg ${t.color.bg}`);
132
+ }
133
+ console.log('\n slide types: title, statement, columns, panel, chips, bignumber, close\n');
134
+ return 0;
135
+ }
136
+
137
+ if (sub === 'sample') {
138
+ const theme = flag(argv, '--theme') || 'terminal';
139
+ console.log(JSON.stringify({ ...SAMPLE, theme }, null, 2));
140
+ return 0;
141
+ }
142
+
143
+ if (sub === 'build') {
144
+ const specPath = argv.slice(1).find((a) => !a.startsWith('-'));
145
+ if (!specPath) { console.error(' usage: atris deck build <spec.json> [--title T] [--theme x] [--update ID]'); return 2; }
146
+ let spec;
147
+ try { spec = JSON.parse(fs.readFileSync(specPath, 'utf8')); }
148
+ catch (e) { console.error(` cannot read spec: ${e.message}`); return 2; }
149
+ const themeOverride = flag(argv, '--theme'); if (themeOverride) spec.theme = themeOverride;
150
+ if (hasFlag(argv, '--html') || hasFlag(argv, '--block')) return outputHtml(spec, argv, specPath);
151
+ { const dt = mergedThemes(THEMES); if (!dt[spec.theme]) { console.error(` unknown theme "${spec.theme}". try: ${Object.keys(dt).join(', ')}`); return 2; } }
152
+ const title = flag(argv, '--title') || `${(spec.brand && spec.brand.name) || 'Atris'} deck`;
153
+
154
+ const tok = token();
155
+ if (!tok) { console.error(' no credentials at ~/.atris/credentials.json — run `atris login` and connect Google Drive.'); return 1; }
156
+
157
+ const url = await publishDeck(spec, { title, updateId: flag(argv, '--update'), tok });
158
+ console.log(`\n ✓ deck ready: ${url}\n`);
159
+ return 0;
160
+ }
161
+
162
+ console.log(`
163
+ atris deck — premium Google Slides from a plain content spec or a markdown doc
164
+
165
+ atris deck from doc.md [--build] [--title T] turn a markdown doc into a deck
166
+ atris deck from doc.md --html --out page.html beautiful HTML page (theme: atris|terminal|paper)
167
+ atris deck from doc.md --block emit the AppBlock JSON for a web app
168
+ atris deck sample [--theme paper] > my.json start from a sample spec
169
+ atris deck build my.json [--title "Q3 review"] create the deck, print the URL
170
+ atris deck build my.json --html --out p.html render the spec as HTML instead of slides
171
+ atris deck themes list design themes
172
+
173
+ 'from' maps headings to slides (## with bullets -> columns, "**X** label" -> a
174
+ big number, Close -> a closing slide). Without --build it prints the spec to tweak.
175
+ Design system is baked in: distinctive fonts, one accent, real data panels, and
176
+ no AI tells (em dashes sanitized, sentence-case labels, no gradient text).
177
+ `);
178
+ return 0;
179
+ }
180
+
181
+ function flag(argv, name) { const i = argv.indexOf(name); return i !== -1 ? argv[i + 1] : null; }
182
+ function hasFlag(argv, name) { return argv.includes(name); }
183
+
184
+ module.exports = { run, SAMPLE, publishDeck };
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
  }