atris 3.55.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 +266 -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 +70 -16
- 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/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 = {
|
package/commands/recap.js
CHANGED
|
@@ -1,6 +1,15 @@
|
|
|
1
1
|
const fs = require('fs');
|
|
2
2
|
const path = require('path');
|
|
3
|
-
const {
|
|
3
|
+
const {
|
|
4
|
+
isCertifiedReview,
|
|
5
|
+
isFreshWorkspace,
|
|
6
|
+
isKeepWorkingMinute,
|
|
7
|
+
buildFirstMinute,
|
|
8
|
+
listUserVisibleWork,
|
|
9
|
+
personName,
|
|
10
|
+
speakFirstMinute,
|
|
11
|
+
speakKeepWorkingMinute,
|
|
12
|
+
} = require('../lib/first-minute');
|
|
4
13
|
const { isRealTestRunnerProof, quoteVerifierCommand } = require('../lib/verifier-quality');
|
|
5
14
|
|
|
6
15
|
const DAY_MS = 24 * 60 * 60 * 1000;
|
|
@@ -241,6 +250,20 @@ function recapSoftTitle(title, maxWords = 5) {
|
|
|
241
250
|
return `"${text.toLowerCase()}"`;
|
|
242
251
|
}
|
|
243
252
|
|
|
253
|
+
function hasLiveKeepWorkingRun(root = process.cwd()) {
|
|
254
|
+
try {
|
|
255
|
+
const { pickLiveLocalMission } = require('./mission');
|
|
256
|
+
return Boolean(pickLiveLocalMission(root));
|
|
257
|
+
} catch {
|
|
258
|
+
return false;
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
function pickSpokenInProgress(items) {
|
|
263
|
+
const list = Array.isArray(items) ? items.filter(Boolean) : [];
|
|
264
|
+
return list.find((item) => item.owner) || list[0] || null;
|
|
265
|
+
}
|
|
266
|
+
|
|
244
267
|
function renderRecapMinute(data, { person } = {}) {
|
|
245
268
|
const who = person != null ? person : personName();
|
|
246
269
|
const greet = who ? `hey ${who}, ` : '';
|
|
@@ -290,9 +313,15 @@ function renderRecapMinute(data, { person } = {}) {
|
|
|
290
313
|
}
|
|
291
314
|
|
|
292
315
|
if (inProgress.length) {
|
|
293
|
-
const item = inProgress
|
|
316
|
+
const item = pickSpokenInProgress(inProgress);
|
|
294
317
|
const named = recapSoftTitle(item && item.title);
|
|
295
|
-
if (item && item.owner && named)
|
|
318
|
+
if (item && item.owner && named) {
|
|
319
|
+
return [
|
|
320
|
+
`${greet}${named} is already yours.`,
|
|
321
|
+
'',
|
|
322
|
+
'next: atris do',
|
|
323
|
+
].join('\n');
|
|
324
|
+
}
|
|
296
325
|
if (named) return `${greet}${named} is ready to claim.`;
|
|
297
326
|
}
|
|
298
327
|
|
|
@@ -319,9 +348,42 @@ function recapAtris(args = []) {
|
|
|
319
348
|
printRecapHelp();
|
|
320
349
|
return;
|
|
321
350
|
}
|
|
351
|
+
const root = process.cwd();
|
|
352
|
+
const visibleBefore = listUserVisibleWork(root);
|
|
322
353
|
const daysIdx = args.indexOf('--days');
|
|
323
354
|
const days = daysIdx !== -1 ? Number(args[daysIdx + 1]) : DEFAULT_DAYS;
|
|
324
|
-
const data = buildRecapData(
|
|
355
|
+
const data = buildRecapData(root, { days });
|
|
356
|
+
if (
|
|
357
|
+
data.empty
|
|
358
|
+
&& isFreshWorkspace(root)
|
|
359
|
+
&& !args.includes('--verbose')
|
|
360
|
+
&& !args.includes('--full')
|
|
361
|
+
&& !args.includes('--share')
|
|
362
|
+
) {
|
|
363
|
+
return speakFirstMinute({
|
|
364
|
+
root,
|
|
365
|
+
asJson: args.includes('--json'),
|
|
366
|
+
files: visibleBefore,
|
|
367
|
+
});
|
|
368
|
+
}
|
|
369
|
+
// Just-minted file folder, nothing running: same two lines as
|
|
370
|
+
// first-minute / status / the next do. Not factory MAP.md.
|
|
371
|
+
// A claimed non-seed task still recaps that work. A live mission
|
|
372
|
+
// and --verbose / --share keep the recap report.
|
|
373
|
+
if (
|
|
374
|
+
!args.includes('--verbose')
|
|
375
|
+
&& !args.includes('--full')
|
|
376
|
+
&& !args.includes('--share')
|
|
377
|
+
&& !hasLiveKeepWorkingRun(root)
|
|
378
|
+
) {
|
|
379
|
+
const minute = buildFirstMinute({ root, files: visibleBefore });
|
|
380
|
+
if (isKeepWorkingMinute(minute)) {
|
|
381
|
+
return speakKeepWorkingMinute({
|
|
382
|
+
root,
|
|
383
|
+
asJson: args.includes('--json'),
|
|
384
|
+
});
|
|
385
|
+
}
|
|
386
|
+
}
|
|
325
387
|
if (args.includes('--json')) {
|
|
326
388
|
console.log(JSON.stringify(data, null, 2));
|
|
327
389
|
return;
|
package/commands/run-front.js
CHANGED
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
// atris run: thin front door over the mission runtime loop.
|
|
2
2
|
// One bounded pursuit: start a mission from the objective (or resume the most
|
|
3
|
-
// logical runnable mission
|
|
3
|
+
// logical runnable mission after --yes), then exit.
|
|
4
|
+
// A hours / keep-working mission is never started from a bare invoke.
|
|
4
5
|
// The old plan→do→review loop lives on behind `atris run --legacy`.
|
|
5
6
|
const path = require('path');
|
|
6
7
|
const { spawnSync } = require('child_process');
|
|
8
|
+
const { hasYesFlag, wantsJson } = require('../lib/noninteractive');
|
|
9
|
+
const { speakFirstMinute } = require('../lib/first-minute');
|
|
7
10
|
|
|
8
11
|
const CLI_PATH = path.join(__dirname, '..', 'bin', 'atris.js');
|
|
9
12
|
|
|
@@ -91,6 +94,14 @@ function pickRunnableMission(root = process.cwd(), missionMap = null, options =
|
|
|
91
94
|
return candidates[0] || null;
|
|
92
95
|
}
|
|
93
96
|
|
|
97
|
+
function isHoursMission(mission) {
|
|
98
|
+
if (!mission) return false;
|
|
99
|
+
if (mission.overnight_loop) return true;
|
|
100
|
+
const text = `${mission.objective || ''} ${mission.stop_condition || ''}`;
|
|
101
|
+
if (/\b(overnight|nonstop|forever|goal\s+after\s+goal|self[-\s]?improve)\b/i.test(text)) return true;
|
|
102
|
+
return /\bhours?\b/i.test(text);
|
|
103
|
+
}
|
|
104
|
+
|
|
94
105
|
function runTickBudget(args, budgetSeconds) {
|
|
95
106
|
const explicit = positiveNumber(readValueFlag(args, '--max-ticks'));
|
|
96
107
|
if (explicit) return Math.floor(explicit);
|
|
@@ -122,14 +133,17 @@ async function runMissionFront(args = []) {
|
|
|
122
133
|
return result.ok ? 0 : 1;
|
|
123
134
|
}
|
|
124
135
|
|
|
136
|
+
// Bare `atris run` talks like the desk. A long keep-working mission
|
|
137
|
+
// needs an objective or an explicit --yes. Headless never prompts.
|
|
138
|
+
if (!hasYesFlag(args)) {
|
|
139
|
+
return speakFirstMinute({ asJson: wantsJson(args) });
|
|
140
|
+
}
|
|
141
|
+
|
|
125
142
|
const mission = pickRunnableMission(process.cwd(), null, {
|
|
126
143
|
allowCallerSessionRunners: liveCodexSession(),
|
|
127
144
|
});
|
|
128
145
|
if (!mission) {
|
|
129
|
-
|
|
130
|
-
console.log('Start one: atris run "<objective>" [--minutes N] [--owner <member>]');
|
|
131
|
-
console.log('Or keep going indefinitely: atris autopilot');
|
|
132
|
-
return 1;
|
|
146
|
+
return speakFirstMinute({ asJson: wantsJson(args) });
|
|
133
147
|
}
|
|
134
148
|
console.log(`Resuming mission ${mission.id} (${mission.status}): ${mission.objective}`);
|
|
135
149
|
const result = spawnSync(process.execPath, [
|
|
@@ -144,6 +158,7 @@ async function runMissionFront(args = []) {
|
|
|
144
158
|
module.exports = {
|
|
145
159
|
runMissionFront,
|
|
146
160
|
pickRunnableMission,
|
|
161
|
+
isHoursMission,
|
|
147
162
|
runObjective,
|
|
148
163
|
runBudgetSeconds,
|
|
149
164
|
runTickBudget,
|
package/commands/spaceship.js
CHANGED
|
@@ -13,17 +13,42 @@
|
|
|
13
13
|
* atris spaceship --hours 4 --repo /path/to/repo --interval 780 --yes
|
|
14
14
|
* atris spaceship --hours 0.01 --tick-cmd /tmp/stub.sh --no-email --yes
|
|
15
15
|
*
|
|
16
|
-
* Without --yes, spaceship only
|
|
16
|
+
* Without --yes, spaceship only talks the keep-working plan (no email, no run).
|
|
17
17
|
* `--json` without --yes prints a JSON refuse and still does not start.
|
|
18
|
+
* `--yes` from an unbound scratch folder refuses: that folder is not a room.
|
|
18
19
|
*/
|
|
19
20
|
|
|
20
21
|
const path = require('path');
|
|
21
22
|
const fs = require('fs');
|
|
22
23
|
const { spawn } = require('child_process');
|
|
23
24
|
const { hasYesFlag, argsWantHelp, wantsJson } = require('../lib/noninteractive');
|
|
25
|
+
const { isUnboundScratchFolder, refuseUnboundScratch } = require('../lib/scratch-root');
|
|
26
|
+
const { resolveWorkspaceRoot } = require('../lib/mission-root');
|
|
27
|
+
const { personName } = require('../lib/first-minute');
|
|
24
28
|
|
|
25
29
|
const SCRIPT = path.join(__dirname, '..', 'scripts', 'spaceship.sh');
|
|
26
30
|
|
|
31
|
+
function greet(person) {
|
|
32
|
+
return person ? `hey ${person}, ` : '';
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function readFlagValue(args, name) {
|
|
36
|
+
const list = Array.isArray(args) ? args : [];
|
|
37
|
+
const idx = list.indexOf(name);
|
|
38
|
+
const raw = idx !== -1 && list[idx + 1] && !String(list[idx + 1]).startsWith('-')
|
|
39
|
+
? String(list[idx + 1]).trim()
|
|
40
|
+
: '';
|
|
41
|
+
return raw;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function spokenHours(raw) {
|
|
45
|
+
const text = raw === undefined || raw === null ? '4' : String(raw).trim();
|
|
46
|
+
const n = Number(text);
|
|
47
|
+
if (!Number.isFinite(n) || n <= 0) return '4 hours';
|
|
48
|
+
if (n === 1) return `${text} hour`;
|
|
49
|
+
return `${text} hours`;
|
|
50
|
+
}
|
|
51
|
+
|
|
27
52
|
function printUsage() {
|
|
28
53
|
const lines = [
|
|
29
54
|
'Usage: atris spaceship [--hours N] [--interval SEC] [--repo PATH]',
|
|
@@ -31,25 +56,34 @@ function printUsage() {
|
|
|
31
56
|
' [--idle-alert N] [--halt-alert N] [--label NAME]',
|
|
32
57
|
' [--no-email] [--yes]',
|
|
33
58
|
'',
|
|
34
|
-
|
|
35
|
-
'Without --yes,
|
|
36
|
-
'Defaults: --hours 4, --interval 780, tick = atris autopilot --auto --iterations=1',
|
|
59
|
+
"Keep working here for a few hours. I'll write you if something changes.",
|
|
60
|
+
'Without --yes, I only say the plan. --no-email stays quiet.',
|
|
37
61
|
];
|
|
38
62
|
console.log(lines.join('\n'));
|
|
39
63
|
}
|
|
40
64
|
|
|
41
65
|
function planLines(args = []) {
|
|
42
|
-
const
|
|
43
|
-
const hours =
|
|
66
|
+
const list = Array.isArray(args) ? args : [];
|
|
67
|
+
const hours = spokenHours(readFlagValue(list, '--hours') || '4');
|
|
68
|
+
const next = list.includes('--no-email')
|
|
69
|
+
? 'next: atris spaceship --yes'
|
|
70
|
+
: "I'll write you if something changes. next: atris spaceship --yes";
|
|
44
71
|
return [
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
' tick: atris autopilot --auto --iterations=1',
|
|
49
|
-
'Pass --yes to start the overnight run.',
|
|
72
|
+
`${greet(personName())}I can keep working here for ${hours}.`,
|
|
73
|
+
'',
|
|
74
|
+
next,
|
|
50
75
|
];
|
|
51
76
|
}
|
|
52
77
|
|
|
78
|
+
function spaceshipTargetRoot(args = []) {
|
|
79
|
+
const list = Array.isArray(args) ? args : [];
|
|
80
|
+
const idx = list.indexOf('--repo');
|
|
81
|
+
const raw = idx !== -1 && list[idx + 1] && !String(list[idx + 1]).startsWith('-')
|
|
82
|
+
? path.resolve(list[idx + 1])
|
|
83
|
+
: process.cwd();
|
|
84
|
+
return resolveWorkspaceRoot(raw);
|
|
85
|
+
}
|
|
86
|
+
|
|
53
87
|
function spaceship(args = []) {
|
|
54
88
|
const list = Array.isArray(args) ? args : [];
|
|
55
89
|
if (argsWantHelp(list) || list.includes('--help') || list.includes('-h')) {
|
|
@@ -75,6 +109,12 @@ function spaceship(args = []) {
|
|
|
75
109
|
process.exit(2);
|
|
76
110
|
}
|
|
77
111
|
|
|
112
|
+
// --yes starts the run. It is not a workspace unlock. An unbound
|
|
113
|
+
// scratch folder is not a room (same class as slack/gmail from scratch).
|
|
114
|
+
if (isUnboundScratchFolder(spaceshipTargetRoot(list))) {
|
|
115
|
+
process.exit(refuseUnboundScratch());
|
|
116
|
+
}
|
|
117
|
+
|
|
78
118
|
const runArgs = list.filter((a) => a !== '--yes' && a !== '-y' && a !== '--json');
|
|
79
119
|
return new Promise((resolve, reject) => {
|
|
80
120
|
if (!fs.existsSync(SCRIPT)) {
|
|
@@ -99,4 +139,4 @@ function spaceship(args = []) {
|
|
|
99
139
|
});
|
|
100
140
|
}
|
|
101
141
|
|
|
102
|
-
module.exports = { spaceship, SCRIPT, printUsage, planLines };
|
|
142
|
+
module.exports = { spaceship, SCRIPT, printUsage, planLines, spokenHours, spaceshipTargetRoot };
|
package/commands/status.js
CHANGED
|
@@ -4,6 +4,13 @@ const { getLogPath, ensureLogDirectory, createLogFile } = require('../lib/journa
|
|
|
4
4
|
const { parseTodo, getTeamActivity } = require('../lib/todo');
|
|
5
5
|
const { clarify } = require('../lib/autoland');
|
|
6
6
|
const { checkoutBehindMessage } = require('../lib/checkout-sync');
|
|
7
|
+
const {
|
|
8
|
+
isFreshWorkspace,
|
|
9
|
+
isKeepWorkingMinute,
|
|
10
|
+
buildFirstMinute,
|
|
11
|
+
speakKeepWorkingMinute,
|
|
12
|
+
speakNothingRunning,
|
|
13
|
+
} = require('../lib/first-minute');
|
|
7
14
|
|
|
8
15
|
// Box drawing helpers
|
|
9
16
|
const W = 64; // inner width
|
|
@@ -122,18 +129,34 @@ function parseStatusTodo(todoFile) {
|
|
|
122
129
|
}
|
|
123
130
|
}
|
|
124
131
|
|
|
132
|
+
function hasLiveKeepWorkingRun(root = process.cwd()) {
|
|
133
|
+
try {
|
|
134
|
+
const { pickLiveLocalMission } = require('./mission');
|
|
135
|
+
return Boolean(pickLiveLocalMission(root));
|
|
136
|
+
} catch {
|
|
137
|
+
return false;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
125
141
|
function statusAtris(isQuick = false, jsonMode = false, verbose = false) {
|
|
126
|
-
|
|
142
|
+
// Fresh folder: empty talks first-talk. A file already here
|
|
143
|
+
// names that file, same as bare atris. Do not mint a room.
|
|
144
|
+
if (isFreshWorkspace()) {
|
|
145
|
+
process.exit(speakNothingRunning({ asJson: jsonMode }));
|
|
146
|
+
}
|
|
127
147
|
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
148
|
+
// Just-minted file folder, nothing running: same two lines as
|
|
149
|
+
// first-minute / the next do. A live mission still gets the board.
|
|
150
|
+
// --verbose keeps the factory dump because the operator asked for it.
|
|
151
|
+
if (!verbose && !hasLiveKeepWorkingRun()) {
|
|
152
|
+
const minute = buildFirstMinute({ root: process.cwd() });
|
|
153
|
+
if (isKeepWorkingMinute(minute)) {
|
|
154
|
+
process.exit(speakKeepWorkingMinute({ asJson: jsonMode }));
|
|
132
155
|
}
|
|
133
|
-
console.log('✗ atris/ folder not found. Run "atris init" first.');
|
|
134
|
-
process.exit(1);
|
|
135
156
|
}
|
|
136
157
|
|
|
158
|
+
const targetDir = path.join(process.cwd(), 'atris');
|
|
159
|
+
|
|
137
160
|
// Load task board state.
|
|
138
161
|
const todoFile = path.join(targetDir, 'TODO.md');
|
|
139
162
|
const todo = parseStatusTodo(todoFile);
|
package/commands/task.js
CHANGED
|
@@ -60,7 +60,18 @@ const {
|
|
|
60
60
|
decisionMarkerFor,
|
|
61
61
|
DECISION_REFUSE_REASON,
|
|
62
62
|
} = require('../lib/task-decision');
|
|
63
|
-
const {
|
|
63
|
+
const {
|
|
64
|
+
buildFirstMinute,
|
|
65
|
+
deskNextCommand,
|
|
66
|
+
firstTalkCommand,
|
|
67
|
+
folderName,
|
|
68
|
+
personName,
|
|
69
|
+
pickNext,
|
|
70
|
+
speakFirstMinute,
|
|
71
|
+
taskCommand,
|
|
72
|
+
taskNextCommand,
|
|
73
|
+
} = require('../lib/first-minute');
|
|
74
|
+
const { loadContext } = require('../lib/state-detection');
|
|
64
75
|
|
|
65
76
|
const DEFAULT_OWNER = process.env.ATRIS_AGENT_ID
|
|
66
77
|
|| process.env.USER
|
|
@@ -6166,6 +6177,23 @@ function renderTaskDesk(rows, refRows = rows) {
|
|
|
6166
6177
|
}
|
|
6167
6178
|
|
|
6168
6179
|
function cmdAdd(args) {
|
|
6180
|
+
const root = process.cwd();
|
|
6181
|
+
// Empty folder talks like bare atris. A leftover title is not a
|
|
6182
|
+
// task desk. After init, a title still files.
|
|
6183
|
+
if (isUninitializedTaskFolder(root)) {
|
|
6184
|
+
if (wantsJson(args)) {
|
|
6185
|
+
printJson({
|
|
6186
|
+
ok: false,
|
|
6187
|
+
action: 'none',
|
|
6188
|
+
command: firstTalkCommand(folderName(root)),
|
|
6189
|
+
task_id: null,
|
|
6190
|
+
projection_path: null,
|
|
6191
|
+
task: null,
|
|
6192
|
+
});
|
|
6193
|
+
return;
|
|
6194
|
+
}
|
|
6195
|
+
return speakFirstMinute({ root, fresh: true });
|
|
6196
|
+
}
|
|
6169
6197
|
const pos = positional(args);
|
|
6170
6198
|
const title = pos.join(' ').trim();
|
|
6171
6199
|
if (!title) {
|
|
@@ -6476,9 +6504,19 @@ function cmdDay(args) {
|
|
|
6476
6504
|
|
|
6477
6505
|
function cmdFirstMinute() {
|
|
6478
6506
|
const root = process.cwd();
|
|
6507
|
+
const fresh = !fs.existsSync(path.join(root, 'atris'));
|
|
6508
|
+
let context = {};
|
|
6509
|
+
if (!fresh) {
|
|
6510
|
+
try {
|
|
6511
|
+
context = loadContext(root);
|
|
6512
|
+
} catch {
|
|
6513
|
+
context = {};
|
|
6514
|
+
}
|
|
6515
|
+
}
|
|
6479
6516
|
const screen = buildFirstMinute({
|
|
6480
6517
|
root,
|
|
6481
|
-
fresh
|
|
6518
|
+
fresh,
|
|
6519
|
+
context,
|
|
6482
6520
|
});
|
|
6483
6521
|
console.log(screen.text);
|
|
6484
6522
|
}
|
|
@@ -6809,10 +6847,12 @@ function createEndgameSeedTask(taskDb, db, seed, owner) {
|
|
|
6809
6847
|
}
|
|
6810
6848
|
|
|
6811
6849
|
function nextActionFromCommand(command) {
|
|
6812
|
-
const
|
|
6850
|
+
const text = String(command || '').trim();
|
|
6851
|
+
const match = text.match(/^atris (?:task|mission) (\S+)/);
|
|
6813
6852
|
const verb = match ? match[1] : '';
|
|
6814
|
-
if (
|
|
6815
|
-
return
|
|
6853
|
+
if (verb && verb !== 'new') return verb;
|
|
6854
|
+
if (/^atris do\b/.test(text)) return 'do';
|
|
6855
|
+
return 'none';
|
|
6816
6856
|
}
|
|
6817
6857
|
|
|
6818
6858
|
function cmdNextTruth(args) {
|
|
@@ -6823,9 +6863,24 @@ function cmdNextTruth(args) {
|
|
|
6823
6863
|
const workspaceRoot = taskDb.workspaceRoot();
|
|
6824
6864
|
const rows = taskDb.listTasks(db, { workspaceRoot, limit: 500 });
|
|
6825
6865
|
const existingProj = readProjectionFile(workspaceRoot);
|
|
6866
|
+
const dbHasActionable = rows.some((task) => {
|
|
6867
|
+
const status = task && task.status;
|
|
6868
|
+
return status === 'open' || status === 'claimed' || status === 'review';
|
|
6869
|
+
});
|
|
6870
|
+
const projHasActionable = Boolean(
|
|
6871
|
+
existingProj
|
|
6872
|
+
&& Array.isArray(existingProj.tasks)
|
|
6873
|
+
&& existingProj.tasks.some((task) => {
|
|
6874
|
+
const status = task && task.status;
|
|
6875
|
+
return status === 'open' || status === 'claimed' || status === 'review';
|
|
6876
|
+
}),
|
|
6877
|
+
);
|
|
6826
6878
|
let projection;
|
|
6827
6879
|
let outPath;
|
|
6828
|
-
if (
|
|
6880
|
+
if (!dbHasActionable && projHasActionable) {
|
|
6881
|
+
projection = existingProj;
|
|
6882
|
+
outPath = path.resolve(path.join(workspaceRoot || '.', '.atris', 'state', 'tasks.projection.json'));
|
|
6883
|
+
} else if (rows.length === 0 && existingProj && Array.isArray(existingProj.tasks) && existingProj.tasks.length > 0) {
|
|
6829
6884
|
projection = existingProj;
|
|
6830
6885
|
outPath = path.resolve(path.join(workspaceRoot || '.', '.atris', 'state', 'tasks.projection.json'));
|
|
6831
6886
|
} else {
|
|
@@ -6894,9 +6949,9 @@ function cmdNext(args) {
|
|
|
6894
6949
|
if (isUninitializedTaskFolder(root)) {
|
|
6895
6950
|
if (wantsJson(args)) {
|
|
6896
6951
|
printJson({
|
|
6897
|
-
ok:
|
|
6898
|
-
action: '
|
|
6899
|
-
command:
|
|
6952
|
+
ok: false,
|
|
6953
|
+
action: 'none',
|
|
6954
|
+
command: firstTalkCommand(folderName(root)),
|
|
6900
6955
|
task_id: null,
|
|
6901
6956
|
owner: String(flag(args, '--as') || personName() || DEFAULT_OWNER),
|
|
6902
6957
|
scope: normalizeTaskQueueScope(taskQueueScopeFromArgs(args)),
|
|
@@ -6905,9 +6960,7 @@ function cmdNext(args) {
|
|
|
6905
6960
|
});
|
|
6906
6961
|
return;
|
|
6907
6962
|
}
|
|
6908
|
-
|
|
6909
|
-
console.log(screen.text);
|
|
6910
|
-
return;
|
|
6963
|
+
return speakFirstMinute({ root, fresh: true });
|
|
6911
6964
|
}
|
|
6912
6965
|
if (!hasFlag(args, '--create-next')) return cmdNextTruth(args);
|
|
6913
6966
|
const owner = flag(args, '--as') || DEFAULT_OWNER;
|
|
@@ -9779,16 +9832,17 @@ function appendRelabelArchivedJournalReceipt(workspaceRoot, { actor, count, ids
|
|
|
9779
9832
|
function cmdReady(args) {
|
|
9780
9833
|
const pos = positional(args);
|
|
9781
9834
|
const id = pos[0];
|
|
9782
|
-
|
|
9783
|
-
|
|
9835
|
+
const proofFlag = flag(args, '--proof');
|
|
9836
|
+
const verifyFlag = flag(args, '--verify');
|
|
9837
|
+
const resultFlag = textFlag(args, ['--result']);
|
|
9838
|
+
if (!id || (!proofFlag && !verifyFlag && !resultFlag)) {
|
|
9839
|
+
console.log('Usage: atris task ready <id> --proof "..." --result "<sentence>"');
|
|
9784
9840
|
process.exit(2);
|
|
9785
9841
|
}
|
|
9786
9842
|
// Two ways to prove: --proof "<note>" (claimed, pattern-checked) or
|
|
9787
9843
|
// --verify "<command>" which actually RUNS the command and gates ready on exit 0.
|
|
9788
9844
|
// If --proof names npm test, node --test, or git diff --check, this process
|
|
9789
9845
|
// must have run that command. A sentence that names one of those is a lie.
|
|
9790
|
-
const proofFlag = flag(args, '--proof');
|
|
9791
|
-
const verifyFlag = flag(args, '--verify');
|
|
9792
9846
|
const proofUrl = textFlag(args, ['--proof-url']);
|
|
9793
9847
|
const iFetched = hasFlag(args, '--i-fetched');
|
|
9794
9848
|
if (proofUrl && !iFetched) {
|
package/commands/terminal.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* atris terminal <business> <command...> [--timeout N]
|
|
3
3
|
*
|
|
4
4
|
* Run a shell command directly on a business EC2 workspace via the warm runner.
|
|
5
|
-
* This is the load-bearing primitive for fast bulk ops
|
|
5
|
+
* This is the load-bearing primitive for fast bulk ops: one bash call beats
|
|
6
6
|
* hundreds of rate-limited individual file API calls.
|
|
7
7
|
*
|
|
8
8
|
* SAFETY:
|
|
@@ -27,7 +27,7 @@ const path = require('path');
|
|
|
27
27
|
const { loadCredentials, abortOnAuthFailure } = require('../utils/auth');
|
|
28
28
|
const { apiRequestJson } = require('../utils/api');
|
|
29
29
|
const { loadBusinesses, saveBusinesses } = require('./business');
|
|
30
|
-
const { requireAccountBound, refuseAccountGlobal } = require('../lib/account-bound');
|
|
30
|
+
const { looksLikeBusinessSlug, requireAccountBound, refuseAccountGlobal } = require('../lib/account-bound');
|
|
31
31
|
const { argsWantHelp, wantsJson } = require('../lib/noninteractive');
|
|
32
32
|
|
|
33
33
|
function sleep(ms) {
|
|
@@ -151,8 +151,8 @@ async function terminalAtris() {
|
|
|
151
151
|
try { return JSON.parse(fs.readFileSync(bizFile, 'utf8')).slug; } catch { return null; }
|
|
152
152
|
})();
|
|
153
153
|
|
|
154
|
-
//
|
|
155
|
-
const firstLooksLikeSlug =
|
|
154
|
+
// Flags like --json match a hyphenated word regex. Require a real slug first.
|
|
155
|
+
const firstLooksLikeSlug = looksLikeBusinessSlug(args[0]);
|
|
156
156
|
|
|
157
157
|
if (firstLooksLikeSlug && args.length > 1) {
|
|
158
158
|
slug = args[0];
|
|
@@ -161,7 +161,7 @@ async function terminalAtris() {
|
|
|
161
161
|
slug = cwdSlug;
|
|
162
162
|
command = args.join(' ');
|
|
163
163
|
} else if (firstLooksLikeSlug && args.length === 1) {
|
|
164
|
-
// First
|
|
164
|
+
// First and only arg is a slug-shaped word with no command.
|
|
165
165
|
console.error('Missing command. Usage: atris terminal <business> <command>');
|
|
166
166
|
process.exit(1);
|
|
167
167
|
} else {
|