atris 3.33.3 → 3.35.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 (54) hide show
  1. package/AGENTS.md +0 -2
  2. package/FOR_AGENTS.md +5 -3
  3. package/atris/skills/engines/SKILL.md +16 -7
  4. package/atris/skills/render-cli/SKILL.md +88 -0
  5. package/atris.md +19 -0
  6. package/ax +475 -17
  7. package/bin/atris.js +206 -44
  8. package/commands/aeo.js +52 -0
  9. package/commands/autoland.js +313 -19
  10. package/commands/business.js +91 -8
  11. package/commands/codex-goal.js +26 -2
  12. package/commands/drive.js +187 -0
  13. package/commands/engine.js +73 -2
  14. package/commands/feed.js +202 -0
  15. package/commands/github.js +38 -0
  16. package/commands/gm.js +262 -3
  17. package/commands/init.js +13 -1
  18. package/commands/integrations.js +39 -11
  19. package/commands/interview.js +143 -0
  20. package/commands/land.js +114 -13
  21. package/commands/lesson.js +112 -1
  22. package/commands/linear.js +38 -0
  23. package/commands/loops.js +212 -0
  24. package/commands/member.js +398 -42
  25. package/commands/mission.js +598 -64
  26. package/commands/now.js +25 -1
  27. package/commands/radar.js +259 -14
  28. package/commands/serve.js +54 -0
  29. package/commands/status.js +50 -5
  30. package/commands/stripe.js +38 -0
  31. package/commands/supabase.js +39 -0
  32. package/commands/task.js +935 -71
  33. package/commands/truth.js +29 -3
  34. package/commands/unknowns.js +627 -0
  35. package/commands/update.js +44 -0
  36. package/commands/vercel.js +38 -0
  37. package/commands/worktree.js +68 -10
  38. package/commands/write.js +399 -0
  39. package/lib/auto-accept-certified.js +70 -19
  40. package/lib/autoland.js +39 -3
  41. package/lib/fleet.js +256 -13
  42. package/lib/memory-view.js +14 -5
  43. package/lib/mission-runtime-loop.js +7 -0
  44. package/lib/official-cli-integration.js +174 -0
  45. package/lib/outbound-send-gate.js +165 -0
  46. package/lib/permission-grants.js +293 -0
  47. package/lib/review-integrity.js +147 -0
  48. package/lib/runner-command.js +23 -0
  49. package/lib/task-db.js +220 -7
  50. package/lib/task-proof.js +20 -0
  51. package/lib/task-receipt.js +93 -0
  52. package/package.json +1 -1
  53. package/utils/update-check.js +27 -6
  54. package/atris/learnings.jsonl +0 -1
