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.
- package/atris/skills/engines/SKILL.md +11 -3
- package/atris/skills/x-search/SKILL.md +20 -1
- package/atris/skills/youtube/SKILL.md +51 -43
- package/bin/atris.js +119 -87
- package/commands/autopilot-front.js +29 -13
- package/commands/brainstorm.js +77 -476
- package/commands/business.js +13 -1
- package/commands/fleet-report.js +2 -2
- package/commands/human-missions.js +26 -2
- package/commands/init.js +2 -35
- package/commands/integrations.js +100 -0
- package/commands/land.js +53 -23
- package/commands/later.js +52 -0
- package/commands/log.js +79 -30
- package/commands/mission.js +117 -22
- package/commands/next.js +153 -63
- package/commands/now.js +80 -38
- package/commands/recap.js +66 -4
- package/commands/run-front.js +20 -5
- package/commands/spaceship.js +52 -12
- package/commands/status.js +30 -7
- package/commands/task.js +56 -19
- package/commands/terminal.js +5 -5
- package/commands/workflow.js +18 -5
- package/commands/x-search.js +123 -13
- package/commands/youtube.js +639 -36
- package/lib/account-bound.js +7 -0
- package/lib/apply-gate.js +94 -0
- package/lib/context-gatherer.js +55 -8
- package/lib/engine-ask.js +16 -6
- package/lib/first-minute.js +552 -26
- package/lib/known-commands.js +1 -1
- package/lib/runner-command.js +5 -3
- package/lib/scratch-root.js +44 -0
- package/package.json +1 -1
- package/utils/auth.js +9 -0
package/commands/mission.js
CHANGED
|
@@ -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 =
|
|
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.
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
11378
|
-
|
|
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 =
|
|
11391
|
-
const rest =
|
|
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
|
-
|
|
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
|
|
22
|
-
return String(
|
|
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
|
|
26
|
-
const
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
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
|
|
35
|
-
if (!card) return '
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
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
|
|
44
|
-
|
|
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
|
-
|
|
54
|
-
|
|
55
|
-
|
|
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
|
-
|
|
149
|
+
log(`${cardLabel(card)} is working.`);
|
|
65
150
|
return 0;
|
|
66
151
|
}
|
|
67
152
|
const prompt = card.next_action && card.next_action.prompt;
|
|
68
|
-
|
|
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
|
-
|
|
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
|
-
|
|
87
|
-
|
|
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
|
-
|
|
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
|
|
99
|
-
const
|
|
100
|
-
|
|
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')
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
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
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
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
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
652
|
+
const rel = path.relative(root, result.path);
|
|
653
|
+
if (pathOnly) {
|
|
654
|
+
console.log(rel);
|
|
655
|
+
return 0;
|
|
656
|
+
}
|
|
625
657
|
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
ok:
|
|
630
|
-
|
|
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
|
-
|
|
638
|
-
|
|
639
|
-
|
|
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
|
-
|
|
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 = {
|