atris 3.56.0 → 3.56.2

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 (45) hide show
  1. package/atris/skills/engines/SKILL.md +11 -3
  2. package/atris/skills/x-search/SKILL.md +20 -1
  3. package/atris/skills/youtube/SKILL.md +51 -43
  4. package/bin/atris.js +134 -90
  5. package/commands/auth.js +6 -1
  6. package/commands/autopilot-front.js +29 -13
  7. package/commands/brainstorm.js +77 -476
  8. package/commands/business.js +13 -1
  9. package/commands/engine.js +7 -0
  10. package/commands/experiments.js +178 -0
  11. package/commands/fleet-report.js +2 -2
  12. package/commands/founder.js +12 -0
  13. package/commands/human-missions.js +64 -2
  14. package/commands/init.js +2 -35
  15. package/commands/integrations.js +100 -0
  16. package/commands/land.js +53 -23
  17. package/commands/later.js +52 -0
  18. package/commands/launchpad.js +45 -10
  19. package/commands/log.js +79 -30
  20. package/commands/mission.js +117 -22
  21. package/commands/next.js +153 -63
  22. package/commands/now.js +115 -38
  23. package/commands/recap.js +97 -4
  24. package/commands/run-front.js +20 -5
  25. package/commands/spaceship.js +52 -12
  26. package/commands/status.js +38 -7
  27. package/commands/task.js +56 -19
  28. package/commands/terminal.js +5 -5
  29. package/commands/workflow.js +19 -5
  30. package/commands/x-search.js +123 -13
  31. package/commands/youtube.js +941 -36
  32. package/lib/account-bound.js +7 -0
  33. package/lib/apply-gate.js +102 -0
  34. package/lib/config-guard.js +106 -0
  35. package/lib/context-gatherer.js +55 -8
  36. package/lib/engine-ask.js +16 -6
  37. package/lib/engine-registry.js +14 -4
  38. package/lib/first-minute.js +571 -26
  39. package/lib/known-commands.js +1 -1
  40. package/lib/pack-capabilities.js +13 -3
  41. package/lib/runner-command.js +5 -3
  42. package/lib/scratch-root.js +44 -0
  43. package/lib/workspace-scaffold.js +3 -1
  44. package/package.json +1 -1
  45. package/utils/auth.js +84 -6
package/commands/land.js CHANGED
@@ -1,6 +1,7 @@
1
1
  const fs = require('fs');
2
2
  const path = require('path');
3
3
  const { runGit } = require('../lib/git-spawn');
4
+ const { compactErrorPayload, printCliJson } = require('../lib/cli-json');
4
5
  const { listWorktrees, statusCounts } = require('./worktree');
5
6
 
6
7
  const DEFAULT_TTL_DAYS = 7;