package/bin/atris.js CHANGED
@@ -29,7 +29,6 @@ try {
29
29
  const {
30
30
  checkForUpdates,
31
31
  showUpdateNotification,
32
- autoUpdate,
33
32
  inspectInstallGitState,
34
33
  formatInstallGitWarning,
35
34
  } = require('../utils/update-check');
@@ -91,14 +90,10 @@ const skipUpdateCheck = Boolean(process.env.ATRIS_SKIP_UPDATE_CHECK || process.e
91
90
  if (!skipUpdateCheck && (!updateCommand || (updateCommand && !['version', 'update'].includes(updateCommand)))) {
92
91
  updateCheckPromise = checkForUpdates()
93
92
  .then((updateInfo) => {
94
- // Show notification if update available (after command completes)
95
93
  if (updateInfo) {
96
- const autoUpdateStarted = autoUpdate(updateInfo, {
94
+ showUpdateNotification(updateInfo, {
97
95
  packageRoot: path.join(__dirname, '..'),
98
96
  });
99
- if (!autoUpdateStarted) {
100
- showUpdateNotification(updateInfo);
101
- }
102
97
  }
103
98
  return updateInfo;
104
99
  })
@@ -469,8 +464,8 @@ function showHelp() {
469
464
  console.log(' 3. Atris acts with context, memory, tools, and a review loop');
470
465
  console.log('');
471
466
  console.log('Common invocations:');
472
- console.log(' atris init Global install: initialize this project');
473
- console.log(' npx atris init Local install: initialize this project');
467
+ console.log(' atris init [--yes] Global install: initialize this project');
468
+ console.log(' npx atris init [--yes] Local install: initialize this project');
474
469
  console.log(' atris computer');
475
470
  console.log(' atris business init "My Company"');
476
471
  console.log(' atris run');
@@ -487,7 +482,7 @@ function showHelp() {
487
482
  console.log('');
488
483
  console.log('Setup:');
489
484
  console.log(' setup - Guided first-time setup (login, pick business, pull)');
490
- console.log(' init - Initialize Atris in current project');
485
+ console.log(' init - Initialize Atris in current project (--yes skips prompts)');
491
486
  console.log(' update - Update local files to latest version');
492
487
  console.log(' upgrade - Install latest Atris from npm');
493
488
  console.log('');
@@ -517,6 +512,12 @@ function showHelp() {
517
512
  console.log(' harvest - Find bugs and next actions from receipts, run logs, and thinking');
518
513
  console.log(' verify - Validate work is done (tests, MAP.md, changes)');
519
514
  console.log(' task - Local agent task plane (atomic claims, TODO import)');
515
+ console.log(' golden path (zero human turns):');
516
+ console.log(' atris task delegate "fix the login bug" --to <member>');
517
+ console.log(' atris task claim <id> --as <member>');
518
+ console.log(' ... build ...');
519
+ console.log(' atris task ready <id> --verify');
520
+ console.log(' atris autoland tick # second check runs, task lands');
520
521
  console.log(' mission - Goal + loop + member owner + verifier + receipt');
521
522
  console.log(' release - Tag release, bump version, create GitHub release, draft /launch');
522
523
  console.log(' learn - Project learnings (patterns, pitfalls, preferences)');
@@ -526,6 +527,7 @@ function showHelp() {
526
527
  console.log(' query - Local-first wiki query against atris/wiki/');
527
528
  console.log(' lint - Local-first wiki lint for atris/wiki/');
528
529
  console.log(' loop - Local wiki upkeep loop (stale pages, orphans, next ingest)');
530
+ console.log(' write - Guided writing sessions: you write every word, atris structures + reviews');
529
531
  console.log('');
530
532
  console.log('Optional helpers:');
531
533
  console.log(' brainstorm - Explore ideas conversationally before planning');
@@ -533,8 +535,9 @@ function showHelp() {
533
535
  console.log(' improve - Run one paid RL tick (POST /api/improve, deducts credits)');
534
536
  console.log(' worktree - Isolated Git worktrees plus guarded ship/merge for parallel agents');
535
537
  console.log(' land - The landing: what is actually done vs still in the air; --reap backs up + clears overdue');
538
+ console.log(' drive - One self-driving tick: mission doctor -> auto-fix -> count disengagements');
536
539
  console.log(' autoland - Approve the policy once; certified work lands itself, you keep irreversible calls');
537
- console.log(' engine - Bring any intelligence: roster of installed coding CLIs, default engine, --engine per run, `engine test` preflight');
540
+ console.log(' engine - Bring any intelligence: roster of installed coding CLIs, default engine, --engine per run, `engine test` preflight, `engine dispatch <task-id> --engine <name>` one-command claim/build/verify/ship');
538
541
  console.log(' sign - Co-author trailer on every commit in an atris workspace (on/off/status)');
539
542
  console.log(' visualize - Generate a Slack/deck-ready visual from a prompt');
540
543
  console.log(' youtube - Process YouTube videos with timestamped transcript-first analysis');
@@ -594,6 +597,7 @@ function showHelp() {
594
597
  console.log(' console - Start/attach always-on coding console (tmux daemon)');
595
598
  console.log(' soul - Show, snapshot, or fork workspace identity');
596
599
  console.log(' fleet - Inspect local fleet status');
600
+ console.log(' loops - Background loops board: what runs, what died, start/stop');
597
601
  console.log(' agent - Select cloud agent, spawn worker requests, or run `agent doctor`');
598
602
  console.log(' chat - Chat with Atris 2 Fast in this workspace (--agent for cloud agent; or: atris chat scan)');
599
603
  console.log(' fast - Chat with Atris2 Fast');
@@ -605,6 +609,11 @@ function showHelp() {
605
609
  console.log(' accounts - Manage accounts (list, add, remove)');
606
610
  console.log('');
607
611
  console.log('Integrations:');
612
+ console.log(' github - github cli wrapper (doctor, auth, pr list/create/checks/view)');
613
+ console.log(' vercel - vercel cli wrapper (doctor, auth, deploy/ls/logs/inspect)');
614
+ console.log(' supabase - supabase cli wrapper (doctor, auth, status/db/functions)');
615
+ console.log(' linear - linear cli wrapper (doctor, auth, issue list/create/view/update)');
616
+ console.log(' stripe - stripe cli wrapper (doctor, auth, listen/trigger/products)');
608
617
  console.log(' gmail - Email commands (inbox, read, archive)');
609
618
  console.log(' calendar - Calendar commands (today, week)');
610
619
  console.log(' twitter - Twitter commands (post)');
@@ -765,7 +774,8 @@ function showVerifyHelp() {
765
774
 
766
775
  function showUpdateHelp(commandName = 'update') {
767
776
  console.log('');
768
- console.log(`Usage: atris ${commandName} [--all] [--dry-run] [--force]`);
777
+ const selfFlag = commandName === 'update' ? ' [--self]' : '';
778
+ console.log(`Usage: atris ${commandName} [--all] [--dry-run] [--force]${selfFlag}`);
769
779
  console.log('');
770
780
  console.log('Description:');
771
781
  console.log(' Sync Atris workspace files from the installed CLI templates.');
@@ -774,6 +784,9 @@ function showUpdateHelp(commandName = 'update') {
774
784
  console.log(' --all Update Atris files across projects under the current tree.');
775
785
  console.log(' --dry-run Preview update work without writing files.');
776
786
  console.log(' --force Overwrite existing template files where supported.');
787
+ if (commandName === 'update') {
788
+ console.log(' --self Update the installed atris cli from npm (packaged installs only).');
789
+ }
777
790
  console.log(' --help, -h Show this help.');
778
791
  console.log('');
779
792
  }
@@ -973,9 +986,9 @@ if (command === '2' && ['fast', 'pro'].includes(String(firstCommandArg || '').to
973
986
  const knownCommands = ['init', 'log', 'now', 'radar', 'ctop', 'launchpad', 'status', 'analytics', 'visualize', 'brain', 'brainstorm', 'autopilot', 'run', '_start', 'plan', 'do', 'review', 'release',
974
987
  'activate', '_activate', 'agent', 'chat', 'fast', 'ax', 'console', 'serve', 'login', 'logout', 'whoami', 'switch', 'use', 'accounts', '_resolve', '_profile-email', '_switch-session', 'shell-init', 'update', 'upgrade', 'version', 'help', 'next', 'atris',
975
988
  'clean', 'harvest', 'verify', 'search', 'skill', 'member', 'codex-goal', 'app', 'apps', 'learn', 'lesson', 'plugin', 'experiments', 'receipt', 'proof', 'openclaw', 'pull', 'push', 'live', 'align', 'terminal', 'computer', 'diff', 'business', 'sync', 'youtube',
976
- 'ingest', 'query', 'lint', 'loop', 'pulse', 'task', 'mission', 'probe', 'worktree', 'land', 'autoland', 'aeo', 'slop', 'strings', 'security-review', 'secure', 'deck', 'site', 'theme', 'card', 'reel', 'improve', 'xp', 'play', 'gm', 'x', 'recap', 'signup', 'clarity', 'moves',
977
- 'gmail', 'calendar', 'twitter', 'slack', 'imessage', 'integrations', 'setup', 'clean-workspace', 'cw',
978
- 'fork', 'browse', 'publish', 'sleep', 'wake', 'feedback', 'errors', 'wiki', 'code-review', 'cr', 'soul', 'fleet', 'compile', 'spaceship', 'truth', 'sign', 'engine', 'engines'];
989
+ 'ingest', 'query', 'lint', 'loop', 'pulse', 'task', 'mission', 'probe', 'worktree', 'land', 'autoland', 'drive', 'aeo', 'slop', 'strings', 'write', 'security-review', 'secure', 'deck', 'site', 'theme', 'card', 'reel', 'improve', 'xp', 'play', 'gm', 'x', 'recap', 'signup', 'clarity', 'interview', 'moves', 'unknowns',
990
+ 'github', 'vercel', 'supabase', 'linear', 'stripe', 'gmail', 'calendar', 'twitter', 'slack', 'imessage', 'integrations', 'setup', 'clean-workspace', 'cw',
991
+ 'fork', 'browse', 'publish', 'sleep', 'wake', 'feedback', 'errors', 'wiki', 'code-review', 'cr', 'soul', 'fleet', 'loops', 'compile', 'spaceship', 'truth', 'sign', 'engine', 'engines', 'feed'];
979
992
 
980
993
  // Check if command is an atris.md spec file - triggers welcome visualization
981
994
  function isSpecFile(cmd) {
@@ -1085,6 +1098,19 @@ function printAtrisOverview() {
1085
1098
  console.log('');
1086
1099
  }
1087
1100
 
1101
+ function useInteractiveAtrisUi() {
1102
+ return Boolean(process.stdin.isTTY && process.stdout.isTTY && !process.env.ATRIS_NO_INTERACTIVE);
1103
+ }
1104
+
1105
+ function initNonInteractiveFlag() {
1106
+ const args = process.argv.slice(2);
1107
+ return args.includes('--yes') || args.includes('-y');
1108
+ }
1109
+
1110
+ function shouldSkipContextGatherer() {
1111
+ return !useInteractiveAtrisUi() || initNonInteractiveFlag();
1112
+ }
1113
+
1088
1114
  async function interactiveEntry(userInput) {
1089
1115
  const workspaceDir = process.cwd();
1090
1116
  const state = detectWorkspaceState(workspaceDir);
@@ -1173,33 +1199,62 @@ async function interactiveEntry(userInput) {
1173
1199
  backlogCount,
1174
1200
  inboxCount,
1175
1201
  })) {
1176
- const answer = String(userInput || '').trim() || await askContextGatherer(workspaceDir);
1177
- if (isAtrisMetaQuestion(answer)) {
1178
- printAtrisOverview();
1179
- return;
1180
- }
1181
- if (!answer.trim()) {
1202
+ const hotAnswer = String(userInput || '').trim();
1203
+ if (hotAnswer) {
1204
+ const answer = hotAnswer;
1205
+ if (isAtrisMetaQuestion(answer)) {
1206
+ printAtrisOverview();
1207
+ return;
1208
+ }
1209
+ const profile = saveContextProfile(workspaceDir, answer, { source: 'hot_start' });
1210
+ const starter = createStarterTask(workspaceDir, answer);
1182
1211
  console.log('');
1183
- console.log('No problem. When you are ready, answer in normal words.');
1184
- console.log('Example: "help me organize college applications" or "help me build a small website".');
1212
+ console.log('Got it. I saved your first direction.');
1213
+ console.log(`Focus: ${profile.first_answer}`);
1214
+ if (starter && starter.display_id) {
1215
+ console.log(`First task: ${starter.display_id} — ${starter.title}`);
1216
+ } else if (starter && starter.title) {
1217
+ console.log(`First task: ${starter.title}`);
1218
+ }
1219
+ if (mapStatus !== 'ready') {
1220
+ printMapBootstrap({ userInput: answer, prefix: 'Next setup step' });
1221
+ return;
1222
+ }
1223
+ await planCmd(answer);
1185
1224
  return;
1186
1225
  }
1187
- const profile = saveContextProfile(workspaceDir, answer, { source: userInput ? 'hot_start' : 'cold_start' });
1188
- const starter = createStarterTask(workspaceDir, answer);
1189
- console.log('');
1190
- console.log('Got it. I saved your first direction.');
1191
- console.log(`Focus: ${profile.first_answer}`);
1192
- if (starter && starter.display_id) {
1193
- console.log(`First task: ${starter.display_id} — ${starter.title}`);
1194
- } else if (starter && starter.title) {
1195
- console.log(`First task: ${starter.title}`);
1196
- }
1197
- if (mapStatus !== 'ready') {
1198
- printMapBootstrap({ userInput: answer, prefix: 'Next setup step' });
1226
+ if (shouldSkipContextGatherer()) {
1227
+ console.log('');
1228
+ console.log("context gatherer skipped (non-interactive). run 'atris plan' when you're ready.");
1229
+ } else {
1230
+ const answer = await askContextGatherer(workspaceDir);
1231
+ if (isAtrisMetaQuestion(answer)) {
1232
+ printAtrisOverview();
1233
+ return;
1234
+ }
1235
+ if (!answer.trim()) {
1236
+ console.log('');
1237
+ console.log('No problem. When you are ready, answer in normal words.');
1238
+ console.log('Example: "help me organize college applications" or "help me build a small website".');
1239
+ return;
1240
+ }
1241
+ const profile = saveContextProfile(workspaceDir, answer, { source: 'cold_start' });
1242
+ const starter = createStarterTask(workspaceDir, answer);
1243
+ console.log('');
1244
+ console.log('Got it. I saved your first direction.');
1245
+ console.log(`Focus: ${profile.first_answer}`);
1246
+ if (starter && starter.display_id) {
1247
+ console.log(`First task: ${starter.display_id} — ${starter.title}`);
1248
+ } else if (starter && starter.title) {
1249
+ console.log(`First task: ${starter.title}`);
1250
+ }
1251
+ if (mapStatus !== 'ready') {
1252
+ printMapBootstrap({ userInput: answer, prefix: 'Next setup step' });
1253
+ return;
1254
+ }
1255
+ await planCmd(answer);
1199
1256
  return;
1200
1257
  }
1201
- await planCmd(answer);
1202
- return;
1203
1258
  }
1204
1259
 
1205
1260
  if (mapStatus !== 'ready') {
@@ -1435,6 +1490,44 @@ function showWelcomeVisualization() {
1435
1490
  const landText = `${landInfo.branches} in the air, ${landInfo.due} overdue`;
1436
1491
  console.log(` │ 🛬 Land: ${landText.padEnd(26)}│`);
1437
1492
  }
1493
+ let rotInfo = null;
1494
+ try {
1495
+ const { parseLessons } = require('../lib/memory-view');
1496
+ const resolved = path.resolve(cwd);
1497
+ const parent = path.dirname(resolved);
1498
+ const grandparent = path.dirname(parent);
1499
+ let worktreeDir;
1500
+ if (path.basename(grandparent) === '.agent-worktrees') {
1501
+ worktreeDir = parent;
1502
+ } else {
1503
+ worktreeDir = path.join(path.dirname(resolved), '.agent-worktrees', path.basename(resolved));
1504
+ }
1505
+ let worktrees = 0;
1506
+ if (fs.existsSync(worktreeDir)) {
1507
+ worktrees = fs.readdirSync(worktreeDir, { withFileTypes: true })
1508
+ .filter((entry) => entry.isDirectory()).length;
1509
+ }
1510
+ let lessonsText = '';
1511
+ try {
1512
+ lessonsText = fs.readFileSync(path.join(atrisDir, 'lessons.md'), 'utf8');
1513
+ } catch (err) {
1514
+ lessonsText = '';
1515
+ }
1516
+ // rot = fail lessons nobody has resolved. A `pass` lesson is knowledge
1517
+ // that worked — it has nothing to resolve and counting it guilt-trips
1518
+ // the operator with a number (600+) no one can ever drive to zero.
1519
+ const unresolvedLessons = parseLessons(lessonsText)
1520
+ .filter((lesson) => lesson.status === 'fail' && !lesson.resolved && !/\[resolved\]/i.test(lesson.text)).length;
1521
+ if (worktrees > 0 || unresolvedLessons > 0) {
1522
+ rotInfo = { worktrees, lessons: unresolvedLessons };
1523
+ }
1524
+ } catch (err) {
1525
+ rotInfo = null;
1526
+ }
1527
+ if (rotInfo) {
1528
+ const rotText = `${rotInfo.worktrees} stale worktree${rotInfo.worktrees === 1 ? '' : 's'}, ${rotInfo.lessons} unresolved lesson${rotInfo.lessons === 1 ? '' : 's'}`;
1529
+ console.log(` │ 🧹 rot: ${rotText.padEnd(26)}│`);
1530
+ }
1438
1531
  console.log(` │ 📝 Journal: ${(journalEntries + ' entries today').padEnd(26)}│`);
1439
1532
  if (endgameState.slug !== 'unset' && endgameState.horizon) {
1440
1533
  const endgameLine = endgameState.slug + ' — ' + endgameState.horizon;
@@ -1549,6 +1642,11 @@ if (command === 'init') {
1549
1642
  Promise.resolve(require('../commands/land').landCommand(process.argv.slice(3)))
1550
1643
  .then((code) => process.exit(code || 0))
1551
1644
  .catch((err) => { console.error(`\n✗ Error: ${err.message || err}`); process.exit(1); });
1645
+ } else if (command === 'drive') {
1646
+ // Drive: one self-driving tick — mission doctor -> auto-fix safe findings -> count disengagements.
1647
+ Promise.resolve(require('../commands/drive').driveCommand(process.argv.slice(3)))
1648
+ .then((code) => process.exit(code || 0))
1649
+ .catch((err) => { console.error(`\n✗ Error: ${err.message || err}`); process.exit(1); });
1552
1650
  } else if (command === 'radar' || command === 'ctop') {
1553
1651
  const radarArgs = command === 'ctop' ? ['--agents', ...process.argv.slice(3)] : process.argv.slice(3);
1554
1652
  Promise.resolve(require('../commands/radar').radarCommand(radarArgs))
@@ -1642,6 +1740,11 @@ if (command === 'init') {
1642
1740
  showUpdateHelp(command);
1643
1741
  process.exit(0);
1644
1742
  }
1743
+ if (command === 'update' && args.includes('--self')) {
1744
+ const { updateSelf } = require('../commands/update');
1745
+ const result = updateSelf({ packageRoot: path.join(__dirname, '..') });
1746
+ process.exit(result.ok ? 0 : 1);
1747
+ }
1645
1748
  if (isBusinessSync) {
1646
1749
  Promise.resolve(require('../commands/business-sync').businessSync(process.argv.slice(3)))
1647
1750
  .then(() => process.exit(0))
@@ -2125,6 +2228,21 @@ if (command === 'init') {
2125
2228
  require('../commands/gm').gmCommand(...process.argv.slice(3))
2126
2229
  .then(() => process.exit(0))
2127
2230
  .catch((err) => { console.error(`✗ Error: ${err.message || err}`); process.exit(1); });
2231
+ } else if (command === 'github') {
2232
+ const status = require('../commands/github').githubCommand(process.argv.slice(3));
2233
+ process.exit(status);
2234
+ } else if (command === 'vercel') {
2235
+ const status = require('../commands/vercel').vercelCommand(process.argv.slice(3));
2236
+ process.exit(status);
2237
+ } else if (command === 'supabase') {
2238
+ const status = require('../commands/supabase').supabaseCommand(process.argv.slice(3));
2239
+ process.exit(status);
2240
+ } else if (command === 'linear') {
2241
+ const status = require('../commands/linear').linearCommand(process.argv.slice(3));
2242
+ process.exit(status);
2243
+ } else if (command === 'stripe') {
2244
+ const status = require('../commands/stripe').stripeCommand(process.argv.slice(3));
2245
+ process.exit(status);
2128
2246
  } else if (command === 'gmail') {
2129
2247
  const { gmailCommand } = require('../commands/integrations');
2130
2248
  const subcommand = process.argv[3];
@@ -2267,6 +2385,14 @@ if (command === 'init') {
2267
2385
  require('../commands/fleet').fleet(args)
2268
2386
  .then(() => process.exit(0))
2269
2387
  .catch((err) => { console.error(`\n✗ Error: ${err.message || err}`); process.exit(1); });
2388
+ } else if (command === 'loops') {
2389
+ try {
2390
+ require('../commands/loops').loopsCommand(process.argv[3], ...process.argv.slice(4));
2391
+ process.exit(0);
2392
+ } catch (error) {
2393
+ console.error(`\n✗ Error: ${error.message || error}`);
2394
+ process.exit(1);
2395
+ }
2270
2396
  } else if (command === 'spaceship') {
2271
2397
  const args = process.argv.slice(3);
2272
2398
  require('../commands/spaceship').spaceship(args)
@@ -2317,10 +2443,13 @@ if (command === 'init') {
2317
2443
  } else if (command === 'engine' || command === 'engines') {
2318
2444
  // Engine: bring any intelligence — roster of installed coding CLIs, default
2319
2445
  // engine per workspace, --engine <name> rides any loop for one run.
2320
- try {
2321
- const engineArgs = command === 'engines' && process.argv.length <= 3 ? [] : process.argv.slice(3);
2322
- process.exit(require('../commands/engine').engineCommand(engineArgs) || 0);
2323
- } catch (err) { console.error(`\n✗ Error: ${err.message || err}`); process.exit(1); }
2446
+ // `engine dispatch` runs an async claim -> build -> ship flight, so the
2447
+ // return value may be a promise or a plain number; Promise.resolve handles
2448
+ // both (mirrors the `compile` command dispatch just above).
2449
+ const engineArgs = command === 'engines' && process.argv.length <= 3 ? [] : process.argv.slice(3);
2450
+ Promise.resolve(require('../commands/engine').engineCommand(engineArgs))
2451
+ .then((code) => process.exit(typeof code === 'number' ? code : 0))
2452
+ .catch((err) => { console.error(`\n✗ Error: ${err.message || err}`); process.exit(1); });
2324
2453
  } else if (command === 'sign') {
2325
2454
  // Sign: prepare-commit-msg hook that credits Atris as co-author on commits in atris workspaces.
2326
2455
  try { process.exit(require('../commands/sign').signCommand(process.argv[3]) || 0); }
@@ -2330,6 +2459,11 @@ if (command === 'init') {
2330
2459
  Promise.resolve(require('../commands/slop').slopCommand(process.argv.slice(3)))
2331
2460
  .then((code) => process.exit(typeof code === 'number' ? code : 0))
2332
2461
  .catch((err) => { console.error(`\n✗ Error: ${err.message || err}`); process.exit(1); });
2462
+ } else if (command === 'write') {
2463
+ // Write: guided writing sessions — human types every word, atris structures + reviews (plan-do-review for prose).
2464
+ Promise.resolve(require('../commands/write').writeCommand(process.argv.slice(3)))
2465
+ .then((code) => process.exit(typeof code === 'number' ? code : 0))
2466
+ .catch((err) => { console.error(`\n✗ Error: ${err.message || err}`); process.exit(1); });
2333
2467
  } else if (command === 'strings') {
2334
2468
  // Strings: content design system from live code (no LLM). Extracts UI strings, flags variants,
2335
2469
  // enforces preferred terms at the gate. Exit 1 = variants/banned terms found, for CI + the gate.
@@ -2352,11 +2486,27 @@ if (command === 'init') {
2352
2486
  Promise.resolve(require('../commands/moves').movesCommand(process.argv.slice(3)))
2353
2487
  .then((code) => process.exit(typeof code === 'number' ? code : 0))
2354
2488
  .catch((err) => { console.error(`\n✗ Error: ${err.message || err}`); process.exit(1); });
2489
+ } else if (command === 'unknowns') {
2490
+ // Unknowns: blindspot pass from local territory context into the SQLite ledger.
2491
+ Promise.resolve(require('../commands/unknowns').unknownsCommand(process.argv.slice(3)))
2492
+ .then((code) => process.exit(typeof code === 'number' ? code : 0))
2493
+ .catch((err) => { console.error(`\n✗ Error: ${err.message || err}`); process.exit(1); });
2494
+ } else if (command === 'feed') {
2495
+ // Feed: read and post the business group feed — receipts and state changes only.
2496
+ // Sets exitCode instead of process.exit() so large --json output flushes fully.
2497
+ Promise.resolve(require('../commands/feed').feedCommand(process.argv.slice(3)))
2498
+ .then((code) => { process.exitCode = typeof code === 'number' ? code : 0; })
2499
+ .catch((err) => { console.error(`\n✗ Error: ${err.message || err}`); process.exitCode = 1; });
2355
2500
  } else if (command === 'clarity') {
2356
2501
  // Clarity: interview yourself once; agents read how you work so you stop repeating it.
2357
2502
  Promise.resolve(require('../commands/clarity').clarityCommand(process.argv.slice(3)))
2358
2503
  .then((code) => process.exit(typeof code === 'number' ? code : 0))
2359
2504
  .catch((err) => { console.error(`\n✗ Error: ${err.message || err}`); process.exit(1); });
2505
+ } else if (command === 'interview') {
2506
+ // Interview: the interlinked 1-on-1 — live interview that extracts judgment into a member file.
2507
+ Promise.resolve(require('../commands/interview').interviewCommand(process.argv.slice(3)))
2508
+ .then((code) => process.exit(typeof code === 'number' ? code : 0))
2509
+ .catch((err) => { console.error(`\n✗ Error: ${err.message || err}`); process.exit(1); });
2360
2510
  } else if (command === 'deck') {
2361
2511
  // Deck: premium Google Slides from a plain content spec, via the Atris deck engine (anti-slop design system).
2362
2512
  Promise.resolve(require('../commands/deck').run(process.argv.slice(3)))
@@ -2725,8 +2875,19 @@ async function agentAtris() {
2725
2875
  async function chatAtris() {
2726
2876
  // Get message from command line args; --agent forces the legacy cloud-agent lane
2727
2877
  const rawArgs = process.argv.slice(3);
2728
- const agentLane = rawArgs.includes('--agent');
2729
- const message = rawArgs.filter(arg => arg !== '--agent').join(' ').trim();
2878
+ const fastLaneFlags = [];
2879
+ const messageArgs = [];
2880
+ let agentLane = false;
2881
+ for (const arg of rawArgs) {
2882
+ if (arg === '--agent') {
2883
+ agentLane = true;
2884
+ } else if (arg === '--print' || arg === '--headless') {
2885
+ fastLaneFlags.push(arg);
2886
+ } else {
2887
+ messageArgs.push(arg);
2888
+ }
2889
+ }
2890
+ const message = messageArgs.join(' ').trim();
2730
2891
 
2731
2892
  // Respect -h / --help before any auth/state checks
2732
2893
  if (message === '-h' || message === '--help' || message === 'help') {
@@ -2737,6 +2898,7 @@ async function chatAtris() {
2737
2898
  console.log('');
2738
2899
  console.log(' atris chat Interactive chat (ax --fast --chat)');
2739
2900
  console.log(' atris chat "what now?" One-shot message (ax --fast)');
2901
+ console.log(' atris chat --print "..." Headless JSON result (ax --fast --print)');
2740
2902
  console.log(' atris chat --agent [...] Legacy cloud-agent lane (needs `atris agent`)');
2741
2903
  process.exit(0);
2742
2904
  }
@@ -2748,7 +2910,7 @@ async function chatAtris() {
2748
2910
  process.exit(1);
2749
2911
  }
2750
2912
 
2751
- const missionIntent = missionRunIntentFromFastMessage(message);
2913
+ const missionIntent = fastLaneFlags.length ? null : missionRunIntentFromFastMessage(message);
2752
2914
  if (missionIntent) {
2753
2915
  process.exit(await runLocalFastMission(missionIntent));
2754
2916
  }
@@ -2761,7 +2923,7 @@ async function chatAtris() {
2761
2923
  if (!agentLane) {
2762
2924
  try {
2763
2925
  const axPath = path.join(__dirname, '..', 'ax');
2764
- const axArgs = message ? ['--fast', message] : ['--fast', '--chat'];
2926
+ const axArgs = message || fastLaneFlags.length ? ['--fast', ...fastLaneFlags, message].filter(Boolean) : ['--fast', '--chat'];
2765
2927
  const run = spawnSync(process.execPath, [axPath, ...axArgs], { stdio: 'inherit' });
2766
2928
  process.exit(run.status || 0);
2767
2929
  } catch {
package/commands/aeo.js CHANGED
@@ -157,6 +157,9 @@ function pickSlug(args) {
157
157
 
158
158
  function printHelp() {
159
159
  console.log('Usage:');
160
+ console.log(' Live scan (no login needed):');
161
+ console.log(' atris aeo scan <url> [--vs <competitor-url>] [--json]');
162
+ console.log('');
160
163
  console.log(' Backend / EC2:');
161
164
  console.log(' atris aeo init [--workspace <slug>]');
162
165
  console.log(' atris aeo draft "<topic>" [--workspace <slug>] [--queries q1,q2] [--slug X] [--url URL]');
@@ -178,6 +181,54 @@ function printHelp() {
178
181
  console.log(' atris aeo discover https://atris.ai/aeo --canonical-url https://atris.ai/aeo');
179
182
  }
180
183
 
184
+ const FREE_SCAN_BASE = process.env.ATRIS_API_BASE || 'https://api.atris.ai';
185
+
186
+ async function aeoScan(args) {
187
+ const asJson = args.includes('--json');
188
+ const vs = readArg(args, '--vs');
189
+ const url = args.filter((a) => !a.startsWith('--') && a !== vs)[0];
190
+ if (!url) {
191
+ console.error('Usage: atris aeo scan <url> [--vs <competitor-url>] [--json]');
192
+ process.exit(1);
193
+ }
194
+ const target = /^https?:\/\//i.test(url) ? url : `https://${url}`;
195
+ const qs = new URLSearchParams({ url: target });
196
+ if (vs) qs.set('vs', /^https?:\/\//i.test(vs) ? vs : `https://${vs}`);
197
+ const endpoint = `${FREE_SCAN_BASE}/api/business/aeo/free-scan?${qs}`;
198
+ let data;
199
+ try {
200
+ const res = await fetch(endpoint, { signal: AbortSignal.timeout(60000) });
201
+ if (!res.ok) {
202
+ const body = await res.text().catch(() => '');
203
+ console.error(`Scan failed (${res.status}): ${body.slice(0, 200)}`);
204
+ process.exit(1);
205
+ }
206
+ data = await res.json();
207
+ } catch (e) {
208
+ console.error(`Scan failed: ${e.message}`);
209
+ process.exit(1);
210
+ }
211
+ if (asJson) { console.log(JSON.stringify(data, null, 2)); return; }
212
+ console.log(`\n ${data.name || target}`);
213
+ console.log(` Score: ${data.score}/100 (${data.grade}) — ${data.status}`);
214
+ if (data.gap_statement) console.log(`\n ${data.gap_statement}`);
215
+ const gaps = data.gaps || [];
216
+ if (gaps.length) {
217
+ console.log('\n Gaps:');
218
+ for (const g of gaps) console.log(` ✗ ${g.title || g.key}: ${g.fix || ''}`);
219
+ }
220
+ const comp = data.competitors || [];
221
+ if (comp.length) {
222
+ console.log('\n Head-to-head:');
223
+ for (const c of comp) console.log(` ${pad(String(c.score ?? '?'), 4)} ${c.url || c.name}`);
224
+ }
225
+ if (data.draft_llms_txt) {
226
+ console.log('\n Draft llms.txt included — save it with:');
227
+ console.log(` atris aeo scan ${url} --json | jq -r .draft_llms_txt > llms.txt`);
228
+ }
229
+ console.log('');
230
+ }
231
+
181
232
  async function aeoInit(args) {
182
233
  const slug = pickSlug(args);
183
234
  if (!slug) {
@@ -545,6 +596,7 @@ async function run(args = []) {
545
596
  const sub = args[0];
546
597
  if (!sub || sub === 'help' || sub === '--help' || sub === '-h') return printHelp();
547
598
  const rest = args.slice(1);
599
+ if (sub === 'scan') return aeoScan(rest);
548
600
  if (sub === 'init') return aeoInit(rest);
549
601
  if (sub === 'draft') return aeoDraft(rest);
550
602
  if (sub === 'log') return aeoLog(rest);