atris 3.35.0 → 3.37.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 (133) hide show
  1. package/AGENTS.md +37 -0
  2. package/README.md +5 -3
  3. package/atris/GETTING_STARTED.md +1 -1
  4. package/atris/atris.md +3 -0
  5. package/atris/policies/day-loop-voice.md +102 -0
  6. package/atris/policies/outbound-artifact-gate.md +2 -0
  7. package/atris/skills/design/SKILL.md +56 -32
  8. package/atris/skills/endgame/SKILL.md +12 -6
  9. package/atris/skills/engines/SKILL.md +22 -4
  10. package/atris/skills/fable-method/SKILL.md +66 -0
  11. package/atris/skills/improve/SKILL.md +65 -45
  12. package/atris/skills/youtube/SKILL.md +10 -1
  13. package/atris.md +2 -0
  14. package/ax +147 -19
  15. package/bin/atris.js +576 -267
  16. package/commands/activate.js +194 -88
  17. package/commands/agents.js +166 -0
  18. package/commands/autoland.js +459 -107
  19. package/commands/autopilot-front.js +20 -2
  20. package/commands/autopilot.js +118 -2
  21. package/commands/avail.js +407 -0
  22. package/commands/bench.js +188 -0
  23. package/commands/brain.js +3 -0
  24. package/commands/brief.js +651 -0
  25. package/commands/business-sync.js +192 -6
  26. package/commands/clean.js +50 -24
  27. package/commands/close.js +1083 -0
  28. package/commands/cloud.js +245 -0
  29. package/commands/compile.js +292 -1
  30. package/commands/computer.js +150 -3
  31. package/commands/dream.js +365 -0
  32. package/commands/drill.js +371 -0
  33. package/commands/engine.js +993 -32
  34. package/commands/experiments.js +28 -0
  35. package/commands/feedback.js +34 -12
  36. package/commands/fleet-report.js +206 -0
  37. package/commands/gm.js +23 -0
  38. package/commands/goal.js +247 -0
  39. package/commands/improve.js +642 -26
  40. package/commands/init.js +72 -44
  41. package/commands/interview.js +67 -1
  42. package/commands/land.js +152 -52
  43. package/commands/lifecycle.js +39 -3
  44. package/commands/log.js +84 -1
  45. package/commands/loops.js +220 -16
  46. package/commands/meet.js +220 -0
  47. package/commands/member.js +511 -34
  48. package/commands/mission.js +3144 -338
  49. package/commands/next.js +137 -0
  50. package/commands/now.js +220 -25
  51. package/commands/one-lap.js +776 -0
  52. package/commands/orb.js +314 -0
  53. package/commands/pack-craft.js +179 -0
  54. package/commands/pack.js +823 -0
  55. package/commands/play.js +3 -2
  56. package/commands/probe.js +30 -3
  57. package/commands/pulse.js +241 -46
  58. package/commands/push.js +260 -82
  59. package/commands/rainmaker.js +49 -0
  60. package/commands/report.js +415 -0
  61. package/commands/scout.js +147 -0
  62. package/commands/search.js +363 -0
  63. package/commands/skill.js +47 -3
  64. package/commands/slop.js +50 -2
  65. package/commands/soul.js +1 -1
  66. package/commands/stream.js +861 -0
  67. package/commands/study.js +693 -0
  68. package/commands/sync.js +67 -54
  69. package/commands/task.js +1346 -117
  70. package/commands/team.js +73 -0
  71. package/commands/verify.js +96 -0
  72. package/commands/watch.js +303 -0
  73. package/commands/wish.js +500 -0
  74. package/commands/workflow.js +11 -5
  75. package/commands/worktree.js +234 -13
  76. package/commands/xp.js +29 -11
  77. package/lib/auto-accept-certified.js +331 -34
  78. package/lib/autoland.js +319 -54
  79. package/lib/ax-auto-lane.js +96 -0
  80. package/lib/bench/context.js +147 -0
  81. package/lib/bench/engines.js +141 -0
  82. package/lib/bench/report.js +140 -0
  83. package/lib/bench/runner.js +512 -0
  84. package/lib/brief-ledger.js +350 -0
  85. package/lib/cloud-mission.js +259 -0
  86. package/lib/codex-flight.js +154 -0
  87. package/lib/default-runner.js +45 -0
  88. package/lib/default-verifier.js +70 -0
  89. package/lib/engine-registry.js +232 -0
  90. package/lib/experiments/daily.js +640 -0
  91. package/lib/fleet.js +2219 -67
  92. package/lib/improve-vitals-html.js +171 -0
  93. package/lib/known-commands.js +58 -0
  94. package/lib/loop-doctor.js +416 -0
  95. package/lib/member-switches.js +144 -0
  96. package/lib/mission-room.js +1 -0
  97. package/lib/mission-root.js +52 -0
  98. package/lib/next-moves.js +327 -10
  99. package/lib/one-lap-validator.js +60 -0
  100. package/lib/orb-context.js +477 -0
  101. package/lib/orb-scorecard.js +224 -0
  102. package/lib/policy-lessons.js +52 -1
  103. package/lib/pulse.js +277 -3
  104. package/lib/receipt-block.js +168 -0
  105. package/lib/receipt-evidence.js +65 -4
  106. package/lib/router-brain.js +352 -0
  107. package/lib/runner-command.js +10 -0
  108. package/lib/self-drive.js +258 -0
  109. package/lib/short-name.js +103 -0
  110. package/lib/spawn-env.js +18 -0
  111. package/lib/state-detection.js +56 -1
  112. package/lib/sync-status.js +59 -0
  113. package/lib/task-db.js +108 -29
  114. package/lib/task-proof.js +23 -1
  115. package/lib/team-presence.js +260 -0
  116. package/lib/tool-result-encode.js +7 -0
  117. package/lib/trust-tiers.js +90 -0
  118. package/lib/usage.js +107 -0
  119. package/lib/voice-gate.js +163 -0
  120. package/lib/wish-audit.js +1368 -0
  121. package/lib/wish-delegate.js +1840 -0
  122. package/lib/wish-design.js +110 -0
  123. package/lib/wish-stats.js +183 -0
  124. package/lib/wish-store.js +354 -0
  125. package/lib/zip.js +221 -0
  126. package/package.json +3 -1
  127. package/templates/loops/atris/loops/LOOPS.md +55 -0
  128. package/templates/loops/atris/loops/TICK.md +24 -0
  129. package/templates/loops/atris/loops/feedback.md +22 -0
  130. package/templates/loops/atris/loops/quality.md +22 -0
  131. package/templates/loops/atris/wiki/systems/loops.md +41 -0
  132. package/utils/api.js +5 -1
  133. package/utils/auth.js +57 -21
package/bin/atris.js CHANGED
@@ -14,6 +14,15 @@ const os = require('os');
14
14
  const crypto = require('crypto');
15
15
  const PACKAGE_JSON_PATH = path.join(__dirname, '..', 'package.json');
16
16
 
17
+ // Exit without truncating piped stdout: process.exit() drops whatever is still
18
+ // queued in the pipe buffer (large outputs died at 512-byte boundaries). Let
19
+ // the event loop drain naturally; an unref'd timer forces exit if a stray
20
+ // handle keeps the process alive.
21
+ function exitWhenFlushed(code) {
22
+ process.exitCode = code;
23
+ setTimeout(() => process.exit(code), 3000).unref();
24
+ }
25
+
17
26
  let CLI_VERSION = 'unknown';
