atris 3.56.0 → 3.56.1

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.
@@ -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,
package/commands/next.js CHANGED
@@ -1,5 +1,14 @@
1
1
  'use strict';
2
2
 
3
+ const { argsWantHelp, wantsJson } = require('../lib/noninteractive');
4
+ const {
5
+ buildFirstMinute,
6
+ folderName,
7
+ freshMinuteJson,
8
+ isFreshWorkspace,
9
+ listUserVisibleWork,
10
+ } = require('../lib/first-minute');
11
+ const { loadContext } = require('../lib/state-detection');
3
12
  const {
4
13
  claimRoadmapItem,
5
14
  nextCards,
@@ -9,70 +18,146 @@ const {
9
18
  seedInboxFromMove,
10
19
  } = require('../lib/next-moves');
11
20
 
12
- function showHelp() {
13
- console.log('');
14
- console.log('Usage: atris next [yes|no|skip]');
15
- console.log('');
16
- console.log('Shows one next move card.');
17
- console.log('');
21
+ function showHelp(log = console.log) {
22
+ log('Usage: atris next [--json]');
18
23
  return 0;
19
24
  }
20
25
 
21
- function cardLabel(card) {
22
- return String(card?.label || card?.title || '').trim();
26
+ function spokenWin(text) {
27
+ return String(text || '')
28
+ .split('\n')
29
+ .map((line) => line.trim())
30
+ .find(Boolean) || '';
23
31
  }
24
32
 
25
- function cardWhy(card) {
26
- const why = String(card?.why || '').trim();
27
- if (why) return why;
28
- const status = String(card?.status || '').trim();
29
- if (status) return status;
30
- if (card?.source === 'task' || card?.source === 'mission') return 'working';
31
- return 'new';
33
+ function minuteText(screen) {
34
+ const win = spokenWin(screen && screen.text);
35
+ const next = String(screen && screen.nextCommand || '').trim();
36
+ if (!next) return win || 'nothing is waiting.';
37
+ return `${win}\n\nnext: ${next}`;
38
+ }
39
+
40
+ function minuteJson(screen, extra = {}) {
41
+ const reason = spokenWin(screen && screen.text).replace(/\.$/, '');
42
+ const next = String((screen && screen.nextCommand) || extra.next_action || '').trim();
43
+ const fresh = extra.fresh === true;
44
+ return {
45
+ schema: 'atris.one_lap.v1',
46
+ ok: Boolean(next) && !fresh,
47
+ status: fresh ? 'stuck' : (next ? 'ok' : 'stuck'),
48
+ reason,
49
+ next_action: next,
50
+ };
51
+ }
52
+
53
+ function printMinute(screen, { asJson = false, log = console.log, fresh = false } = {}) {
54
+ if (asJson) {
55
+ log(JSON.stringify(minuteJson(screen, { fresh }), null, 2));
56
+ return 0;
57
+ }
58
+ log('');
59
+ log(minuteText(screen));
60
+ return 0;
61
+ }
62
+
63
+ function nextScreen(root) {
64
+ const fresh = isFreshWorkspace(root);
65
+ if (fresh) {
66
+ return {
67
+ fresh: true,
68
+ screen: buildFirstMinute({ root, fresh: true }),
69
+ };
70
+ }
71
+ return {
72
+ fresh: false,
73
+ screen: buildFirstMinute({
74
+ root,
75
+ fresh: false,
76
+ context: loadContext(root),
77
+ }),
78
+ };
79
+ }
80
+
81
+ function speakNext(root, { asJson = false, log = console.log } = {}) {
82
+ const { fresh, screen } = nextScreen(root);
83
+ if (asJson && fresh) {
84
+ log(JSON.stringify(freshMinuteJson(folderName(root), listUserVisibleWork(root), { root }), null, 2));
85
+ return 0;
86
+ }
87
+ return printMinute(screen, { asJson, log, fresh });
88
+ }
89
+
90
+ function cardLabel(card) {
91
+ return String(card?.label || card?.title || '').trim();
32
92
  }
33
93
 
34
- function renderCard(card) {
35
- if (!card) return 'Nothing to do. Rest or wish.';
36
- return [
37
- cardLabel(card),
38
- `Why now: ${cardWhy(card)}`,
39
- 'Do it? yes / no / skip',
40
- ].join('\n');
94
+ function cardNextCommand(card) {
95
+ if (!card) return '';
96
+ const action = card.next_action || {};
97
+ const prompt = String(action.prompt || '').trim();
98
+ if (/^(atris|ax)\b/i.test(prompt)) return prompt;
99
+ if (action.type === 'mission_complete' && action.mission_id) {
100
+ if (action.proof_path) {
101
+ return `atris mission complete ${action.mission_id} --proof ${action.proof_path}`;
102
+ }
103
+ return `atris mission status ${action.mission_id}`;
104
+ }
105
+ if (action.type === 'mission_review_prompt' && action.mission_id) {
106
+ return `atris mission status ${action.mission_id}`;
107
+ }
108
+ if (action.type === 'wish_answer_prompt') return 'atris wish answer "your words"';
109
+ if (card.source === 'inbox') return 'atris plan';
110
+ if (card.source === 'task' || card.source === 'roadmap' || card.source === 'endgame') {
111
+ return 'atris do';
112
+ }
113
+ if (card.source === 'mission') {
114
+ const id = action.mission_id || card.ref;
115
+ return id ? `atris mission status ${id}` : 'atris mission status --status active';
116
+ }
117
+ return '';
41
118
  }
42
119
 
43
- function printCard(card) {
44
- console.log(renderCard(card));
120
+ function speakCard(card, { asJson = false, log = console.log } = {}) {
121
+ if (!card) {
122
+ return printMinute({ text: 'nothing is waiting.', nextCommand: '' }, { asJson, log });
123
+ }
124
+ const next = cardNextCommand(card);
125
+ const label = cardLabel(card) || 'this';
126
+ return printMinute({
127
+ text: `${label} is waiting.`,
128
+ nextCommand: next,
129
+ }, { asJson, log });
45
130
  }
46
131
 
47
132
  function wishPromptTitle(label) {
48
133
  return String(label || 'this').replace(/^(#\d+)\s+/, '$1: ');
49
134
  }
50
135
 
51
- function printWishAnswerPrompt(card) {
136
+ function printWishAnswerPrompt(card, log = console.log) {
52
137
  const action = card.next_action || {};
53
- console.log(`Got it, wish ${wishPromptTitle(action.label || cardLabel(card))}.`);
54
- console.log(String(action.question || 'What should be different when this wish comes true?'));
55
- console.log('Answer with: atris wish answer "your words"');
138
+ log(`Got it, wish ${wishPromptTitle(action.label || cardLabel(card))}.`);
139
+ log(String(action.question || 'What should be different when this wish comes true?'));
140
+ log('Answer with: atris wish answer "your words"');
56
141
  return 0;
57
142
  }
58
143
 
59
- function approveMove(card, root) {
144
+ function approveMove(card, root, log = console.log) {
60
145
  recordDecision(root, card, 'approve', new Date().toISOString());
61
146
  if (['roadmap', 'inbox', 'endgame'].includes(card.source)) {
62
147
  seedInboxFromMove(root, card);
63
148
  if (card.source === 'roadmap') claimRoadmapItem(root, card.title);
64
- console.log(`${cardLabel(card)} is working.`);
149
+ log(`${cardLabel(card)} is working.`);
65
150
  return 0;
66
151
  }
67
152
  const prompt = card.next_action && card.next_action.prompt;
68
- console.log(prompt || `${cardLabel(card)} is working.`);
153
+ log(prompt || `${cardLabel(card)} is working.`);
69
154
  return 0;
70
155
  }
71
156
 
72
- function completeMissionFromCard(card) {
157
+ function completeMissionFromCard(card, log = console.log) {
73
158
  const action = card.next_action || {};
74
159
  if (!action.mission_id || !action.proof_path) {
75
- console.log('Review the proof, then complete this mission.');
160
+ log('Review the proof, then complete this mission.');
76
161
  return 0;
77
162
  }
78
163
  const { completeMission } = require('./mission');
@@ -80,57 +165,62 @@ function completeMissionFromCard(card) {
80
165
  return 0;
81
166
  }
82
167
 
83
- function executeCard(card, root) {
168
+ function executeCard(card, root, log = console.log) {
84
169
  const action = card && card.next_action;
85
- if (!card) {
86
- printCard(null);
87
- return 0;
88
- }
89
- if (action?.type === 'wish_answer_prompt') return printWishAnswerPrompt(card);
90
- if (action?.type === 'mission_complete') return completeMissionFromCard(card);
170
+ if (!card) return speakCard(null, { log });
171
+ if (action?.type === 'wish_answer_prompt') return printWishAnswerPrompt(card, log);
172
+ if (action?.type === 'mission_complete') return completeMissionFromCard(card, log);
91
173
  if (action?.type === 'wish_review_prompt' || action?.type === 'mission_review_prompt') {
92
- console.log(action.prompt || 'Review the proof, then choose done or stuck.');
174
+ log(action.prompt || 'Review the proof, then choose done or stuck.');
93
175
  return 0;
94
176
  }
95
- return approveMove(card, root);
177
+ return approveMove(card, root, log);
96
178
  }
97
179
 
98
- function nextCommand(args = [], root = process.cwd()) {
99
- const action = String(args[0] || '').trim().toLowerCase();
100
- if (action === 'help' || action === '--help' || action === '-h') return showHelp();
180
+ function actionToken(args = []) {
181
+ const list = Array.isArray(args) ? args : [];
182
+ const token = list.find((arg) => {
183
+ const text = String(arg || '').trim();
184
+ if (!text || text.startsWith('-')) return false;
185
+ return true;
186
+ });
187
+ return String(token || '').trim().toLowerCase();
188
+ }
189
+
190
+ function nextCommand(args = [], root = process.cwd(), { log = console.log } = {}) {
191
+ const list = Array.isArray(args) ? args : [];
192
+ if (argsWantHelp(list)) return showHelp(log);
193
+
194
+ const asJson = wantsJson(list);
195
+ const action = actionToken(list);
196
+
197
+ if (!action || action === 'json') return speakNext(root, { asJson, log });
101
198
 
102
199
  const current = nextCards(root, 1)[0] || null;
103
- if (!action) {
104
- printCard(current);
105
- if (current?.source === 'dream') markDreamCardConsumed(root, current, new Date().toISOString(), 'dealt');
106
- return 0;
107
- }
108
200
 
109
201
  if (action === 'skip') {
110
202
  const following = current ? nextCards(root, 1, { skipIds: [current.id] })[0] : null;
111
- if (current?.source === 'dream') markDreamCardConsumed(root, current, new Date().toISOString(), 'skipped');
112
- printCard(following);
113
- if (following?.source === 'dream') markDreamCardConsumed(root, following, new Date().toISOString(), 'dealt');
114
- return 0;
203
+ if (current?.source === 'dream') {
204
+ markDreamCardConsumed(root, current, new Date().toISOString(), 'skipped');
205
+ }
206
+ if (following?.source === 'dream') {
207
+ markDreamCardConsumed(root, following, new Date().toISOString(), 'dealt');
208
+ }
209
+ return speakCard(following, { asJson, log });
115
210
  }
116
211
 
117
212
  if (action === 'no') {
118
- if (!current) {
119
- printCard(null);
120
- return 0;
121
- }
213
+ if (!current) return speakCard(null, { asJson, log });
122
214
  parkNextCard(root, current);
123
- console.log(`Parked ${cardLabel(current)}.`);
215
+ log(`Parked ${cardLabel(current)}.`);
124
216
  return 0;
125
217
  }
126
218
 
127
- if (action === 'yes') return executeCard(current, root);
219
+ if (action === 'yes') return executeCard(current, root, log);
128
220
 
129
- console.log('Say yes, no, or skip.');
130
- return 2;
221
+ return speakNext(root, { asJson, log });
131
222
  }
132
223
 
133
224
  module.exports = {
134
225
  nextCommand,
135
- renderCard,
136
226
  };
package/commands/now.js CHANGED
@@ -576,6 +576,31 @@ function refreshNowFile(root = process.cwd(), options = {}) {
576
576
  return { path: nowPath, preserved: false };
577
577
  }
578
578
 
579
+ function stripAnsi(text) {
580
+ return String(text || '').replace(/\u001b\[[0-9;]*m/g, '');
581
+ }
582
+
583
+ function spokenCurrent(text) {
584
+ const line = stripAnsi(text).split(/\n/).map((part) => part.trim()).find(Boolean) || '';
585
+ return line.replace(/^hey\s+[^,]+,\s*/i, '').replace(/\.$/, '');
586
+ }
587
+
588
+ function printNowJson(payload) {
589
+ console.log(JSON.stringify(payload, null, 2));
590
+ }
591
+
592
+ function nowJsonPayload(root = process.cwd()) {
593
+ const { buildFirstMinute, isFreshWorkspace } = require('../lib/first-minute');
594
+ const fresh = isFreshWorkspace(root);
595
+ const screen = buildFirstMinute({ root, fresh });
596
+ const payload = { ok: !fresh };
597
+ const current = spokenCurrent(screen && screen.text);
598
+ const next = stripAnsi(screen && screen.nextCommand);
599
+ if (current) payload.current = current;
600
+ if (next) payload.next = next;
601
+ return payload;
602
+ }
603
+
579
604
  function nowAtris(args = process.argv.slice(3), root = process.cwd()) {
580
605
  const help = args.includes('--help') || args.includes('-h') || args[0] === 'help';
581
606
  if (help) {
@@ -588,8 +613,8 @@ function nowAtris(args = process.argv.slice(3), root = process.cwd()) {
588
613
  console.log(' atris now --refresh Regenerate a small local now.md');
589
614
  console.log(' atris now --all Refresh this parent and every child Atris workspace');
590
615
  console.log(' atris now --path Print the file path only');
591
- console.log(' atris now --json Emit path and content as JSON');
592
- return;
616
+ console.log(' atris now --json Print ok, next or current as JSON');
617
+ return 0;
593
618
  }
594
619
 
595
620
  const init = args.includes('--init');
@@ -598,48 +623,65 @@ function nowAtris(args = process.argv.slice(3), root = process.cwd()) {
598
623
  const pathOnly = args.includes('--path');
599
624
  const asJson = args.includes('--json');
600
625
 
601
- let result;
602
- if (all) {
603
- const workspaces = findChildWorkspaces(root);
604
- for (const workspace of workspaces) {
605
- refreshNowFile(workspace.root);
626
+ try {
627
+ if (asJson && !init && !refresh && !all && !pathOnly) {
628
+ const payload = nowJsonPayload(root);
629
+ printNowJson(payload);
630
+ return payload.ok ? 0 : 2;
606
631
  }
607
- result = refreshNowFile(root);
608
- if (!pathOnly && !asJson) {
609
- console.log(`Refreshed ${workspaces.length} child workspace${workspaces.length === 1 ? '' : 's'}.`);
610
- console.log('');
632
+
633
+ let result;
634
+ if (all) {
635
+ const workspaces = findChildWorkspaces(root);
636
+ for (const workspace of workspaces) {
637
+ refreshNowFile(workspace.root);
638
+ }
639
+ result = refreshNowFile(root);
640
+ if (!pathOnly && !asJson) {
641
+ console.log(`Refreshed ${workspaces.length} child workspace${workspaces.length === 1 ? '' : 's'}.`);
642
+ console.log('');
643
+ }
644
+ } else if (refresh) {
645
+ result = refreshNowFile(root);
646
+ } else if (init) {
647
+ result = ensureNowFile(root);
648
+ } else {
649
+ result = ensureNowFile(root);
611
650
  }
612
- } else if (refresh) {
613
- result = refreshNowFile(root);
614
- } else if (init) {
615
- result = ensureNowFile(root);
616
- } else {
617
- result = ensureNowFile(root);
618
- }
619
651
 
620
- const rel = path.relative(root, result.path);
621
- if (pathOnly) {
622
- console.log(rel);
623
- return;
624
- }
652
+ const rel = path.relative(root, result.path);
653
+ if (pathOnly) {
654
+ console.log(rel);
655
+ return 0;
656
+ }
625
657
 
626
- const content = fs.readFileSync(result.path, 'utf8').trimEnd();
627
- if (asJson) {
628
- console.log(JSON.stringify({
629
- ok: true,
630
- path: rel,
631
- created: Boolean(result.created),
632
- content,
633
- }, null, 2));
634
- return;
635
- }
658
+ if (asJson) {
659
+ const payload = nowJsonPayload(root);
660
+ printNowJson(payload);
661
+ return payload.ok ? 0 : 2;
662
+ }
636
663
 
637
- if (result.created) {
638
- console.log(`Created ${rel}`);
639
- console.log('');
640
- }
664
+ const content = fs.readFileSync(result.path, 'utf8').trimEnd();
665
+ if (result.created) {
666
+ console.log(`Created ${rel}`);
667
+ console.log('');
668
+ }
641
669
 
642
- console.log(content);
670
+ console.log(content);
671
+ return 0;
672
+ } catch (err) {
673
+ const message = stripAnsi(err && err.message ? err.message : String(err));
674
+ if (asJson) {
675
+ printNowJson({
676
+ ok: false,
677
+ current: message.replace(/\.$/, ''),
678
+ next: 'atris init --yes',
679
+ });
680
+ return 2;
681
+ }
682
+ console.error(message);
683
+ return 1;
684
+ }
643
685
  }
644
686
 
645
687
  module.exports = {