atris 3.34.0 → 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 (53) 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 +4 -2
  6. package/ax +475 -17
  7. package/bin/atris.js +197 -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/member.js +398 -42
  24. package/commands/mission.js +554 -64
  25. package/commands/now.js +25 -1
  26. package/commands/radar.js +259 -14
  27. package/commands/serve.js +54 -0
  28. package/commands/status.js +50 -5
  29. package/commands/stripe.js +38 -0
  30. package/commands/supabase.js +39 -0
  31. package/commands/task.js +935 -71
  32. package/commands/truth.js +29 -3
  33. package/commands/unknowns.js +627 -0
  34. package/commands/update.js +44 -0
  35. package/commands/vercel.js +38 -0
  36. package/commands/worktree.js +68 -10
  37. package/commands/write.js +399 -0
  38. package/lib/auto-accept-certified.js +70 -19
  39. package/lib/autoland.js +39 -3
  40. package/lib/fleet.js +250 -9
  41. package/lib/memory-view.js +14 -5
  42. package/lib/mission-runtime-loop.js +7 -0
  43. package/lib/official-cli-integration.js +174 -0
  44. package/lib/outbound-send-gate.js +165 -0
  45. package/lib/permission-grants.js +293 -0
  46. package/lib/review-integrity.js +147 -0
  47. package/lib/runner-command.js +23 -0
  48. package/lib/task-db.js +220 -7
  49. package/lib/task-proof.js +20 -0
  50. package/lib/task-receipt.js +93 -0
  51. package/package.json +1 -1
  52. package/utils/update-check.js +27 -6
  53. 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');
@@ -606,6 +609,11 @@ function showHelp() {
606
609
  console.log(' accounts - Manage accounts (list, add, remove)');
607
610
  console.log('');
608
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)');
609
617
  console.log(' gmail - Email commands (inbox, read, archive)');
610
618
  console.log(' calendar - Calendar commands (today, week)');
611
619
  console.log(' twitter - Twitter commands (post)');