18
27
  try {
19
28
  const pkgRaw = fs.readFileSync(PACKAGE_JSON_PATH, 'utf8');
@@ -65,6 +74,8 @@ const {
65
74
  DEFAULT_CLIENT_ID, DEFAULT_USER_AGENT,
66
75
  } = require('../utils/api');
67
76
  const missionRuntime = require('../lib/mission-runtime-loop');
77
+ const { knownCommands, suggestCommand } = require('../lib/known-commands');
78
+ const { recordUsage } = require('../lib/usage');
68
79
 
69
80
  // Bind DI wrappers (utils/auth uses dependency injection for apiRequestJson)
70
81
  const validateAccessToken = (token) => _validateAccessToken(token, apiRequestJson);
@@ -85,7 +96,8 @@ const helpRequested = updateCommand === 'help'
85
96
  || updateArgs.includes('--help')
86
97
  || updateArgs.includes('-h')
87
98
  || updateArgs[0] === 'help';
88
- const jsonRequested = updateArgs.includes('--json');
99
+ const jsonRequested = process.argv.slice(2).includes('--json');
100
+ const dryRunRequested = updateArgs.includes('--dry-run');
89
101
  const skipUpdateCheck = Boolean(process.env.ATRIS_SKIP_UPDATE_CHECK || process.env.NO_UPDATE_NOTIFIER || helpRequested || jsonRequested);
90
102
  if (!skipUpdateCheck && (!updateCommand || (updateCommand && !['version', 'update'].includes(updateCommand)))) {
91
103
  updateCheckPromise = checkForUpdates()
@@ -107,6 +119,12 @@ let command = process.argv[2];
107
119
  const commandArgs = process.argv.slice(3);
108
120
  const firstCommandArg = process.argv[3];
109
121
  const RUNNER_FLAG_NAMES = ['--runner-bin', '--runner-template', '--runner-model', '--runner-profile'];
122
+ const NATURAL_VALUE_FLAGS = [...RUNNER_FLAG_NAMES, '--engine', '--verify'];
123
+ const SINGLE_WORD_NATURAL_INTENTS = new Set([
124
+ 'add', 'analyze', 'build', 'change', 'check', 'create', 'debug', 'document',
125
+ 'edit', 'fix', 'implement', 'inspect', 'investigate', 'make', 'patch',
126
+ 'refactor', 'remove', 'rename', 'research', 'test', 'update', 'validate', 'write',
127
+ ]);
110
128
 
111
129
  function readOptionArg(args, name) {
112
130
  const prefix = `${name}=`;
@@ -121,6 +139,50 @@ function isOptionValue(args, index, optionNames) {
121
139
  return index > 0 && optionNames.includes(args[index - 1]);
122
140
  }
123
141
 
142
+ function parseNaturalEntryArgs(args = []) {
143
+ const positionals = [];
144
+ let asJson = false;
145
+ let engine = '';
146
+ let verifier = '';
147
+ let error = '';
148
+ for (let i = 0; i < args.length; i += 1) {
149
+ const value = String(args[i] || '');
150
+ if (value === '--json') {
151
+ asJson = true;
152
+ continue;
153
+ }
154
+ const inlineFlag = NATURAL_VALUE_FLAGS.find((name) => value.startsWith(`${name}=`));
155
+ if (inlineFlag) {
156
+ const optionValue = value.slice(inlineFlag.length + 1);
157
+ if (!optionValue) error = `${inlineFlag} needs a value`;
158
+ if (inlineFlag === '--engine') engine = optionValue;
159
+ if (inlineFlag === '--verify') verifier = optionValue;
160
+ continue;
161
+ }
162
+ if (NATURAL_VALUE_FLAGS.includes(value)) {
163
+ const optionValue = String(args[i + 1] || '');
164
+ if (!optionValue || optionValue.startsWith('--')) {
165
+ error = `${value} needs a value`;
166
+ } else {
167
+ if (value === '--engine') engine = optionValue;
168
+ if (value === '--verify') verifier = optionValue;
169
+ i += 1;
170
+ }
171
+ continue;
172
+ }
173
+ positionals.push(value);
174
+ }
175
+ return {
176
+ asJson,
177
+ engine,
178
+ verifier,
179
+ error,
180
+ positionals,
181
+ input: positionals.join(' ').replace(/\s+/g, ' ').trim(),
182
+ multiword: positionals.length > 1 || positionals.some((value) => /\s/.test(value.trim())),
183
+ };
184
+ }
185
+
124
186
  function applyRunnerFlags(args) {
125
187
  // --engine <name> is the operator-facing spelling of --runner-profile:
126
188
  // one flag rents a specific intelligence for this run.
@@ -196,13 +258,14 @@ const isBusinessSyncSafetyCommand = command === 'sync'
196
258
  || firstCommandArg === 'resolve'
197
259
  );
198
260
 
199
- // Auto-sync skills only for commands that modify workspace state
200
- if (['init', 'update', 'upgrade'].includes(command) || (command === 'sync' && !isBusinessSyncSafetyCommand)) {
261
+ // Auto-sync skills only for commands that modify workspace state. Help must
262
+ // stay read-only, including global ~/.claude and ~/.codex skill directories.
263
+ if (!helpRequested && !dryRunRequested && (['init', 'update', 'upgrade'].includes(command) || (command === 'sync' && !isBusinessSyncSafetyCommand))) {
201
264
  try {
202
265
  const { syncSkills } = require('../commands/sync');
203
266
  const skillsUpdated = syncSkills({ silent: true });
204
267
  if (skillsUpdated > 0) {
205
- console.log(`⬆️ ${skillsUpdated} skill${skillsUpdated > 1 ? 's' : ''} updated`);
268
+ console.log(`${skillsUpdated} skill${skillsUpdated > 1 ? 's' : ''} updated`);
206
269
  }
207
270
  } catch (e) {
208
271
  // Non-critical
@@ -345,76 +408,7 @@ function printAtrisGoalBanner(workspaceDir = process.cwd(), label = 'Atris goal'
345
408
  }
346
409
 
347
410
  function showSearchHelp() {
348
- console.log('Usage: atris search <keyword>');
349
- console.log('Example: atris search auth');
350
- }
351
-
352
- function searchJournal(keyword) {
353
- if (!keyword) {
354
- showSearchHelp();
355
- process.exit(1);
356
- }
357
-
358
- if (keyword === '--help' || keyword === '-h') {
359
- showSearchHelp();
360
- process.exit(0);
361
- }
362
-
363
- if (process.argv.slice(4).includes('--help') || process.argv.slice(4).includes('-h')) {
364
- showSearchHelp();
365
- process.exit(1);
366
- }
367
-
368
- const logsDir = path.join(process.cwd(), 'atris', 'logs');
369
- if (!fs.existsSync(logsDir)) {
370
- console.log('No atris/logs/ directory found. Run "atris init" first.');
371
- process.exit(1);
372
- }
373
-
374
- console.log(`Searching for "${keyword}" in atris/logs/**/*.md...\n`);
375
-
376
- const results = [];
377
- const keywordLower = keyword.toLowerCase();
378
-
379
- // Recursively find all .md files in logs directory
380
- function walkDir(dir) {
381
- let files;
382
- try { files = fs.readdirSync(dir); } catch { return; }
383
- for (const file of files) {
384
- try {
385
- const filePath = path.join(dir, file);
386
- const stat = fs.statSync(filePath);
387
- if (stat.isDirectory()) {
388
- walkDir(filePath);
389
- } else if (file.endsWith('.md')) {
390
- const content = fs.readFileSync(filePath, 'utf8');
391
- const lines = content.split('\n');
392
- lines.forEach((line, idx) => {
393
- if (line.toLowerCase().includes(keywordLower)) {
394
- results.push({
395
- file: path.relative(process.cwd(), filePath),
396
- line: idx + 1,
397
- content: line.trim()
398
- });
399
- }
400
- });
401
- }
402
- } catch { /* skip unreadable files */ }
403
- }
404
- }
405
-
406
- walkDir(logsDir);
407
-
408
- if (results.length === 0) {
409
- console.log('No matches found.');
410
- } else {
411
- console.log(`Found ${results.length} match${results.length > 1 ? 'es' : ''}:\n`);
412
- results.forEach(r => {
413
- console.log(`${r.file}:${r.line}`);
414
- console.log(` ${r.content.substring(0, 100)}${r.content.length > 100 ? '...' : ''}`);
415
- console.log('');
416
- });
417
- }
411
+ require('../commands/search').showSearchHelp();
418
412
  }
419
413
 
420
414
  function consoleCmd() {
@@ -453,7 +447,8 @@ function consoleCmd() {
453
447
  function showHelp() {
454
448
  console.log('');
455
449
  console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
456
- console.log('atris — an operating system for intelligence');
450
+ console.log('atris');
451
+ console.log('you say what you want in plain words. atris builds it, checks it, and shows you proof.');
457
452
  console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
458
453
  console.log('');
459
454
  console.log('Quick Start:');
@@ -464,11 +459,15 @@ function showHelp() {
464
459
  console.log(' 3. Atris acts with context, memory, tools, and a review loop');
465
460
  console.log('');
466
461
  console.log('Common invocations:');
462
+ console.log(' atris "<request>" Build one isolated change, verify it, and stop in Review');
463
+ console.log(' atris "<request>" --verify "<cmd>" --json');
464
+ console.log(' Supply proof explicitly and return one JSON result');
467
465
  console.log(' atris init [--yes] Global install: initialize this project');
468
466
  console.log(' npx atris init [--yes] Local install: initialize this project');
469
467
  console.log(' atris computer');
470
468
  console.log(' atris business init "My Company"');
471
469
  console.log(' atris run');
470
+ console.log(' atris drill');
472
471
  console.log(' atris status');
473
472
  console.log(' atris soul');
474
473
  console.log(' atris fleet status');
@@ -498,17 +497,26 @@ function showHelp() {
498
497
  console.log('');
499
498
  console.log('Context & tracking:');
500
499
  console.log(' log - Add ideas to inbox');
500
+ console.log(' wish - Say one plain sentence, then Atris asks only for gaps or delegates it');
501
501
  console.log(' now - Show atris/now.md, the current operating truth');
502
+ console.log(' goal - goal, distance, and what moved today (alias: wtf)');
503
+ console.log(' orb - Pick next moves while engine jobs work in the background');
502
504
  console.log(' activate - Load Atris context');
503
505
  console.log(' radar - Show live agents joined with tasks, missions, and worktrees');
506
+ console.log(' stream - Watch the whole team work live in one terminal');
507
+ console.log(' team - Show who is awake, what they are doing, and running loops');
508
+ console.log(' watch - Turn one sentence into an always-on background watcher');
504
509
  console.log(' ctop - Show a process-first live agent CPU/memory view');
505
510
  console.log(' launchpad - Show the next action from local brain, task, mission, and proof state');
511
+ console.log(' brief - Show the one-glance operator brief');
506
512
  console.log(' status - See local work and completions (`atris status <business>` for remote)');
507
513
  console.log(' recap - What your AI team did, in plain English (--share for paste-ready)');
514
+ console.log(' report - Weekly block: landings, journal completions, and Career XP');
508
515
  console.log(' xp - Show Career XP and contribution graph');
509
516
  console.log(' analytics - Show recent productivity from journals');
510
- console.log(' search - Search journal history (atris search <keyword>)');
517
+ console.log(' search - Search workspace memory (atris search <keyword>)');
511
518
  console.log(' clean - Housekeeping (stale tasks, archive journals, broken refs)');
519
+ console.log(' close - track open loops with deadlines and daily escalation');
512
520
  console.log(' harvest - Find bugs and next actions from receipts, run logs, and thinking');
513
521
  console.log(' verify - Validate work is done (tests, MAP.md, changes)');
514
522
  console.log(' task - Local agent task plane (atomic claims, TODO import)');
@@ -518,9 +526,13 @@ function showHelp() {
518
526
  console.log(' ... build ...');
519
527
  console.log(' atris task ready <id> --verify');
520
528
  console.log(' atris autoland tick # second check runs, task lands');
521
- console.log(' mission - Goal + loop + member owner + verifier + receipt');
529
+ console.log(' mission - Goal + loop + member owner + verifier + receipt; --budget quick|long|deep sets bounded tiers');
522
530
  console.log(' release - Tag release, bump version, create GitHub release, draft /launch');
523
531
  console.log(' learn - Project learnings (patterns, pitfalls, preferences)');
532
+ console.log(' study - On-demand learning feed: ingest topic, start server, open browser');
533
+ console.log(' rainmaker - Relationship manager dashboard (atrisos-backend/scripts/rainmaker.py)');
534
+ console.log(' meet - onboard a stranger in one sitting and print their /book link');
535
+ console.log(' avail - Booking availability (/book/{username} weekly windows)');
524
536
  console.log(' brain - Compile MAP/TODO/wiki/state into a loadable agent brain');
525
537
  console.log(' lesson - Append a one-line lesson to atris/lessons.md (mine: distill receipts/episodes/scorecards into policy lessons)');
526
538
  console.log(' ingest - Local-first wiki ingest into atris/wiki/');
@@ -537,7 +549,7 @@ function showHelp() {
537
549
  console.log(' land - The landing: what is actually done vs still in the air; --reap backs up + clears overdue');
538
550
  console.log(' drive - One self-driving tick: mission doctor -> auto-fix -> count disengagements');
539
551
  console.log(' autoland - Approve the policy once; certified work lands itself, you keep irreversible calls');
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');
552
+ console.log(' engine - Engine registry: list/resolve roles, health flips, default engine, `engine test`, and dispatch flights');
541
553
  console.log(' sign - Co-author trailer on every commit in an atris workspace (on/off/status)');
542
554
  console.log(' visualize - Generate a Slack/deck-ready visual from a prompt');
543
555
  console.log(' youtube - Process YouTube videos with timestamped transcript-first analysis');
@@ -547,6 +559,7 @@ function showHelp() {
547
559
  console.log(' experiments validate - Validate experiment packs');
548
560
  console.log(' experiments run <slug> - Execute a pack or record an Endstate receipt');
549
561
  console.log(' experiments benchmark [m] - Run validate/runtime experiment benchmarks');
562
+ console.log(' bench - run core benchmark gates');
550
563
  console.log('');
551
564
  console.log('Compile loop (learn like AI, run like code):');
552
565
  console.log(' compile record <name> - Append an execution record (--input/--output)');
@@ -562,6 +575,7 @@ function showHelp() {
562
575
  console.log('Sync:');
563
576
  console.log(' pull - Pull journals + member data from cloud');
564
577
  console.log(' push - Push workspace files to cloud');
578
+ console.log(' cloud - Delete cloud files not present locally (cloud clean --dry-run|--yes)');
565
579
  console.log(' live - Keep a business brain fresh (doctor, pull, watch, push)');
566
580
  console.log(' clean-workspace <slug> - Analyze & remove junk files from a workspace (alias: cw)');
567
581
  console.log('');
@@ -569,8 +583,9 @@ function showHelp() {
569
583
  console.log(' browse [query] - Discover workspace templates');
570
584
  console.log(' fork <template> - Clone a template into a new workspace');
571
585
  console.log(' publish - Share your workspace as a template');
572
- console.log(' sleep [business] - Pause workspace compute (context saved)');
573
- console.log(' wake [business] - Resume workspace (agents restart)');
586
+ console.log(' pack - Publish or install an Atris brain zip');
587
+ console.log(' sleep [business|member] [--loop id] - Pause compute or flip a member switch');
588
+ console.log(' wake [business|member] [--loop id] - Resume compute or flip a member switch');
574
589
  console.log('');
575
590
  console.log('Business:');
576
591
  console.log(' business init <name> - Create shared owner + first/default computer');
@@ -597,7 +612,8 @@ function showHelp() {
597
612
  console.log(' console - Start/attach always-on coding console (tmux daemon)');
598
613
  console.log(' soul - Show, snapshot, or fork workspace identity');
599
614
  console.log(' fleet - Inspect local fleet status');
600
- console.log(' loops - Background loops board: what runs, what died, start/stop');
615
+ console.log(' loops - Self-improving loop audit/scaffold (`init`, `audit`, `tick`, `board`)');
616
+ console.log(' self-improve - Alias for `atris loops init`');
601
617
  console.log(' agent - Select cloud agent, spawn worker requests, or run `agent doctor`');
602
618
  console.log(' chat - Chat with Atris 2 Fast in this workspace (--agent for cloud agent; or: atris chat scan)');
603
619
  console.log(' fast - Chat with Atris2 Fast');
@@ -762,12 +778,18 @@ function showVerifyHelp() {
762
778
  console.log('');
763
779
  console.log('Usage: atris verify [task]');
764
780
  console.log('Usage: atris verify <feature-slug> --section <name>');
781
+ console.log('Usage: atris verify artifact <path> [--objective "<text>"] [--min-lines N] [--max-age-hours H] [--json]');
765
782
  console.log('');
766
783
  console.log('Description:');
767
784
  console.log(' Validate workspace health, a specific task, or a feature rubric section.');
785
+ console.log(' The artifact form runs deterministic substance checks on a mission artifact');
786
+ console.log(' (a pre-filter for empty/skeleton/placeholder output, not a quality judgment).');
768
787
  console.log('');
769
788
  console.log('Options:');
770
789
  console.log(' --section <name> Run a fenced bash check from atris/features/<slug>/validate.md.');
790
+ console.log(' --objective <t> Require the artifact to cover the objective vocabulary.');
791
+ console.log(' --min-lines <n> Minimum substantive lines (default 10).');
792
+ console.log(' --max-age-hours <h> Require the artifact to be modified within this window.');
771
793
  console.log(' --help, -h Show this help.');
772
794
  console.log('');
773
795
  }
@@ -968,6 +990,7 @@ const { logAtris: logCmd } = require('../commands/log');
968
990
  const { activateAtris: activateCmd } = require('../commands/activate');
969
991
  const { statusAtris: statusCmd } = require('../commands/status');
970
992
  const { planAtris: planCmd, doAtris: doCmd, reviewAtris: reviewCmd } = require('../commands/workflow');
993
+ const { runOrb: orbCmd } = require('../commands/orb');
971
994
 
972
995
  // All other commands are lazy-loaded inline (require() only when invoked)
973
996
 
@@ -983,12 +1006,6 @@ if (command === '2' && ['fast', 'pro'].includes(String(firstCommandArg || '').to
983
1006
  }
984
1007
 
985
1008
  // Check if this is a known command or natural language input
986
- const knownCommands = ['init', 'log', 'now', 'radar', 'ctop', 'launchpad', 'status', 'analytics', 'visualize', 'brain', 'brainstorm', 'autopilot', 'run', '_start', 'plan', 'do', 'review', 'release',
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',
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',
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'];
992
1009
 
993
1010
  // Check if command is an atris.md spec file - triggers welcome visualization
994
1011
  function isSpecFile(cmd) {
@@ -1043,11 +1060,7 @@ const voiceTriggers = {
1043
1060
  if (!command || !knownCommands.includes(command)) {
1044
1061
  // Check voice triggers before falling through to natural language
1045
1062
  const fullInput = process.argv.slice(2).join(' ').toLowerCase().trim();
1046
- const fullInputWithoutFlags = process.argv.slice(2)
1047
- .filter((arg, index, args) => !String(arg).startsWith('-') && !isOptionValue(args, index, RUNNER_FLAG_NAMES))
1048
- .join(' ')
1049
- .toLowerCase()
1050
- .trim();
1063
+ const fullInputWithoutFlags = parseNaturalEntryArgs(process.argv.slice(2)).input.toLowerCase();
1051
1064
  const triggered = voiceTriggers[fullInput] || voiceTriggers[fullInputWithoutFlags];
1052
1065
  if (triggered) {
1053
1066
  command = triggered;
@@ -1061,28 +1074,36 @@ if (!command || !knownCommands.includes(command)) {
1061
1074
  }
1062
1075
 
1063
1076
  if (!command || !knownCommands.includes(command)) {
1064
- const userInput = process.argv.slice(2).join(' ');
1077
+ const rawNaturalArgs = process.argv.slice(2);
1078
+ const natural = parseNaturalEntryArgs(rawNaturalArgs);
1079
+ const userInput = natural.input;
1080
+ const directSingleWordNatural = !natural.multiword
1081
+ && SINGLE_WORD_NATURAL_INTENTS.has(userInput.toLowerCase());
1065
1082
 
1066
- if (process.argv.includes('--json')) {
1083
+ if (natural.asJson && !natural.multiword && !directSingleWordNatural) {
1067
1084
  console.log(JSON.stringify({
1068
1085
  ok: false,
1069
1086
  error: command ? `unknown command: ${command}` : 'unknown command',
1070
1087
  command: command || null,
1071
- input: userInput,
1088
+ input: rawNaturalArgs.join(' '),
1072
1089
  usage: 'atris help',
1073
1090
  }, null, 2));
1074
1091
  process.exit(2);
1075
1092
  }
1076
1093
 
1077
1094
  // Warn if this looks like a mistyped single-word command (no spaces)
1078
- if (command && !userInput.includes(' ')) {
1095
+ if (command && !natural.multiword && !directSingleWordNatural) {
1079
1096
  console.log(`⚠ Unknown command: "${command}". Run "atris help" for available commands.`);
1097
+ const suggestion = suggestCommand(command);
1098
+ if (suggestion) {
1099
+ console.log(` Did you mean "atris ${suggestion}"?`);
1100
+ }
1080
1101
  console.log(' Treating as natural language input...\n');
1081
1102
  }
1082
1103
 
1083
1104
  // Launch interactive entry (the "Performance")
1084
- interactiveEntry(userInput)
1085
- .then(() => process.exit(0))
1105
+ interactiveEntry(userInput, { oneLap: true, asJson: natural.asJson, engine: natural.engine, verifier: natural.verifier, optionError: natural.error })
1106
+ .then((code) => process.exit(Number.isInteger(code) ? code : 0))
1086
1107
  .catch((error) => {
1087
1108
  console.error(`✗ Error: ${error.message || error}`);
1088
1109
  process.exit(1);
@@ -1090,6 +1111,12 @@ if (!command || !knownCommands.includes(command)) {
1090
1111
  return;
1091
1112
  }
1092
1113
 
1114
+ // Help and previews promise a write-free workspace. Business workspaces
1115
+ // already have .atris/, so ordinary usage telemetry would break that promise.
1116
+ if (!(helpRequested || dryRunRequested)) {
1117
+ recordUsage(command, process.cwd());
1118
+ }
1119
+
1093
1120
  function printAtrisOverview() {
1094
1121
  console.log('');
1095
1122
  console.log('Atris is an AI computer for a workspace.');
@@ -1111,23 +1138,90 @@ function shouldSkipContextGatherer() {
1111
1138
  return !useInteractiveAtrisUi() || initNonInteractiveFlag();
1112
1139
  }
1113
1140
 
1114
- async function interactiveEntry(userInput) {
1141
+ function firstUseCommand() {
1142
+ return 'atris "help me choose the first useful step for this project"';
1143
+ }
1144
+
1145
+ function firstMissionObjective() {
1146
+ return 'Verify this Atris workspace is ready';
1147
+ }
1148
+
1149
+ function localOwnerName() {
1150
+ return process.env.USER || os.userInfo?.().username || 'operator';
1151
+ }
1152
+
1153
+ function firstMissionOwner(root = process.cwd()) {
1154
+ const defaultOwner = path.join(root, 'atris', 'team', 'validator', 'MEMBER.md');
1155
+ return fs.existsSync(defaultOwner) ? 'validator' : localOwnerName();
1156
+ }
1157
+
1158
+ function firstMissionCommand() {
1159
+ return `atris mission start "${firstMissionObjective()}" --owner ${firstMissionOwner()} --runner manual --lane workspace --verify "node -e \\"require('fs').accessSync('atris/atris.md')\\"" --stop "workspace readiness is verified"`;
1160
+ }
1161
+
1162
+ function printFirstUseNext() {
1163
+ const row = (label, value) => ` ${label.padEnd(9)}${value}`;
1164
+ console.log(row('next', 'run `atris` and describe what you want in plain words.'));
1165
+ console.log(`agents: ${firstMissionCommand()}`);
1166
+ console.log(`Then: ${firstUseCommand()}`);
1167
+ }
1168
+
1169
+ function printStarterTaskNext(starter) {
1170
+ console.log('next setup: open atris/MAP.md, then claim the starter task.');
1171
+ if (starter && starter.display_id) {
1172
+ console.log(`Next: atris task claim ${starter.display_id} --as ${localOwnerName()}`);
1173
+ return;
1174
+ }
1175
+ console.log('Next: atris task next --as ' + localOwnerName());
1176
+ }
1177
+
1178
+ async function interactiveEntry(userInput, options = {}) {
1115
1179
  const workspaceDir = process.cwd();
1116
1180
  const state = detectWorkspaceState(workspaceDir);
1117
1181
  const context = loadContext(workspaceDir);
1118
1182
 
1183
+ if (options.asJson && !String(userInput || '').trim()) {
1184
+ console.log(JSON.stringify({
1185
+ schema: 'atris.one_lap.v1',
1186
+ ok: false,
1187
+ status: 'stuck',
1188
+ reason: 'a request is required',
1189
+ next_action: 'atris "<request>" --json',
1190
+ }, null, 2));
1191
+ return 2;
1192
+ }
1193
+
1119
1194
  if (isAtrisMetaQuestion(userInput)) {
1195
+ if (options.asJson) {
1196
+ console.log(JSON.stringify({
1197
+ schema: 'atris.overview.v1',
1198
+ ok: true,
1199
+ product: 'Atris',
1200
+ description: 'An AI computer for a workspace with context, tasks, memory, tools, and proof.',
1201
+ workflow: ['plan', 'do', 'review'],
1202
+ }, null, 2));
1203
+ return 0;
1204
+ }
1120
1205
  printAtrisOverview();
1121
1206
  return;
1122
1207
  }
1123
1208
 
1124
1209
  // Fresh install - offer init
1125
1210
  if (state.state === 'fresh') {
1211
+ if (options.asJson) {
1212
+ console.log(JSON.stringify({
1213
+ schema: 'atris.one_lap.v1',
1214
+ ok: false,
1215
+ status: 'stuck',
1216
+ reason: 'this workspace is not initialized',
1217
+ next_action: 'atris init --yes',
1218
+ }, null, 2));
1219
+ return 2;
1220
+ }
1126
1221
  console.log('\nNo atris/ folder found.');
1127
1222
  console.log('');
1128
- console.log('Start here:');
1129
- console.log(' atris init if Atris is installed globally');
1130
- console.log(' npx atris init if Atris was installed in this project');
1223
+ console.log('Next: atris init');
1224
+ console.log('Local project install instead? Run: npx atris init');
1131
1225
  return;
1132
1226
  }
1133
1227
 
@@ -1155,6 +1249,55 @@ async function interactiveEntry(userInput) {
1155
1249
  // commitment that hasn't been closed yet.
1156
1250
  const activeMissions = loadActiveMissions(workspaceDir);
1157
1251
  const liveMissionsCount = activeMissions.length;
1252
+ const wipCount = inProgressTasksCount + inProgressFeaturesCount;
1253
+ const mapStatus = context.mapStatus || (context.mapExists ? 'ready' : 'missing');
1254
+ const gatherContext = shouldGatherContext({
1255
+ root: workspaceDir,
1256
+ userInput,
1257
+ mapStatus,
1258
+ liveMissionsCount,
1259
+ wipCount,
1260
+ backlogCount,
1261
+ inboxCount,
1262
+ completedTasksCount,
1263
+ });
1264
+
1265
+ if (options.optionError) {
1266
+ const result = {
1267
+ schema: 'atris.one_lap.v1',
1268
+ ok: false,
1269
+ status: 'stuck',
1270
+ reason: options.optionError,
1271
+ next_action: 'atris "<request>" [--engine <id>] [--verify "<cmd>"] [--json]',
1272
+ };
1273
+ if (options.asJson) console.log(JSON.stringify(result, null, 2));
1274
+ else {
1275
+ console.log('lap: stuck');
1276
+ console.log(`why it matters: ${result.reason}`);
1277
+ console.log(`next: ${result.next_action}`);
1278
+ }
1279
+ return 2;
1280
+ }
1281
+
1282
+ if (userInput && mapStatus === 'ready' && !gatherContext && options.oneLap !== false) {
1283
+ return require('../commands/one-lap').runOneLap(userInput, {
1284
+ root: workspaceDir,
1285
+ asJson: options.asJson === true,
1286
+ engine: options.engine || '',
1287
+ verifier: options.verifier || '',
1288
+ });
1289
+ }
1290
+
1291
+ if (options.asJson && userInput) {
1292
+ console.log(JSON.stringify({
1293
+ schema: 'atris.one_lap.v1',
1294
+ ok: false,
1295
+ status: 'stuck',
1296
+ reason: mapStatus !== 'ready' ? 'the workspace map is not ready' : 'first-contact context is required',
1297
+ next_action: mapStatus !== 'ready' ? 'atris init --yes' : 'atris "<first direction>"',
1298
+ }, null, 2));
1299
+ return 2;
1300
+ }
1158
1301
  // Mission needs a tick when: it has a verifier configured AND that verifier
1159
1302
  // hasn't passed yet. Planning-state missions count too — first tick is what
1160
1303
  // moves them to running.
@@ -1164,41 +1307,29 @@ async function interactiveEntry(userInput) {
1164
1307
 
1165
1308
  // Build status line
1166
1309
  const parts = [];
1167
- const wipCount = inProgressTasksCount + inProgressFeaturesCount;
1168
1310
  if (wipCount > 0) {
1169
- parts.push(`WIP: ${wipCount}`);
1311
+ parts.push(`work in progress: ${wipCount}`);
1170
1312
  }
1171
1313
  if (liveMissionsCount > 0) {
1172
- parts.push(`Missions: ${liveMissionsCount}`);
1314
+ parts.push(`missions: ${liveMissionsCount}`);
1173
1315
  }
1174
1316
  if (inboxCount > 0) {
1175
- parts.push(`Inbox: ${inboxCount}`);
1317
+ parts.push(`inbox: ${inboxCount}`);
1176
1318
  }
1177
1319
  if (backlogCount > 0) {
1178
- parts.push(`Backlog: ${backlogCount}`);
1320
+ parts.push(`backlog: ${backlogCount}`);
1179
1321
  }
1180
1322
  if (completedTasksCount > 0) {
1181
- parts.push(`Done: ${completedTasksCount}`);
1323
+ parts.push(`done: ${completedTasksCount}`);
1182
1324
  }
1183
- const statusLine = parts.length > 0 ? parts.join(' | ') : 'Clean slate';
1325
+ const statusLine = parts.length > 0 ? parts.join(' | ') : 'clean slate';
1184
1326
 
1185
1327
  console.log('');
1186
- console.log('┌─────────────────────────────────────────────────────────────┐');
1187
- console.log('│ CONTEXT LOADED │');
1188
- console.log('├─────────────────────────────────────────────────────────────┤');
1189
- console.log(`│ ${statusLine.padEnd(60)}│`);
1190
- console.log('└─────────────────────────────────────────────────────────────┘');
1328
+ const contextRow = (label, value) => ` ${label.padEnd(9)}${value}`;
1329
+ console.log(contextRow('context', 'loaded'));
1330
+ console.log(contextRow('status', statusLine));
1191
1331
 
1192
- const mapStatus = context.mapStatus || (context.mapExists ? 'ready' : 'missing');
1193
- if (shouldGatherContext({
1194
- root: workspaceDir,
1195
- userInput,
1196
- mapStatus,
1197
- liveMissionsCount,
1198
- wipCount,
1199
- backlogCount,
1200
- inboxCount,
1201
- })) {
1332
+ if (gatherContext) {
1202
1333
  const hotAnswer = String(userInput || '').trim();
1203
1334
  if (hotAnswer) {
1204
1335
  const answer = hotAnswer;
@@ -1217,7 +1348,7 @@ async function interactiveEntry(userInput) {
1217
1348
  console.log(`First task: ${starter.title}`);
1218
1349
  }
1219
1350
  if (mapStatus !== 'ready') {
1220
- printMapBootstrap({ userInput: answer, prefix: 'Next setup step' });
1351
+ printStarterTaskNext(starter);
1221
1352
  return;
1222
1353
  }
1223
1354
  await planCmd(answer);
@@ -1225,7 +1356,11 @@ async function interactiveEntry(userInput) {
1225
1356
  }
1226
1357
  if (shouldSkipContextGatherer()) {
1227
1358
  console.log('');
1228
- console.log("context gatherer skipped (non-interactive). run 'atris plan' when you're ready.");
1359
+ if (process.argv.includes('--verbose')) {
1360
+ console.log('context gatherer skipped (non-interactive).');
1361
+ }
1362
+ printFirstUseNext();
1363
+ return;
1229
1364
  } else {
1230
1365
  const answer = await askContextGatherer(workspaceDir);
1231
1366
  if (isAtrisMetaQuestion(answer)) {
@@ -1249,7 +1384,7 @@ async function interactiveEntry(userInput) {
1249
1384
  console.log(`First task: ${starter.title}`);
1250
1385
  }
1251
1386
  if (mapStatus !== 'ready') {
1252
- printMapBootstrap({ userInput: answer, prefix: 'Next setup step' });
1387
+ printStarterTaskNext(starter);
1253
1388
  return;
1254
1389
  }
1255
1390
  await planCmd(answer);
@@ -1384,54 +1519,62 @@ function printMapBootstrap({ userInput, prefix = 'Bootstrap required' } = {}) {
1384
1519
  console.log('');
1385
1520
  }
1386
1521
 
1387
- // ASCII Welcome Visualization
1522
+ // Boot status: plain rows, honest numbers, one next action.
1523
+ // The banner only renders in a real terminal; when a hook or agent captures
1524
+ // this output it stays compact so it costs almost nothing in context.
1388
1525
  function showWelcomeVisualization() {
1389
- const { getTaskCounts } = require('../lib/state-detection');
1526
+ const { getTaskGlance } = require('../lib/state-detection');
1390
1527
  const { readEndgameState } = require('../commands/autopilot');
1391
1528
  const cwd = process.cwd();
1392
1529
  const atrisDir = path.join(cwd, 'atris');
1393
1530
  const projectName = path.basename(cwd);
1531
+ const row = (label, value) => ` ${label.padEnd(9)}${value}`;
1532
+ const sub = (text) => ` ${' '.repeat(9)}- ${text}`;
1533
+ const trimTitle = (t) => (String(t).length > 64 ? `${String(t).slice(0, 61)}...` : String(t));
1394
1534
 
1395
- // Gather workspace stats
1396
- let filesIndexed = 0;
1397
- let tasksInBacklog = 0;
1398
- let tasksInProgress = 0;
1399
- let tasksInReview = 0;
1400
- let tasksCertified = 0;
1535
+ let glance = {
1536
+ backlog: 0, active: 0, review: 0, reviewCertified: 0,
1537
+ activeTitles: [], backlogTitles: [], certifiedTitles: []
1538
+ };
1401
1539
  let journalEntries = 0;
1402
- let hasMap = false;
1403
- let isInitialized = fs.existsSync(atrisDir);
1540
+ let latestBriefName = '';
1541
+ let latestBriefTitle = '';
1542
+ const isInitialized = fs.existsSync(atrisDir);
1404
1543
  let endgameState = { slug: 'unset', horizon: '' };
1405
1544
 
1406
1545
  if (isInitialized) {
1407
- // Check MAP.md
1408
- const mapPath = path.join(atrisDir, 'MAP.md');
1409
- if (fs.existsSync(mapPath)) {
1410
- hasMap = true;
1411
- const mapContent = fs.readFileSync(mapPath, 'utf8');
1412
- // Count file references (lines with file paths)
1413
- const fileRefs = mapContent.match(/`[^`]+\.(js|ts|py|go|rs|md|json|yaml|yml)`/g);
1414
- filesIndexed = fileRefs ? fileRefs.length : 0;
1415
- }
1416
-
1417
- // Task lane counts — DB truth first, TODO.md parse as fallback
1418
1546
  try {
1419
- const counts = getTaskCounts(atrisDir);
1420
- tasksInBacklog = counts.backlog;
1421
- tasksInProgress = counts.active;
1422
- tasksInReview = counts.review;
1423
- tasksCertified = counts.reviewCertified;
1547
+ glance = getTaskGlance(atrisDir);
1424
1548
  } catch {
1425
1549
  // Silently fail - show 0 tasks if reading fails
1426
1550
  }
1427
1551
 
1428
- // Read endgame state
1429
1552
  try {
1430
1553
  endgameState = readEndgameState(cwd);
1431
1554
  } catch {
1432
1555
  // Silently fail - show unset if reading fails
1433
1556
  }
1434
1557
 
1558
+ try {
1559
+ const briefsDir = path.join(atrisDir, 'wiki', 'briefs');
1560
+ latestBriefName = fs.readdirSync(briefsDir, { withFileTypes: true })
1561
+ .filter((entry) => entry.isFile() && entry.name.endsWith('.md'))
1562
+ .map((entry) => ({ name: entry.name, mtimeMs: fs.statSync(path.join(briefsDir, entry.name)).mtimeMs }))
1563
+ .sort((a, b) => b.mtimeMs - a.mtimeMs || a.name.localeCompare(b.name))[0]?.name || '';
1564
+ if (latestBriefName) {
1565
+ // Prefer the brief's own H1 over its filename; strip miner boilerplate
1566
+ // like "YouTube brief: <slug>" so boot reads like a sentence.
1567
+ const head = fs.readFileSync(path.join(briefsDir, latestBriefName), 'utf8').slice(0, 2000);
1568
+ const h1 = head.split('\n').find((line) => line.startsWith('# '));
1569
+ let title = h1 ? h1.slice(2).trim() : '';
1570
+ title = title.replace(/^youtube brief:\s*/i, '').trim();
1571
+ const slug = path.basename(latestBriefName, '.md');
1572
+ latestBriefTitle = title && title.toLowerCase() !== slug.toLowerCase() ? title : slug;
1573
+ }
1574
+ } catch {
1575
+ // Briefs are optional, so missing or unreadable directories stay silent.
1576
+ }
1577
+
1435
1578
  // Count journal entries today
1436
1579
  const today = new Date();
1437
1580
  const year = today.getFullYear();
@@ -1446,119 +1589,136 @@ function showWelcomeVisualization() {
1446
1589
  }
1447
1590
 
1448
1591
  console.log('');
1449
- console.log(' ╭──────────────────────────────────────────╮');
1450
- console.log(' │ │');
1451
- console.log(' │ █████╗ ████████╗██████╗ ██╗███████╗ │');
1452
- console.log(' │ ██╔══██╗╚══██╔══╝██╔══██╗██║██╔════╝ │');
1453
- console.log(' │ ███████║ ██║ ██████╔╝██║███████╗ │');
1454
- console.log(' │ ██╔══██║ ██║ ██╔══██╗██║╚════██║ │');
1455
- console.log(' │ ██║ ██║ ██║ ██║ ██║██║███████║ │');
1456
- console.log(' │ ╚═╝ ╚═╝ ╚═╝ ╚═╝ ╚═╝╚═╝╚══════╝ │');
1457
- console.log(' │ │');
1458
- console.log(' ╰──────────────────────────────────────────╯');
1592
+ if (process.stdout.isTTY) {
1593
+ console.log(' ╭──────────────────────────────────────────╮');
1594
+ console.log(' │ │');
1595
+ console.log(' │ █████╗ ████████╗██████╗ ██╗███████╗ │');
1596
+ console.log(' │ ██╔══██╗╚══██╔══╝██╔══██╗██║██╔════╝ │');
1597
+ console.log(' │ ███████║ ██║ ██████╔╝██║███████╗ │');
1598
+ console.log(' │ ██╔══██║ ██║ ██╔══██╗██║╚════██║ │');
1599
+ console.log(' │ ██║ ██║ ██║ ██║ ██║██║███████║ │');
1600
+ console.log(' │ ╚═╝ ╚═╝ ╚═╝ ╚═╝ ╚═╝╚═╝╚══════╝ │');
1601
+ console.log(' │ │');
1602
+ console.log(' ╰──────────────────────────────────────────╯');
1603
+ console.log('');
1604
+ }
1605
+ console.log(` atris v${CLI_VERSION} · ${projectName}`);
1459
1606
  console.log('');
1460
1607
 
1461
1608
  if (!isInitialized) {
1462
- console.log(' ⚡ Spec detected. No workspace found.');
1609
+ console.log(' no atris workspace here yet.');
1463
1610
  console.log('');
1464
- console.log(' ┌─ READY TO INITIALIZE ────────────────────┐');
1465
- console.log(' │ │');
1466
- console.log(` │ 📍 Project: ${projectName.substring(0, 25).padEnd(25)}│`);
1467
- console.log(` │ 📄 Spec: atris.md v${CLI_VERSION.padEnd(18)}│`);
1468
- console.log(' │ │');
1469
- console.log(' │ Run "atris init" to create workspace │');
1470
- console.log(' │ │');
1471
- console.log(' └──────────────────────────────────────────┘');
1472
- } else {
1473
- console.log(' ⚡ Scanning spec...');
1611
+ console.log(row('next', 'atris init (set up this folder)'));
1474
1612
  console.log('');
1475
- console.log(' ┌─ WORKSPACE DETECTED ─────────────────────┐');
1476
- console.log(' │ │');
1477
- console.log(` │ 📍 Project: ${projectName.substring(0, 25).padEnd(25)}│`);
1478
- console.log(` │ 📄 Spec: atris.md v${CLI_VERSION.padEnd(18)}│`);
1479
- console.log(` │ 🗺️ Map: ${hasMap ? (filesIndexed + ' files indexed').padEnd(26) : 'not generated yet'.padEnd(26)}│`);
1480
- console.log(` │ 📋 Tasks: ${(tasksInBacklog + ' backlog, ' + tasksInProgress + ' active').padEnd(26)}│`);
1481
- if (tasksInReview > 0) {
1482
- const reviewText = tasksCertified > 0
1483
- ? `${tasksInReview} waiting (${tasksCertified} certified)`
1484
- : `${tasksInReview} waiting`;
1485
- console.log(` │ ⏳ Review: ${reviewText.padEnd(26)}│`);
1613
+ return;
1614
+ }
1615
+
1616
+ if (latestBriefName) {
1617
+ console.log(` learned \"${latestBriefTitle}\" overnight -> atris/wiki/briefs/${latestBriefName}`);
1618
+ }
1619
+
1620
+ // Show the work itself, not counts. A newcomer in any domain (code, docs,
1621
+ // a travel plan) should read actual task names and know what's happening.
1622
+ // Waiting-on-you comes first: the one thing only a human can do.
1623
+ if (glance.reviewCertified > 0) {
1624
+ console.log(row('you', `${glance.reviewCertified} done, waiting for your ok:`));
1625
+ glance.certifiedTitles.forEach((t) => console.log(sub(trimTitle(t))));
1626
+ }
1627
+
1628
+ if (glance.active > 0) {
1629
+ console.log(row('now', trimTitle(glance.activeTitles[0] || 'work moving')));
1630
+ glance.activeTitles.slice(1).forEach((t) => console.log(sub(trimTitle(t))));
1631
+ const tail = [];
1632
+ const moreActive = glance.active - glance.activeTitles.length;
1633
+ if (moreActive > 0) tail.push(`${moreActive} more moving`);
1634
+ if (glance.backlog > 0) tail.push(`${glance.backlog} waiting to start`);
1635
+ if (glance.review > 0) tail.push(`${glance.review} getting a final look`);
1636
+ if (tail.length) console.log(` ${' '.repeat(9)}...and ${tail.join(', ')}`);
1637
+ } else if (glance.backlog > 0) {
1638
+ console.log(row('soon', trimTitle(glance.backlogTitles[0] || 'work queued')));
1639
+ glance.backlogTitles.slice(1).forEach((t) => console.log(sub(trimTitle(t))));
1640
+ const moreBacklog = glance.backlog - glance.backlogTitles.length;
1641
+ if (moreBacklog > 0) console.log(` ${' '.repeat(9)}...and ${moreBacklog} more waiting`);
1642
+ } else {
1643
+ console.log(row('now', 'nothing on the list yet'));
1644
+ }
1645
+
1646
+ // landSummary is expensive (git board classification) - compute once per boot.
1647
+ let landInfo = null;
1648
+ try { landInfo = require('../commands/land').landSummary(cwd); } catch (err) { landInfo = null; }
1649
+ let rotInfo = null;
1650
+ try {
1651
+ const { parseLessons } = require('../lib/memory-view');
1652
+ const resolved = path.resolve(cwd);
1653
+ const parent = path.dirname(resolved);
1654
+ const grandparent = path.dirname(parent);
1655
+ let worktreeDir;
1656
+ if (path.basename(grandparent) === '.agent-worktrees') {
1657
+ worktreeDir = parent;
1658
+ } else {
1659
+ worktreeDir = path.join(path.dirname(resolved), '.agent-worktrees', path.basename(resolved));
1486
1660
  }
1487
- let landInfo = null;
1488
- try { landInfo = require('../commands/land').landSummary(process.cwd()); } catch (err) { landInfo = null; }
1489
- if (landInfo && landInfo.branches > 0) {
1490
- const landText = `${landInfo.branches} in the air, ${landInfo.due} overdue`;
1491
- console.log(` │ 🛬 Land: ${landText.padEnd(26)}│`);
1661
+ let worktrees = 0;
1662
+ if (fs.existsSync(worktreeDir)) {
1663
+ worktrees = fs.readdirSync(worktreeDir, { withFileTypes: true })
1664
+ .filter((entry) => entry.isDirectory()).length;
1492
1665
  }
1493
- let rotInfo = null;
1666
+ let lessonsText = '';
1494
1667
  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
- }
1668
+ lessonsText = fs.readFileSync(path.join(atrisDir, 'lessons.md'), 'utf8');
1524
1669
  } 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)}│`);
1670
+ lessonsText = '';
1530
1671
  }
1531
- console.log(` │ 📝 Journal: ${(journalEntries + ' entries today').padEnd(26)}│`);
1532
- if (endgameState.slug !== 'unset' && endgameState.horizon) {
1533
- const endgameLine = endgameState.slug + ' — ' + endgameState.horizon;
1534
- const paddedEndgame = endgameLine.padEnd(26);
1535
- console.log(` │ 🎯 Endgame: ${paddedEndgame}│`);
1672
+ // cleanup = fail lessons nobody has resolved. A `pass` lesson is knowledge
1673
+ // that worked - it has nothing to resolve and counting it guilt-trips
1674
+ // the operator with a number (600+) no one can ever drive to zero.
1675
+ const unresolvedLessons = parseLessons(lessonsText)
1676
+ .filter((lesson) => lesson.status === 'fail' && !lesson.resolved && !/\[resolved[\]:]/i.test(lesson.text)).length;
1677
+ if (worktrees > 0 || unresolvedLessons > 0) {
1678
+ rotInfo = { worktrees, lessons: unresolvedLessons };
1536
1679
  }
1537
- console.log(' │ │');
1538
- console.log(' │ ┌──────────────────────────────────┐ │');
1539
- console.log(' │ │ MAP.md ←──── YOU ARE HERE │ │');
1540
- console.log(' │ │ ↓ │ │');
1541
- const taskText = `${tasksInBacklog} task${tasksInBacklog === 1 ? '' : 's'} waiting`;
1542
- console.log(` │ │ TODO.md ←── ${taskText.padEnd(20)}│ │`);
1543
- console.log(' │ │ ↓ │ │');
1544
- console.log(' │ │ navigator → executor → validator│ │');
1545
- console.log(' │ └──────────────────────────────────┘ │');
1546
- console.log(' │ │');
1547
- console.log(' └──────────────────────────────────────────┘');
1548
- }
1549
- console.log('');
1550
- if (tasksCertified > 0) {
1551
- console.log(` Ready. ${tasksCertified} certified await accept — run 'atris task reviews'.`);
1680
+ } catch (err) {
1681
+ rotInfo = null;
1682
+ }
1683
+ // One tidy row for all loose ends, in words that work outside engineering:
1684
+ // unlanded finished work = "to put away", stale worktrees = "old copies".
1685
+ const tidyBits = [];
1686
+ if (landInfo && landInfo.branches > 0) {
1687
+ let landText = `${landInfo.branches} finished piece${landInfo.branches === 1 ? '' : 's'} to put away`;
1688
+ if (landInfo.due > 0) landText += ` (${landInfo.due} overdue)`;
1689
+ tidyBits.push(landText);
1690
+ }
1691
+ if (rotInfo && rotInfo.worktrees > 0) tidyBits.push(`${rotInfo.worktrees} old cop${rotInfo.worktrees === 1 ? 'y' : 'ies'} to toss`);
1692
+ if (rotInfo && rotInfo.lessons > 0) tidyBits.push(`${rotInfo.lessons} open problem${rotInfo.lessons === 1 ? '' : 's'}`);
1693
+ if (tidyBits.length) {
1694
+ console.log(row('tidy', tidyBits.join(', ')));
1695
+ }
1696
+
1697
+ console.log(row('logs', journalEntries > 0
1698
+ ? `${journalEntries} note${journalEntries === 1 ? '' : 's'} today`
1699
+ : 'nothing yet today'));
1700
+
1701
+ if (endgameState.slug !== 'unset') {
1702
+ // Repeated impression: the horizon sentence renders verbatim every boot
1703
+ // so agents keep the target in mind (test/boot-impression.test.js).
1704
+ console.log(row('goal', endgameState.horizon || endgameState.slug));
1705
+ }
1706
+
1707
+ // The next command always carries a plain-english gloss: a newcomer should
1708
+ // know what typing it will do before they type it.
1709
+ let next;
1710
+ if (glance.reviewCertified > 0) {
1711
+ next = `atris task reviews (approve the finished work)`;
1712
+ } else if (landInfo && landInfo.due > 0) {
1713
+ next = `atris land --reap (put away the overdue work)`;
1714
+ } else if (endgameState.slug !== 'unset') {
1715
+ next = 'atris autopilot (do the next piece of work)';
1552
1716
  } else {
1553
- let landHint = null;
1554
- try { landHint = require('../commands/land').landSummary(process.cwd()); } catch (err) { landHint = null; }
1555
- if (landHint && landHint.due > 0) {
1556
- console.log(` Ready. ${landHint.due} overdue in the landing — run 'atris land --reap'.`);
1557
- } else {
1558
- console.log(` Ready. Run 'atris plan' to start.`);
1559
- }
1717
+ next = 'atris plan (plan the first tasks)';
1560
1718
  }
1561
1719
  console.log('');
1720
+ console.log(row('next', next));
1721
+ console.log('');
1562
1722
  }
1563
1723
 
1564
1724
  if (command === 'init') {
@@ -1616,10 +1776,38 @@ if (command === 'init') {
1616
1776
  Promise.resolve(require('../commands/task').run(process.argv.slice(3)))
1617
1777
  .then(() => process.exit(0))
1618
1778
  .catch((err) => { console.error(`\n✗ Error: ${err.message || err}`); process.exit(1); });
1779
+ } else if (command === 'team') {
1780
+ Promise.resolve(require('../commands/team').teamCommand(process.argv.slice(3)))
1781
+ .then((code) => process.exit(code || 0))
1782
+ .catch((err) => { console.error(`\nerror: ${err.message || err}`); process.exit(1); });
1783
+ } else if (command === 'wish') {
1784
+ Promise.resolve(require('../commands/wish').wishCommand(process.argv.slice(3)))
1785
+ .then((code) => process.exit(code || 0))
1786
+ .catch((err) => { console.error(`\n✗ Error: ${err.message || err}`); process.exit(1); });
1787
+ } else if (command === 'drill') {
1788
+ Promise.resolve(require('../commands/drill').drillCommand(process.argv.slice(3)))
1789
+ .then((code) => process.exit(code || 0))
1790
+ .catch((err) => { console.error(`\n✗ Error: ${err.message || err}`); process.exit(1); });
1791
+ } else if (command === 'bench') {
1792
+ Promise.resolve(require('../commands/bench').benchCommand(process.argv.slice(3)))
1793
+ .then((code) => process.exit(code || 0))
1794
+ .catch((err) => { console.error(`\n✗ Error: ${err.message || err}`); process.exit(2); });
1619
1795
  } else if (command === 'mission') {
1796
+ // process.exit() can outrun a piped stdout: writes beyond the 64KB pipe
1797
+ // buffer are async, so large --json payloads truncate at 64KB multiples.
1798
+ // Queue an empty write and exit from its callback — it fires only after
1799
+ // every earlier buffered write has drained (BCK-1306).
1800
+ const exitAfterStdoutDrain = (code) => {
1801
+ if (process.stdout.writableLength === 0) process.exit(code);
1802
+ else process.stdout.write('', () => process.exit(code));
1803
+ };
1620
1804
  Promise.resolve(require('../commands/mission').missionCommand(process.argv.slice(3)))
1621
- .then(() => process.exit(process.exitCode || 0))
1622
- .catch((err) => { console.error(`\n✗ Error: ${err.message || err}`); process.exit(1); });
1805
+ .then(() => exitAfterStdoutDrain(process.exitCode || 0))
1806
+ .catch((err) => { console.error(`\n✗ Error: ${err.message || err}`); exitAfterStdoutDrain(1); });
1807
+ } else if (command === 'agents') {
1808
+ // Glanceable view of every member's state: stuck, waiting on you, working, resting.
1809
+ const code = require('../commands/agents').agentsCommand(process.argv.slice(3));
1810
+ process.exit(code || 0);
1623
1811
  } else if (command === 'pulse') {
1624
1812
  // Pulse: durable overnight self-improvement heartbeat (OS cron) for atris-cli.
1625
1813
  Promise.resolve(require('../commands/pulse').pulseCommand(process.argv.slice(3)))
@@ -1642,16 +1830,32 @@ if (command === 'init') {
1642
1830
  Promise.resolve(require('../commands/land').landCommand(process.argv.slice(3)))
1643
1831
  .then((code) => process.exit(code || 0))
1644
1832
  .catch((err) => { console.error(`\n✗ Error: ${err.message || err}`); process.exit(1); });
1833
+ } else if (command === 'close') {
1834
+ Promise.resolve(require('../commands/close').run(process.argv.slice(3)))
1835
+ .then((code) => process.exit(typeof code === 'number' ? code : 0))
1836
+ .catch((err) => { console.error(`\nerror: ${err.message || err}`); process.exit(1); });
1837
+ } else if (command === 'goal' || command === 'wtf') {
1838
+ Promise.resolve(require('../commands/goal').run(process.argv.slice(3)))
1839
+ .then((code) => process.exit(typeof code === 'number' ? code : 0))
1840
+ .catch((err) => { console.error(`\nerror: ${err.message || err}`); process.exit(1); });
1645
1841
  } else if (command === 'drive') {
1646
1842
  // Drive: one self-driving tick — mission doctor -> auto-fix safe findings -> count disengagements.
1647
1843
  Promise.resolve(require('../commands/drive').driveCommand(process.argv.slice(3)))
1648
1844
  .then((code) => process.exit(code || 0))
1649
1845
  .catch((err) => { console.error(`\n✗ Error: ${err.message || err}`); process.exit(1); });
1846
+ } else if (command === 'orb') {
1847
+ Promise.resolve(orbCmd(process.argv.slice(3)))
1848
+ .then((code) => process.exit(typeof code === 'number' ? code : 0))
1849
+ .catch((err) => { console.error(`\nError: ${err.message || err}`); process.exit(1); });
1650
1850
  } else if (command === 'radar' || command === 'ctop') {
1651
1851
  const radarArgs = command === 'ctop' ? ['--agents', ...process.argv.slice(3)] : process.argv.slice(3);
1652
1852
  Promise.resolve(require('../commands/radar').radarCommand(radarArgs))
1653
1853
  .then((code) => process.exit(code || 0))
1654
1854
  .catch((err) => { console.error(`\n✗ Error: ${err.message || err}`); process.exit(1); });
1855
+ } else if (command === 'stream') {
1856
+ Promise.resolve(require('../commands/stream').streamCommand(process.argv.slice(3)))
1857
+ .then((code) => process.exit(code || 0))
1858
+ .catch((err) => { console.error(`\n✗ Error: ${err.message || err}`); process.exit(1); });
1655
1859
  } else if (command === 'truth') {
1656
1860
  // Truth: one table rolling up mission state, tasks, feature proof receipts, and loop heartbeats.
1657
1861
  Promise.resolve(require('../commands/truth').truthCommand(process.argv.slice(3)))
@@ -1671,6 +1875,22 @@ if (command === 'init') {
1671
1875
  Promise.resolve(require('../commands/improve').run(process.argv.slice(3)))
1672
1876
  .then((code) => process.exit(typeof code === 'number' ? code : 0))
1673
1877
  .catch((err) => { console.error(`\n✗ Error: ${err.message || err}`); process.exit(1); });
1878
+ } else if (command === 'study') {
1879
+ // Study: on-demand learning feed ingest + local server + browser open.
1880
+ Promise.resolve(require('../commands/study').run(process.argv.slice(3)))
1881
+ .then((code) => process.exit(typeof code === 'number' ? code : 0))
1882
+ .catch((err) => { console.error(`\n✗ Error: ${err.message || err}`); process.exit(1); });
1883
+ } else if (command === 'rainmaker') {
1884
+ const code = require('../commands/rainmaker').rainmakerCommand(process.argv.slice(3));
1885
+ process.exit(typeof code === 'number' ? code : 0);
1886
+ } else if (command === 'avail') {
1887
+ Promise.resolve(require('../commands/avail').availCommand(process.argv.slice(3)))
1888
+ .then((code) => process.exit(typeof code === 'number' ? code : 0))
1889
+ .catch((err) => { console.error(`\n✗ Error: ${err.message || err}`); process.exit(1); });
1890
+ } else if (command === 'meet') {
1891
+ Promise.resolve(require('../commands/meet').meetCommand(process.argv.slice(3)))
1892
+ .then((code) => process.exit(typeof code === 'number' ? code : 0))
1893
+ .catch((err) => { console.error(`\n✗ Error: ${err.message || err}`); process.exit(1); });
1674
1894
  } else if (command === 'brain') {
1675
1895
  Promise.resolve()
1676
1896
  .then(() => require('../commands/brain').brainCommand(process.argv.slice(3)))
@@ -1710,6 +1930,14 @@ if (command === 'init') {
1710
1930
  } else {
1711
1931
  logCmd();
1712
1932
  }
1933
+ } else if (command === 'logs') {
1934
+ try {
1935
+ require('../commands/log').logsDigest(process.argv.slice(3));
1936
+ process.exit(0);
1937
+ } catch (error) {
1938
+ console.error(error.message || String(error));
1939
+ process.exit(1);
1940
+ }
1713
1941
  } else if (command === 'now') {
1714
1942
  const args = process.argv.slice(3);
1715
1943
  if (args.includes('--help') || args.includes('-h') || args[0] === 'help') {
@@ -1726,6 +1954,8 @@ if (command === 'init') {
1726
1954
  process.exit(0);
1727
1955
  }
1728
1956
  activateCmd();
1957
+ } else if (command === 'watch') {
1958
+ require('../commands/watch').watchAtris();
1729
1959
  } else if (command === 'update' || command === 'sync') {
1730
1960
  const args = process.argv.slice(3);
1731
1961
  const firstSyncArg = process.argv[3];
@@ -1756,7 +1986,10 @@ if (command === 'init') {
1756
1986
  .then(() => process.exit(0))
1757
1987
  .catch((err) => { console.error(`\n✗ Error: ${err.message || err}`); process.exit(1); });
1758
1988
  } else {
1759
- syncCmd();
1989
+ syncCmd({
1990
+ dryRun: args.includes('--dry-run'),
1991
+ force: args.includes('--force') || args.includes('--yes') || args.includes('-y'),
1992
+ });
1760
1993
  }
1761
1994
  } else if (command === 'upgrade') {
1762
1995
  const args = process.argv.slice(3);
@@ -1876,8 +2109,8 @@ if (command === 'init') {
1876
2109
  .catch((err) => { console.error(`\n✗ Error: ${err.message || err}`); process.exit(1); });
1877
2110
  } else if (command === 'youtube') {
1878
2111
  require('../commands/youtube').youtubeCommand(process.argv.slice(3))
1879
- .then(() => process.exit(0))
1880
- .catch((err) => { console.error(`\n✗ Error: ${err.message || err}`); process.exit(1); });
2112
+ .then(() => exitWhenFlushed(0))
2113
+ .catch((err) => { console.error(`\n✗ Error: ${err.message || err}`); exitWhenFlushed(1); });
1881
2114
  } else if (command === 'run') {
1882
2115
  const args = process.argv.slice(3);
1883
2116
  if (args[0] === 'logs') {
@@ -2100,15 +2333,36 @@ if (command === 'init') {
2100
2333
  console.error(`✗ Brainstorm failed: ${error.message || error}`);
2101
2334
  process.exit(1);
2102
2335
  });
2103
- } else if (command === 'next' || command === 'atris') {
2336
+ } else if (command === 'next') {
2337
+ Promise.resolve(require('../commands/next').nextCommand(process.argv.slice(3)))
2338
+ .then((code) => process.exit(typeof code === 'number' ? code : 0))
2339
+ .catch((error) => {
2340
+ console.error(`✗ Error: ${error.message || error}`);
2341
+ process.exit(1);
2342
+ });
2343
+ } else if (command === 'dream') {
2344
+ Promise.resolve(require('../commands/dream').dreamCommand(process.argv.slice(3)))
2345
+ .then((code) => process.exit(typeof code === 'number' ? code : 0))
2346
+ .catch((error) => {
2347
+ console.log('No dreams tonight: could not finish dream');
2348
+ console.log('Run me nightly: atris dream');
2349
+ process.exit(0);
2350
+ });
2351
+ } else if (command === 'atris') {
2104
2352
  const rawArgs = process.argv.slice(3);
2105
2353
  if (rawArgs.includes('--help') || rawArgs.includes('-h') || rawArgs[0] === 'help') {
2106
2354
  showNextHelp(command);
2107
2355
  process.exit(0);
2108
2356
  }
2109
- const userInput = rawArgs.filter((arg) => !arg.startsWith('-')).join(' ').trim();
2110
- interactiveEntry(userInput || null)
2111
- .then(() => process.exit(0))
2357
+ const natural = parseNaturalEntryArgs(rawArgs);
2358
+ interactiveEntry(natural.input || null, {
2359
+ oneLap: Boolean(natural.input),
2360
+ asJson: natural.asJson,
2361
+ engine: natural.engine,
2362
+ verifier: natural.verifier,
2363
+ optionError: natural.error,
2364
+ })
2365
+ .then((code) => process.exit(Number.isInteger(code) ? code : 0))
2112
2366
  .catch((error) => {
2113
2367
  console.error(`✗ Error: ${error.message || error}`);
2114
2368
  process.exit(1);
@@ -2194,6 +2448,26 @@ if (command === 'init') {
2194
2448
  showVerifyHelp();
2195
2449
  process.exit(0);
2196
2450
  }
2451
+ if (args[0] === 'artifact') {
2452
+ const target = args[1] && !args[1].startsWith('--') ? args[1] : null;
2453
+ if (!target) {
2454
+ showVerifyHelp();
2455
+ process.exit(2);
2456
+ }
2457
+ const readValue = (flag) => {
2458
+ const idx = args.indexOf(flag);
2459
+ return idx > 0 && args[idx + 1] ? args[idx + 1] : null;
2460
+ };
2461
+ const minLinesRaw = readValue('--min-lines');
2462
+ const maxAgeRaw = readValue('--max-age-hours');
2463
+ const code = require('../commands/verify').verifyArtifact(target, {
2464
+ objective: readValue('--objective') || undefined,
2465
+ minLines: minLinesRaw !== null ? Number(minLinesRaw) : undefined,
2466
+ maxAgeHours: maxAgeRaw !== null ? Number(maxAgeRaw) : undefined,
2467
+ json: args.includes('--json'),
2468
+ });
2469
+ process.exit(code);
2470
+ }
2197
2471
  const sectionIdx = process.argv.indexOf('--section');
2198
2472
  if (sectionIdx > 0 && process.argv[sectionIdx + 1]) {
2199
2473
  const slug = process.argv[3] && !process.argv[3].startsWith('--') ? process.argv[3] : null;
@@ -2214,12 +2488,24 @@ if (command === 'init') {
2214
2488
  .then(() => process.exit(0))
2215
2489
  .catch((err) => { console.error(`\n✗ Error: ${err.message || err}`); process.exit(1); });
2216
2490
  } else if (command === 'search') {
2217
- const keyword = process.argv.slice(3).join(' ');
2218
- searchJournal(keyword);
2491
+ const code = require('../commands/search').searchCommand(process.argv.slice(3));
2492
+ process.exitCode = code;
2493
+ } else if (command === 'scout') {
2494
+ require('../commands/scout').scoutCommand(process.argv.slice(3))
2495
+ .then((code) => { process.exitCode = code; })
2496
+ .catch((err) => { console.error(`✗ Error: ${err.message || err}`); process.exit(1); });
2219
2497
  } else if (command === 'xp') {
2220
2498
  require('../commands/xp').xpCommand(...process.argv.slice(3))
2221
2499
  .then(() => { process.exitCode = 0; })
2222
2500
  .catch((err) => { console.error(`✗ Error: ${err.message || err}`); process.exit(1); });
2501
+ } else if (command === 'report') {
2502
+ const args = process.argv.slice(3);
2503
+ const { reportCommand, showReportHelp } = require('../commands/report');
2504
+ if (args.includes('--help') || args.includes('-h')) {
2505
+ showReportHelp();
2506
+ process.exit(0);
2507
+ }
2508
+ process.exit(reportCommand(args));
2223
2509
  } else if (command === 'play') {
2224
2510
  require('../commands/play').playCommand(...process.argv.slice(3))
2225
2511
  .then(() => process.exit(0))
@@ -2334,10 +2620,15 @@ if (command === 'init') {
2334
2620
  require('../commands/align').alignAtris()
2335
2621
  .then(() => process.exit(0))
2336
2622
  .catch((err) => { console.error(`\n✗ Error: ${err.message || err}`); process.exit(1); });
2623
+ } else if (command === 'cloud') {
2624
+ require('../commands/cloud').cloudAtris();
2337
2625
  } else if (command === 'terminal') {
2338
2626
  require('../commands/terminal').terminalAtris()
2339
2627
  .then(() => process.exit(0))
2340
2628
  .catch((err) => { console.error(`\n✗ Error: ${err.message || err}`); process.exit(1); });
2629
+ } else if (command === 'fleet-report') {
2630
+ require('../commands/fleet-report').fleetReport()
2631
+ .catch((err) => { console.error(`\n✗ Error: ${err.message || err}`); process.exit(1); });
2341
2632
  } else if (command === 'x') {
2342
2633
  // Fast Agent SDK execution - like "atris x echo hello" or "atris x ls -la"
2343
2634
  const userInput = process.argv.slice(3).join(' ').trim();
@@ -2385,10 +2676,19 @@ if (command === 'init') {
2385
2676
  require('../commands/fleet').fleet(args)
2386
2677
  .then(() => process.exit(0))
2387
2678
  .catch((err) => { console.error(`\n✗ Error: ${err.message || err}`); process.exit(1); });
2388
- } else if (command === 'loops') {
2679
+ } else if (command === 'loops' || command === 'self-improve') {
2389
2680
  try {
2390
- require('../commands/loops').loopsCommand(process.argv[3], ...process.argv.slice(4));
2391
- process.exit(0);
2681
+ const loops = require('../commands/loops');
2682
+ const aliasArgs = process.argv.slice(3);
2683
+ const aliasWantsHelp = ['help', '--help', '-h'].includes(aliasArgs[0]);
2684
+ const subcommand = command === 'self-improve'
2685
+ ? (aliasWantsHelp ? aliasArgs[0] : 'init')
2686
+ : process.argv[3];
2687
+ const args = command === 'self-improve'
2688
+ ? (aliasWantsHelp ? aliasArgs.slice(1) : aliasArgs)
2689
+ : process.argv.slice(4);
2690
+ const exitCode = loops.loopsCommand(subcommand, ...args);
2691
+ process.exit(typeof exitCode === 'number' ? exitCode : 0);
2392
2692
  } catch (error) {
2393
2693
  console.error(`\n✗ Error: ${error.message || error}`);
2394
2694
  process.exit(1);
@@ -2527,6 +2827,15 @@ if (command === 'init') {
2527
2827
  Promise.resolve(require('../commands/card').run(process.argv.slice(3)))
2528
2828
  .then((code) => process.exit(typeof code === 'number' ? code : 0))
2529
2829
  .catch((err) => { console.error(`\n✗ Error: ${err.message || err}`); process.exit(1); });
2830
+ } else if (command === 'brief') {
2831
+ // Brief: one-glance operator surface for landings, waits, and next moves.
2832
+ Promise.resolve(require('../commands/brief').run(process.argv.slice(3)))
2833
+ .then((code) => process.exit(typeof code === 'number' ? code : 0))
2834
+ .catch((err) => { console.error(`\n✗ Error: ${err.message || err}`); process.exit(1); });
2835
+ } else if (command === 'pack') {
2836
+ Promise.resolve(require('../commands/pack').run(process.argv.slice(3)))
2837
+ .then((code) => process.exit(typeof code === 'number' ? code : 0))
2838
+ .catch((err) => { console.error(`\n✗ Error: ${err.message || err}`); process.exit(1); });
2530
2839
  } else if (command === 'reel') {
2531
2840
  // Reel: one line of text into a short on-brand video (an animated card; frames via Chrome + ffmpeg).
2532
2841
  Promise.resolve(require('../commands/reel').run(process.argv.slice(3)))