@@ -236,14 +237,18 @@ function collectBoard(root, { ttlDays = DEFAULT_TTL_DAYS, staleHours = DEFAULT_S
236
237
  // merged branch residue.
237
238
  function landSummary(cwd = process.cwd(), ttlDays = DEFAULT_TTL_DAYS) {
238
239
  const root = repoRoot(cwd);
239
- if (!root) return null;
240
- const board = collectBoard(root, { ttlDays, light: true });
241
- return {
242
- branches: board.summary.active + board.summary.due,
243
- due: board.summary.due,
244
- stale: board.summary.stale,
245
- ttlDays,
246
- };
240
+ if (!root || !hasCommits(root)) return null;
241
+ try {
242
+ const board = collectBoard(root, { ttlDays, light: true });
243
+ return {
244
+ branches: board.summary.active + board.summary.due,
245
+ due: board.summary.due,
246
+ stale: board.summary.stale,
247
+ ttlDays,
248
+ };
249
+ } catch {
250
+ return null;
251
+ }
247
252
  }
248
253
 
249
254
  function salvageDir(root) {
@@ -636,6 +641,40 @@ function readFollowingFlag(args, name, fallback = '') {
636
641
  return args[idx + 1] || fallback;
637
642
  }
638
643
 
644
+ const EMPTY_LAND = {
645
+ not_a_git_repo: 'this folder is not a git repo yet, so there is nothing to land.',
646
+ no_commits: 'nothing is in the air yet because this folder has no commits.',
647
+ no_base: 'no master or main branch yet, so there is nothing to land.',
648
+ };
649
+
650
+ function emptyLandKindFromError(err) {
651
+ if (err && err.code === 'LAND_NO_BASE') return 'no_base';
652
+ const msg = String(err && err.message || err || '');
653
+ if (/not a git/i.test(msg)) return 'not_a_git_repo';
654
+ if (/no commits/i.test(msg)) return 'no_commits';
655
+ if (/no master\/main/i.test(msg)) return 'no_base';
656
+ return '';
657
+ }
658
+
659
+ function printEmptyLand(kind, json) {
660
+ const reason = EMPTY_LAND[kind] ? kind : 'no_base';
661
+ const detail = EMPTY_LAND[reason];
662
+ if (json) {
663
+ const payload = compactErrorPayload({ reason, detail });
664
+ printCliJson(payload, payload, ['--json']);
665
+ } else {
666
+ console.log(detail);
667
+ }
668
+ return 2;
669
+ }
670
+
671
+ function finishLandError(err, json) {
672
+ const kind = emptyLandKindFromError(err);
673
+ if (kind) return printEmptyLand(kind, json);
674
+ console.error(String(err && err.message || err).split('\n')[0]);
675
+ return 2;
676
+ }
677
+
639
678
  function showHelp() {
640
679
  console.log('');
641
680
  console.log('atris land: the landing, what is actually done vs still in the air');
@@ -666,20 +705,14 @@ function landCommand(args = []) {
666
705
  showHelp();
667
706
  return 0;
668
707
  }
708
+ const json = args.includes('--json');
669
709
  const root = repoRoot();
670
- if (!root) {
671
- console.error('not a git repository. fix: git init -b main');
672
- return 1;
673
- }
674
- if (!hasCommits(root)) {
675
- console.error('no commits yet. fix: git commit --allow-empty -m init');
676
- return 1;
677
- }
710
+ if (!root) return printEmptyLand('not_a_git_repo', json);
711
+ if (!hasCommits(root)) return printEmptyLand('no_commits', json);
678
712
  const ttlRaw = readFollowingFlag(args, '--ttl', '');
679
713
  const ttlParsed = Number(ttlRaw);
680
714
  const ttlDays = ttlRaw !== '' && Number.isFinite(ttlParsed) && ttlParsed >= 0 ? ttlParsed : DEFAULT_TTL_DAYS;
681
715
  const base = readFollowingFlag(args, '--base', '');
682
- const json = args.includes('--json');
683
716
 
684
717
  if (args.includes('--reap')) {
685
718
  try {
@@ -688,8 +721,7 @@ function landCommand(args = []) {
688
721
  else printReceipt(receipt);
689
722
  return 0;
690
723
  } catch (err) {
691
- console.error(String(err && err.message || err));
692
- return 1;
724
+ return finishLandError(err, json);
693
725
  }
694
726
  }
695
727
 
@@ -706,8 +738,7 @@ function landCommand(args = []) {
706
738
  else printStory(story);
707
739
  return 0;
708
740
  } catch (err) {
709
- console.error(String(err && err.message || err));
710
- return 1;
741
+ return finishLandError(err, json);
711
742
  }
712
743
  }
713
744
 
@@ -717,8 +748,7 @@ function landCommand(args = []) {
717
748
  else printBoard(board);
718
749
  return 0;
719
750
  } catch (err) {
720
- console.error(String(err && err.message || err));
721
- return 1;
751
+ return finishLandError(err, json);
722
752
  }
723
753
  }
724
754
 
@@ -0,0 +1,52 @@
1
+ 'use strict';
2
+
3
+ const { argsWantHelp } = require('../lib/noninteractive');
4
+ const {
5
+ laterNotePath,
6
+ listUserVisibleWork,
7
+ personName,
8
+ renderLaterRemember,
9
+ spokenLaterNote,
10
+ writeLaterNote,
11
+ } = require('../lib/first-minute');
12
+
13
+ function laterUsage() {
14
+ return 'Usage: atris later "<sentence>"';
15
+ }
16
+
17
+ function laterSentence(args = []) {
18
+ return spokenLaterNote((Array.isArray(args) ? args : [])
19
+ .filter((arg) => !String(arg).startsWith('-'))
20
+ .join(' '));
21
+ }
22
+
23
+ function laterAtris(args = process.argv.slice(3), {
24
+ root = process.cwd(),
25
+ log = console.log,
26
+ } = {}) {
27
+ const list = Array.isArray(args) ? args : [];
28
+ if (argsWantHelp(list)) {
29
+ log(laterUsage());
30
+ return 0;
31
+ }
32
+ const sentence = laterSentence(list);
33
+ if (!sentence) {
34
+ log(laterUsage());
35
+ return 2;
36
+ }
37
+ writeLaterNote(root, sentence);
38
+ const files = listUserVisibleWork(root);
39
+ log('');
40
+ log(renderLaterRemember({
41
+ person: personName(),
42
+ sentence,
43
+ files,
44
+ root,
45
+ }));
46
+ return 0;
47
+ }
48
+
49
+ module.exports = {
50
+ laterAtris,
51
+ laterNotePath,
52
+ };
@@ -3,6 +3,14 @@
3
3
  const fs = require('fs');
4
4
  const path = require('path');
5
5
  const { hasFlag, readFlag } = require('../lib/arg-parser');
6
+ const {
7
+ isFreshWorkspace,
8
+ isKeepWorkingMinute,
9
+ isClaimMinute,
10
+ buildFirstMinute,
11
+ speakFirstMinute,
12
+ speakKeepWorkingMinute,
13
+ } = require('../lib/first-minute');
6
14
 
7
15
  const ACTIVE_MISSION_STATUSES = new Set(['planning', 'running', 'ready']);
8
16
 
@@ -185,15 +193,18 @@ function loadTaskProjection(root) {
185
193
  };
186
194
  }
187
195
 
188
- function chooseNextAction({ root, actor, tasks, missions, brain, endgame }) {
189
- if (!fs.existsSync(path.join(root, 'atris'))) {
190
- return {
191
- kind: 'bootstrap',
192
- label: 'Initialize this workspace',
193
- command: 'atris init',
194
- why: 'No atris/ directory exists yet.',
195
- };
196
+ function hasLiveKeepWorkingRun(root = process.cwd()) {
197
+ try {
198
+ const { pickLiveLocalMission } = require('./mission');
199
+ return Boolean(pickLiveLocalMission(root));
200
+ } catch {
201
+ return false;
196
202
  }
203
+ }
204
+
205
+ function chooseNextAction({ actor, tasks, missions, brain, endgame }) {
206
+ // No-room folders belong to first-minute, not factory init.
207
+ // launchpadCommand speaks those two lines before this ranking runs.
197
208
 
198
209
  const ownClaimed = newest(tasks.filter(task => (
199
210
  task.status === 'claimed'
@@ -609,8 +620,32 @@ function launchpadCommand(args = []) {
609
620
  showLaunchpadHelp();
610
621
  return 0;
611
622
  }
612
- const payload = collectLaunchpad(process.cwd(), args);
613
- if (hasFlag(args, '--json')) {
623
+ const asJson = hasFlag(args, '--json');
624
+ const root = process.cwd();
625
+
626
+ // Fresh folder: empty talks first-talk. A file already here
627
+ // names that file, same as bare atris. Do not mint a room.
628
+ if (isFreshWorkspace(root)) {
629
+ return speakFirstMinute({ root, fresh: true, asJson });
630
+ }
631
+
632
+ // Just-minted file folder, nothing running: same two lines as
633
+ // first-minute / the next keep-working ready. After init, next is claim: same
634
+ // two lines as bare atris / status / now. Not factory init.
635
+ // A live mission still gets the board. --json on the claim
636
+ // path keeps the factory card for scripts.
637
+ if (!hasLiveKeepWorkingRun(root)) {
638
+ const minute = buildFirstMinute({ root });
639
+ if (isKeepWorkingMinute(minute)) {
640
+ return speakKeepWorkingMinute({ root, asJson });
641
+ }
642
+ if (!asJson && isClaimMinute(minute)) {
643
+ return speakFirstMinute({ root });
644
+ }
645
+ }
646
+
647
+ const payload = collectLaunchpad(root, args);
648
+ if (asJson) {
614
649
  console.log(JSON.stringify(payload, null, 2));
615
650
  } else {
616
651
  process.stdout.write(renderLaunchpad(payload));
package/commands/log.js CHANGED
@@ -2,6 +2,7 @@ const fs = require('fs');
2
2
  const path = require('path');
3
3
  const readline = require('readline');
4
4
  const { getLogPath, ensureLogDirectory, createLogFile, addInboxIdea } = require('../lib/file-ops');
5
+ const { isFreshWorkspace, speakFirstMinute } = require('../lib/first-minute');
5
6
  const { isForcedNonInteractive } = require('../lib/noninteractive');
6
7
 
7
8
  function readPipedStdin() {
@@ -12,42 +13,89 @@ function readPipedStdin() {
12
13
  }
13
14
  }
14
15
 
15
- function logAtris() {
16
- const targetDir = path.join(process.cwd(), 'atris');
16
+ function journalRel(logFile) {
17
+ return (path.relative(process.cwd(), logFile) || logFile).split(path.sep).join('/');
18
+ }
19
+
20
+ function printLogJson(payload) {
21
+ console.log(JSON.stringify(payload, null, 2));
22
+ }
17
23
 
18
- if (!fs.existsSync(targetDir)) {
19
- console.error('✗ Error: atris/ folder not found. Run "atris init" first.');
20
- process.exit(1);
24
+ function printReplNeeded(asJson, dateFormatted, rel) {
25
+ const error = `Daily log REPL needs a terminal (${dateFormatted}).`;
26
+ if (asJson) {
27
+ printLogJson({
28
+ ok: false,
29
+ error,
30
+ journal: rel,
31
+ next_command: 'atris log "note"',
32
+ });
33
+ return;
21
34
  }
35
+ console.log(error);
36
+ console.log(`journal: ${rel}`);
37
+ console.log('Next: atris log --repl # in a terminal, or atris log "note" / atris wish --json --no-mission');
38
+ }
22
39
 
23
- ensureLogDirectory();
24
- const { logFile, dateFormatted } = getLogPath();
40
+ function captureNotes(logFile, notes) {
41
+ return notes.map((note) => addInboxIdea(logFile, note));
42
+ }
25
43
 
26
- if (!fs.existsSync(logFile)) {
27
- createLogFile(logFile, dateFormatted);
44
+ function printCapture(asJson, ids, notes, rel) {
45
+ if (asJson) {
46
+ const payload = {
47
+ ok: true,
48
+ action: 'inbox_capture',
49
+ journal: rel,
50
+ next_command: 'atris logs',
51
+ };
52
+ if (notes.length === 1) {
53
+ payload.id = `I${ids[0]}`;
54
+ payload.note = notes[0];
55
+ } else {
56
+ payload.count = notes.length;
57
+ payload.ids = ids.map((id) => `I${id}`);
58
+ payload.notes = notes;
59
+ }
60
+ printLogJson(payload);
61
+ return;
62
+ }
63
+ if (notes.length === 1) {
64
+ console.log(`captured I${ids[0]}: ${notes[0]}`);
65
+ console.log(`journal: ${rel}`);
66
+ console.log('Next: atris logs');
67
+ return;
28
68
  }
69
+ console.log(`captured ${notes.length} note${notes.length === 1 ? '' : 's'} in ${rel}`);
70
+ }
29
71
 
72
+ function logAtris() {
73
+ const root = process.cwd();
30
74
  const args = process.argv.slice(3);
31
- const rel = path.relative(process.cwd(), logFile) || logFile;
75
+ const asJson = args.includes('--json');
32
76
  const forced = isForcedNonInteractive(args);
33
77
  const wantsRepl = args.includes('--repl');
34
78
  const positional = args.filter((arg) => !String(arg).startsWith('-'));
35
79
  const note = positional.join(' ').trim();
36
80
 
37
- // `atris log "sentence"` (or multi-word args) appends to today's Inbox.
38
- if (note && !wantsRepl) {
39
- const id = addInboxIdea(logFile, note);
40
- console.log(`captured I${id}: ${note}`);
41
- console.log(`journal: ${rel}`);
42
- console.log('Next: atris logs');
43
- return;
81
+ // Empty folder talks like bare atris. Do not create atris/ or write
82
+ // a journal. After init, a note still captures.
83
+ if (isFreshWorkspace(root)) {
84
+ process.exit(speakFirstMinute({ root, fresh: true, asJson }));
44
85
  }
45
86
 
46
- // Forced headless: never open a REPL.
47
- if (forced) {
48
- console.log(`Daily log REPL needs a terminal (${dateFormatted}).`);
49
- console.log(`journal: ${rel}`);
50
- console.log('Next: atris log --repl # in a terminal, or atris log "note" / atris wish --json --no-mission');
87
+ ensureLogDirectory();
88
+ const { logFile, dateFormatted } = getLogPath();
89
+
90
+ if (!fs.existsSync(logFile)) {
91
+ createLogFile(logFile, dateFormatted);
92
+ }
93
+
94
+ const rel = journalRel(logFile);
95
+
96
+ // `atris log "sentence"` (or one slug-like word) appends to today's Inbox.
97
+ if (note && !wantsRepl) {
98
+ printCapture(asJson, captureNotes(logFile, [note]), [note], rel);
51
99
  return;
52
100
  }
53
101
 
@@ -56,16 +104,17 @@ function logAtris() {
56
104
  const piped = readPipedStdin();
57
105
  const lines = piped.split(/\r?\n/).map((line) => line.trim()).filter(Boolean);
58
106
  const notes = lines.filter((line) => line.toLowerCase() !== 'exit');
59
- if (notes.length === 0) {
60
- console.log(`Daily log REPL needs a terminal (${dateFormatted}).`);
61
- console.log(`journal: ${rel}`);
62
- console.log('Next: atris log --repl # in a terminal, or atris log "note" / atris logs --json');
107
+ if (notes.length > 0 && !wantsRepl) {
108
+ printCapture(asJson, captureNotes(logFile, notes), notes, rel);
63
109
  return;
64
110
  }
65
- for (const noteLine of notes) {
66
- addInboxIdea(logFile, noteLine);
67
- }
68
- console.log(`captured ${notes.length} note${notes.length === 1 ? '' : 's'} in ${rel}`);
111
+ printReplNeeded(asJson, dateFormatted, rel);
112
+ return;
113
+ }
114
+
115
+ // Forced headless: never open a REPL.
116
+ if (forced) {
117
+ printReplNeeded(asJson, dateFormatted, rel);
69
118
  return;
70
119
  }
71
120
 
@@ -983,6 +983,62 @@ function hasLocalMissionState(root = process.cwd()) {
983
983
  .some((mission) => mission && mission.id && mission.cloud !== true);
984
984
  }
985
985
 
986
+ const LIVE_MISSION_TICK_MS = 24 * 60 * 60 * 1000;
987
+
988
+ function isLiveInFlightMission(mission, now = Date.now(), root = process.cwd()) {
989
+ if (!mission || !mission.id || mission.cloud === true) return false;
990
+ const status = String(mission.status || '');
991
+ if (TERMINAL_STATUSES.has(status) || status === 'paused' || status === 'blocked') return false;
992
+ if (status === 'running') return true;
993
+ const lastTickAt = Date.parse(mission.last_tick_at || '');
994
+ if (!Number.isFinite(lastTickAt)) return false;
995
+ const age = now - lastTickAt;
996
+ if (!(age >= 0 && age <= LIVE_MISSION_TICK_MS)) return false;
997
+ // ready/planning with a recent receipt is still parked unless a driver is
998
+ // alive. A stalled ready row is the archive, not the live card.
999
+ return missionDriverHealth(mission, root).alive === true;
1000
+ }
1001
+
1002
+ function pickLiveLocalMission(root = process.cwd()) {
1003
+ return listMissions(root).find((mission) => isLiveInFlightMission(mission, Date.now(), root)) || null;
1004
+ }
1005
+
1006
+ function wantsBareMissionArchive(args = []) {
1007
+ return hasFlag(args, '--all')
1008
+ && args.every((value) => value === '--all' || value === '--json');
1009
+ }
1010
+
1011
+ function missionStatusRef(args = []) {
1012
+ return stripKnownFlags(args, ['--status', '--limit'], ['--json', '--local', '--all'])[0] || '';
1013
+ }
1014
+
1015
+ function wantsSpokenMissionStatus(args = []) {
1016
+ if (hasFlag(args, '--all') || hasFlag(args, '--local') || hasFlag(args, '--cloud')) return false;
1017
+ if (readFlag(args, '--status', '') || readFlag(args, '--limit', '')) return false;
1018
+ return !missionStatusRef(args);
1019
+ }
1020
+
1021
+ function speakMissionDoor(args = [], root = resolveWorkspaceRoot()) {
1022
+ const asJson = args.includes('--json');
1023
+ const live = pickLiveLocalMission(root);
1024
+ if (live) {
1025
+ return statusMission(asJson ? [live.id, '--json'] : [live.id]);
1026
+ }
1027
+ if (isFreshWorkspace(root) || hasLocalMissionState(root)) {
1028
+ const code = speakFirstMinute({
1029
+ root,
1030
+ fresh: isFreshWorkspace(root),
1031
+ asJson,
1032
+ });
1033
+ process.exitCode = code;
1034
+ return code;
1035
+ }
1036
+ return require('./human-missions').currentMissionCommand(args, {
1037
+ root,
1038
+ fallbackOnMissing: true,
1039
+ });
1040
+ }
1041
+
986
1042
  function terminalNextAction(status) {
987
1043
  if (status === 'complete') return 'mission complete';
988
1044
  if (status === 'stopped') return 'mission stopped';
@@ -4811,9 +4867,16 @@ function statusMission(args) {
4811
4867
  console.log('Use --status active for planning, running, ready, paused, and blocked missions.');
4812
4868
  return;
4813
4869
  }
4870
+ const root = resolveWorkspaceRoot();
4871
+ if (isFreshWorkspace(root) && !missionStatusRef(args)) {
4872
+ return speakMissionDoor(args, root);
4873
+ }
4874
+ if (wantsSpokenMissionStatus(args)) {
4875
+ return speakMissionDoor(args, root);
4876
+ }
4814
4877
  const asJson = wantsJson(args);
4815
4878
  const localOnly = hasFlag(args, '--local');
4816
- const ref = stripKnownFlags(args, ['--status', '--limit'], ['--json', '--local'])[0] || '';
4879
+ const ref = missionStatusRef(args);
4817
4880
  const statusFilter = readFlag(args, '--status', '');
4818
4881
  if (statusFilter && !VALID_STATUSES.has(statusFilter) && !STATUS_ALIASES.has(statusFilter)) {
4819
4882
  exitMissionError(`Invalid --status: ${statusFilter}`, 2, asJson);
@@ -4871,7 +4934,7 @@ function statusMission(args) {
4871
4934
  ...missionStatusLandingLines(mission.last_landing),
4872
4935
  ...(completionGateLabel(mission.completion_gate) ? [` gate: ${completionGateLabel(mission.completion_gate)}`] : []),
4873
4936
  ])
4874
- : ['No missions yet. Run: atris mission start "..." --owner <member>'],
4937
+ : ['No missions yet.'],
4875
4938
  asJson,
4876
4939
  );
4877
4940
  }
@@ -9014,7 +9077,7 @@ async function parseAndValidateMissionRunPhase(args) {
9014
9077
  error: null,
9015
9078
  };
9016
9079
  if (hasFlag(args, '--help') || hasFlag(args, '-h')) {
9017
- help();
9080
+ console.log('Usage: atris mission run <id|objective> [--max-ticks 4] [--max-wall 3600]');
9018
9081
  context.handled = true;
9019
9082
  return context;
9020
9083
  }
@@ -10933,10 +10996,36 @@ async function goalLoopMission(args) {
10933
10996
  if (completedTaskClosed) process.exitCode = 2;
10934
10997
  }
10935
10998
 
10936
- function help() {
10999
+ function help(args = []) {
11000
+ if (hasFlag(args, '--full')) {
11001
+ helpFull();
11002
+ return;
11003
+ }
11004
+ console.log(`
11005
+ Usage: atris mission
11006
+
11007
+ Keep working on one goal.
11008
+
11009
+ atris mission what's in front of you
11010
+ atris mission start begin one goal
11011
+ atris mission status same as atris mission
11012
+ atris mission stop stop the live goal
11013
+ atris mission list every saved goal
11014
+ atris mission inspect <id> --fields status,runner,ack,pings
11015
+
11016
+ Hours of keep-working: atris spaceship
11017
+ Keep going until you stop: atris autopilot
11018
+
11019
+ More flags: atris mission help --full
11020
+ `.trim());
11021
+ }
11022
+
11023
+ function helpFull() {
10937
11024
  console.log(`
10938
11025
  atris mission - durable goal + loop + owner + proof state
10939
11026
 
11027
+ atris mission Same next as bare atris, or the one live mission
11028
+ atris mission list | --all Full mission archive
10940
11029
  atris mission start "<objective>" --owner <member> [--destination "<text>"] [--verify "..."] [--always-on] [--budget quick|long|deep] [--xp-task] [--worktree] [--take-goal-slot]
10941
11030
  [--runner manual|claude|atris2|codex_goal] [--model <id>]
10942
11031
  (runner claude spawns local claude -p per tick, --model passes through;
@@ -11372,23 +11461,20 @@ function missionCommand(args) {
11372
11461
  if (args[0] === 'answer') {
11373
11462
  return require('./human-missions').answerCommand(args.slice(1));
11374
11463
  }
11375
- if (isBareMission && !hasLocalMissionState(resolveWorkspaceRoot())) {
11464
+ if (isBareMission) {
11465
+ // Empty folder talks like bare atris. A live in-flight mission shows
11466
+ // that one card. Completed, stopped, stalled, or ready-with-no-driver
11467
+ // rows stay in the archive. After init with no live mission, speak the
11468
+ // desk next.
11469
+ return speakMissionDoor(args);
11470
+ }
11471
+ if (wantsBareMissionArchive(args)) {
11376
11472
  const root = resolveWorkspaceRoot();
11377
- // Empty folder talks like bare atris. After init, stay on the current
11378
- // card or an honest no-mission, and do not bounce back to init.
11379
- if (isFreshWorkspace(root)) {
11380
- const code = speakFirstMinute({
11381
- root,
11382
- fresh: true,
11383
- asJson: args.includes('--json'),
11384
- });
11385
- process.exitCode = code;
11386
- return code;
11387
- }
11388
- return require('./human-missions').currentMissionCommand(args);
11473
+ if (isFreshWorkspace(root)) return speakMissionDoor(args, root);
11474
+ return statusMission(args);
11389
11475
  }
11390
- const subcommand = isBareMission ? 'status' : (args[0] || 'status');
11391
- const rest = isBareMission ? args : args.slice(1);
11476
+ const subcommand = args[0] || 'status';
11477
+ const rest = args.slice(1);
11392
11478
  // Every mission verb resolves its state store from process.cwd(). Running one
11393
11479
  // from a subdirectory used to create a nested .atris store the fleet never
11394
11480
  // reads (proven footgun: a nested .atris appeared under
@@ -11404,8 +11490,6 @@ function missionCommand(args) {
11404
11490
  case 'new':
11405
11491
  return startMission(rest);
11406
11492
  case 'status':
11407
- case 'list':
11408
- case 'ls':
11409
11493
  case 'show':
11410
11494
  case 'info':
11411
11495
  case 'view':
@@ -11416,6 +11500,16 @@ function missionCommand(args) {
11416
11500
  });
11417
11501
  }
11418
11502
  return statusMission(rest);
11503
+ case 'list':
11504
+ case 'ls': {
11505
+ if (hasFlag(rest, '--help') || hasFlag(rest, '-h') || String(rest[0] || '').trim() === 'help') {
11506
+ console.log('Usage: atris mission list [--status <state>] [--limit <n>] [--local] [--json]');
11507
+ return;
11508
+ }
11509
+ const listRoot = resolveWorkspaceRoot();
11510
+ if (isFreshWorkspace(listRoot)) return speakMissionDoor(rest, listRoot);
11511
+ return statusMission(rest.includes('--all') ? rest : ['--all', ...rest]);
11512
+ }
11419
11513
  case 'doctor':
11420
11514
  case 'check':
11421
11515
  return doctorMission(rest);
@@ -11469,7 +11563,7 @@ function missionCommand(args) {
11469
11563
  case 'help':
11470
11564
  case '--help':
11471
11565
  case '-h':
11472
- return help();
11566
+ return help(rest);
11473
11567
  default: {
11474
11568
  const first = String(subcommand || '');
11475
11569
  if (first && !first.startsWith('-')) {
@@ -11505,6 +11599,7 @@ module.exports = {
11505
11599
  reapPausedMissions,
11506
11600
  missionHeartbeatLines,
11507
11601
  listMissions,
11602
+ pickLiveLocalMission,
11508
11603
  freezeMissionVerifier,
11509
11604
  markMissionReviewReady,
11510
11605
  listWorktreeRollupMissions,