@@ -766,7 +774,8 @@ function showVerifyHelp() {
766
774
 
767
775
  function showUpdateHelp(commandName = 'update') {
768
776
  console.log('');
769
- 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}`);
770
779
  console.log('');
771
780
  console.log('Description:');
772
781
  console.log(' Sync Atris workspace files from the installed CLI templates.');
@@ -775,6 +784,9 @@ function showUpdateHelp(commandName = 'update') {
775
784
  console.log(' --all Update Atris files across projects under the current tree.');
776
785
  console.log(' --dry-run Preview update work without writing files.');
777
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
+ }
778
790
  console.log(' --help, -h Show this help.');
779
791
  console.log('');
780
792
  }
@@ -974,9 +986,9 @@ if (command === '2' && ['fast', 'pro'].includes(String(firstCommandArg || '').to
974
986
  const knownCommands = ['init', 'log', 'now', 'radar', 'ctop', 'launchpad', 'status', 'analytics', 'visualize', 'brain', 'brainstorm', 'autopilot', 'run', '_start', 'plan', 'do', 'review', 'release',
975
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',
976
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',
977
- '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',
978
- 'gmail', 'calendar', 'twitter', 'slack', 'imessage', 'integrations', 'setup', 'clean-workspace', 'cw',
979
- 'fork', 'browse', 'publish', 'sleep', 'wake', 'feedback', 'errors', 'wiki', 'code-review', 'cr', 'soul', 'fleet', 'loops', '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'];
980
992
 
981
993
  // Check if command is an atris.md spec file - triggers welcome visualization
982
994
  function isSpecFile(cmd) {
@@ -1086,6 +1098,19 @@ function printAtrisOverview() {
1086
1098
  console.log('');
1087
1099
  }
1088
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
+
1089
1114
  async function interactiveEntry(userInput) {
1090
1115
  const workspaceDir = process.cwd();
1091
1116
  const state = detectWorkspaceState(workspaceDir);
@@ -1174,33 +1199,62 @@ async function interactiveEntry(userInput) {
1174
1199
  backlogCount,
1175
1200
  inboxCount,
1176
1201
  })) {
1177
- const answer = String(userInput || '').trim() || await askContextGatherer(workspaceDir);
1178
- if (isAtrisMetaQuestion(answer)) {
1179
- printAtrisOverview();
1180
- return;
1181
- }
1182
- 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);
1183
1211
  console.log('');
1184
- console.log('No problem. When you are ready, answer in normal words.');
1185
- 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);
1186
1224
  return;
1187
1225
  }
1188
- const profile = saveContextProfile(workspaceDir, answer, { source: userInput ? 'hot_start' : 'cold_start' });
1189
- const starter = createStarterTask(workspaceDir, answer);
1190
- console.log('');
1191
- console.log('Got it. I saved your first direction.');
1192
- console.log(`Focus: ${profile.first_answer}`);
1193
- if (starter && starter.display_id) {
1194
- console.log(`First task: ${starter.display_id} — ${starter.title}`);
1195
- } else if (starter && starter.title) {
1196
- console.log(`First task: ${starter.title}`);
1197
- }
1198
- if (mapStatus !== 'ready') {
1199
- 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);
1200
1256
  return;
1201
1257
  }
1202
- await planCmd(answer);
1203
- return;
1204
1258
  }
1205
1259
 
1206
1260
  if (mapStatus !== 'ready') {
@@ -1436,6 +1490,44 @@ function showWelcomeVisualization() {
1436
1490
  const landText = `${landInfo.branches} in the air, ${landInfo.due} overdue`;
1437
1491
  console.log(` │ 🛬 Land: ${landText.padEnd(26)}│`);
1438
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
+ }
1439
1531
  console.log(` │ 📝 Journal: ${(journalEntries + ' entries today').padEnd(26)}│`);
1440
1532
  if (endgameState.slug !== 'unset' && endgameState.horizon) {
1441
1533
  const endgameLine = endgameState.slug + ' — ' + endgameState.horizon;
@@ -1550,6 +1642,11 @@ if (command === 'init') {
1550
1642
  Promise.resolve(require('../commands/land').landCommand(process.argv.slice(3)))
1551
1643
  .then((code) => process.exit(code || 0))
1552
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); });
1553
1650
  } else if (command === 'radar' || command === 'ctop') {
1554
1651
  const radarArgs = command === 'ctop' ? ['--agents', ...process.argv.slice(3)] : process.argv.slice(3);
1555
1652
  Promise.resolve(require('../commands/radar').radarCommand(radarArgs))
@@ -1643,6 +1740,11 @@ if (command === 'init') {
1643
1740
  showUpdateHelp(command);
1644
1741
  process.exit(0);
1645
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
+ }
1646
1748
  if (isBusinessSync) {
1647
1749
  Promise.resolve(require('../commands/business-sync').businessSync(process.argv.slice(3)))
1648
1750
  .then(() => process.exit(0))
@@ -2126,6 +2228,21 @@ if (command === 'init') {
2126
2228
  require('../commands/gm').gmCommand(...process.argv.slice(3))
2127
2229
  .then(() => process.exit(0))
2128
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);
2129
2246
  } else if (command === 'gmail') {
2130
2247
  const { gmailCommand } = require('../commands/integrations');
2131
2248
  const subcommand = process.argv[3];
@@ -2326,10 +2443,13 @@ if (command === 'init') {
2326
2443
  } else if (command === 'engine' || command === 'engines') {
2327
2444
  // Engine: bring any intelligence — roster of installed coding CLIs, default
2328
2445
  // engine per workspace, --engine <name> rides any loop for one run.
2329
- try {
2330
- const engineArgs = command === 'engines' && process.argv.length <= 3 ? [] : process.argv.slice(3);
2331
- process.exit(require('../commands/engine').engineCommand(engineArgs) || 0);
2332
- } 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); });
2333
2453
  } else if (command === 'sign') {
2334
2454
  // Sign: prepare-commit-msg hook that credits Atris as co-author on commits in atris workspaces.
2335
2455
  try { process.exit(require('../commands/sign').signCommand(process.argv[3]) || 0); }
@@ -2339,6 +2459,11 @@ if (command === 'init') {
2339
2459
  Promise.resolve(require('../commands/slop').slopCommand(process.argv.slice(3)))
2340
2460
  .then((code) => process.exit(typeof code === 'number' ? code : 0))
2341
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); });
2342
2467
  } else if (command === 'strings') {
2343
2468
  // Strings: content design system from live code (no LLM). Extracts UI strings, flags variants,
2344
2469
  // enforces preferred terms at the gate. Exit 1 = variants/banned terms found, for CI + the gate.
@@ -2361,11 +2486,27 @@ if (command === 'init') {
2361
2486
  Promise.resolve(require('../commands/moves').movesCommand(process.argv.slice(3)))
2362
2487
  .then((code) => process.exit(typeof code === 'number' ? code : 0))
2363
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; });
2364
2500
  } else if (command === 'clarity') {
2365
2501
  // Clarity: interview yourself once; agents read how you work so you stop repeating it.
2366
2502
  Promise.resolve(require('../commands/clarity').clarityCommand(process.argv.slice(3)))
2367
2503
  .then((code) => process.exit(typeof code === 'number' ? code : 0))
2368
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); });
2369
2510
  } else if (command === 'deck') {
2370
2511
  // Deck: premium Google Slides from a plain content spec, via the Atris deck engine (anti-slop design system).
2371
2512
  Promise.resolve(require('../commands/deck').run(process.argv.slice(3)))
@@ -2734,8 +2875,19 @@ async function agentAtris() {
2734
2875
  async function chatAtris() {
2735
2876
  // Get message from command line args; --agent forces the legacy cloud-agent lane
2736
2877
  const rawArgs = process.argv.slice(3);
2737
- const agentLane = rawArgs.includes('--agent');
2738
- 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();
2739
2891
 
2740
2892
  // Respect -h / --help before any auth/state checks
2741
2893
  if (message === '-h' || message === '--help' || message === 'help') {
@@ -2746,6 +2898,7 @@ async function chatAtris() {
2746
2898
  console.log('');
2747
2899
  console.log(' atris chat Interactive chat (ax --fast --chat)');
2748
2900
  console.log(' atris chat "what now?" One-shot message (ax --fast)');
2901
+ console.log(' atris chat --print "..." Headless JSON result (ax --fast --print)');
2749
2902
  console.log(' atris chat --agent [...] Legacy cloud-agent lane (needs `atris agent`)');
2750
2903
  process.exit(0);
2751
2904
  }
@@ -2757,7 +2910,7 @@ async function chatAtris() {
2757
2910
  process.exit(1);
2758
2911
  }
2759
2912
 
2760
- const missionIntent = missionRunIntentFromFastMessage(message);
2913
+ const missionIntent = fastLaneFlags.length ? null : missionRunIntentFromFastMessage(message);
2761
2914
  if (missionIntent) {
2762
2915
  process.exit(await runLocalFastMission(missionIntent));
2763
2916
  }
@@ -2770,7 +2923,7 @@ async function chatAtris() {
2770
2923
  if (!agentLane) {
2771
2924
  try {
2772
2925
  const axPath = path.join(__dirname, '..', 'ax');
2773
- const axArgs = message ? ['--fast', message] : ['--fast', '--chat'];
2926
+ const axArgs = message || fastLaneFlags.length ? ['--fast', ...fastLaneFlags, message].filter(Boolean) : ['--fast', '--chat'];
2774
2927
  const run = spawnSync(process.execPath, [axPath, ...axArgs], { stdio: 'inherit' });
2775
2928
  process.exit(run.status || 0);
2776
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);