atris 3.53.0 → 3.56.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/FOR_AGENTS.md +7 -0
- package/README.md +1 -1
- package/atris/policies/ANTISLOP.md +2 -1
- package/atris/skills/copy-editor/SKILL.md +2 -1
- package/atris/skills/design/SKILL.md +1 -1
- package/atris/skills/x-search/SKILL.md +24 -22
- package/atris/skills/youtube/SKILL.md +82 -33
- package/atris/team/_template/SOUL.md +40 -0
- package/atris/team/validator/MEMBER.md +1 -2
- package/bin/atris +21 -0
- package/bin/atris.js +563 -373
- package/commands/api-key.js +170 -0
- package/commands/auth.js +453 -43
- package/commands/autoland.js +3 -1
- package/commands/autopilot-front.js +58 -11
- package/commands/avail.js +5 -4
- package/commands/balance.js +55 -0
- package/commands/bench.js +29 -1
- package/commands/brain.js +63 -12
- package/commands/brainstorm.js +18 -0
- package/commands/business.js +149 -1
- package/commands/chat-scan.js +17 -4
- package/commands/close.js +5 -1
- package/commands/doctor.js +174 -0
- package/commands/dream.js +7 -0
- package/commands/drill.js +34 -2
- package/commands/engine.js +47 -35
- package/commands/errors.js +58 -8
- package/commands/experiments.js +14 -0
- package/commands/feedback.js +61 -2
- package/commands/fleet-report.js +27 -7
- package/commands/fleet.js +98 -11
- package/commands/founder.js +60 -21
- package/commands/human-missions.js +79 -4
- package/commands/improve.js +54 -4
- package/commands/init.js +68 -92
- package/commands/install.js +40 -0
- package/commands/integrations.js +565 -30
- package/commands/interview.js +8 -0
- package/commands/land.js +48 -17
- package/commands/learn.js +29 -4
- package/commands/log.js +51 -0
- package/commands/member.js +68 -10
- package/commands/mission.js +287 -50
- package/commands/now.js +15 -3
- package/commands/orb.js +97 -2
- package/commands/pack.js +83 -9
- package/commands/playbook.js +381 -0
- package/commands/plugin.js +16 -0
- package/commands/radar.js +31 -7
- package/commands/recap.js +128 -21
- package/commands/release.js +102 -2
- package/commands/review.js +5 -10
- package/commands/revisions.js +55 -0
- package/commands/run-front.js +12 -4
- package/commands/scout.js +12 -1
- package/commands/search.js +123 -28
- package/commands/sign.js +2 -2
- package/commands/signup.js +12 -3
- package/commands/site-deploy.js +17 -4
- package/commands/site.js +2 -2
- package/commands/skill-eval.js +289 -0
- package/commands/skill.js +56 -6
- package/commands/social.js +603 -0
- package/commands/spaceship.js +69 -6
- package/commands/stream.js +8 -1
- package/commands/study.js +32 -19
- package/commands/sync.js +28 -22
- package/commands/task.js +435 -60
- package/commands/teach.js +72 -63
- package/commands/terminal.js +25 -9
- package/commands/topup.js +88 -0
- package/commands/truth.js +52 -6
- package/commands/usage.js +91 -0
- package/commands/verify.js +3 -2
- package/commands/version.js +3 -1
- package/commands/voice.js +5 -1
- package/commands/who.js +69 -11
- package/commands/wiki.js +7 -4
- package/commands/wish.js +76 -5
- package/commands/workflow.js +327 -145
- package/commands/worktree.js +1 -0
- package/commands/write.js +5 -0
- package/commands/x-search.js +340 -0
- package/commands/xp.js +5 -1
- package/commands/youtube.js +551 -25
- package/lib/account-bound.js +47 -0
- package/lib/auto-accept-certified.js +8 -1
- package/lib/auto-lessons.js +186 -0
- package/lib/autoland.js +4 -2
- package/lib/chat-log-scan.js +7 -4
- package/lib/cli-json.js +77 -0
- package/lib/cli-scope.js +26 -0
- package/lib/codex-flight.js +1 -0
- package/lib/conductor-artifacts.js +1 -1
- package/lib/context-gatherer.js +11 -0
- package/lib/developer-api.js +116 -0
- package/lib/feedback-triage.js +274 -0
- package/lib/first-minute.js +398 -0
- package/lib/fleet.js +115 -34
- package/lib/functional-owner.js +22 -0
- package/lib/known-commands.js +31 -5
- package/lib/member-scaffold.js +197 -0
- package/lib/mission-root.js +4 -2
- package/lib/noninteractive.js +88 -0
- package/lib/policy-lessons.js +4 -1
- package/lib/revision-metric.js +279 -0
- package/lib/scratch-root.js +48 -0
- package/lib/skill-eval-gate.js +249 -0
- package/lib/task-db.js +4 -0
- package/lib/task-proof.js +69 -8
- package/lib/verifier-quality.js +69 -0
- package/lib/wish-audit.js +2 -2
- package/lib/wish-delegate.js +10 -1
- package/lib/workspace-scaffold.js +270 -0
- package/package.json +3 -2
- package/scripts/det/checklist-score.js +191 -0
- package/scripts/det/ytsearch +31 -0
- package/utils/update-check.js +15 -0
package/commands/teach.js
CHANGED
|
@@ -1,86 +1,95 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
* atris teach —
|
|
4
|
+
* atris teach — local cases under ./atris/teach only.
|
|
5
5
|
*
|
|
6
|
-
* atris teach
|
|
7
|
-
* atris teach
|
|
8
|
-
* atris teach
|
|
9
|
-
* atris teach guards rerun every promoted guard (regression suite)
|
|
6
|
+
* atris teach list cases in atris/teach
|
|
7
|
+
* atris teach <id> show one case file
|
|
8
|
+
* atris teach --help usage
|
|
10
9
|
*
|
|
11
|
-
*
|
|
12
|
-
* red gate: a case must be observed FAILING before it can become a guard.
|
|
13
|
-
* Nonzero exit means a guard regressed.
|
|
10
|
+
* Does not call the backend or dump account-global state.
|
|
14
11
|
*/
|
|
15
12
|
|
|
16
13
|
const fs = require('fs');
|
|
17
14
|
const path = require('path');
|
|
18
|
-
const { spawnSync } = require('child_process');
|
|
19
15
|
|
|
20
|
-
const
|
|
21
|
-
|
|
22
|
-
function requireVenvPython() {
|
|
23
|
-
const venvPython = path.join(BACKEND_ROOT, 'venv/bin/python');
|
|
24
|
-
if (!fs.existsSync(venvPython)) {
|
|
25
|
-
console.error(`✗ Backend venv not found: ${venvPython}`);
|
|
26
|
-
process.exit(1);
|
|
27
|
-
}
|
|
28
|
-
return venvPython;
|
|
29
|
-
}
|
|
16
|
+
const TEACH_DIR = path.join('atris', 'teach');
|
|
30
17
|
|
|
31
18
|
function usage() {
|
|
32
|
-
console.log('atris teach —
|
|
19
|
+
console.log('atris teach — local cases under ./atris/teach');
|
|
33
20
|
console.log('');
|
|
34
|
-
console.log(' atris teach
|
|
35
|
-
console.log(' atris teach
|
|
36
|
-
console.log(' atris teach
|
|
37
|
-
console.log(' atris teach guards rerun every guard; nonzero exit = regression');
|
|
38
|
-
console.log(' atris teach drafts corrections captured from live turns');
|
|
39
|
-
console.log(' atris teach fix <id> brief a fix for a reproduced case (--dispatch to run it)');
|
|
40
|
-
console.log(' atris teach scaffold … turn a captured correction into a red case');
|
|
41
|
-
console.log(' atris teach mine which recorded tick failures could become cases');
|
|
21
|
+
console.log(' atris teach list case files in atris/teach');
|
|
22
|
+
console.log(' atris teach <id> show atris/teach/<id>.md (or .json)');
|
|
23
|
+
console.log(' atris teach --help this help');
|
|
42
24
|
console.log('');
|
|
43
|
-
console.log('
|
|
44
|
-
console.log(' --complaint "…" --file path=@localfile --forbid-changed-files');
|
|
25
|
+
console.log('Teach only reads ./atris/teach. It does not call the backend.');
|
|
45
26
|
}
|
|
46
27
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
function
|
|
52
|
-
if (!
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
if (subcommand === 'scaffold') return ['scaffold', ...args];
|
|
64
|
-
if (subcommand === 'run') {
|
|
65
|
-
// `atris teach run <id>` -> `run --case <id>`; bare `run` runs every case.
|
|
66
|
-
if (args.length && !args[0].startsWith('-')) return ['run', '--case', args[0], ...args.slice(1)];
|
|
67
|
-
return ['run', ...args];
|
|
28
|
+
function teachRoot(cwd = process.cwd()) {
|
|
29
|
+
return path.join(path.resolve(cwd), TEACH_DIR);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function listCaseFiles(root) {
|
|
33
|
+
if (!fs.existsSync(root)) return [];
|
|
34
|
+
return fs.readdirSync(root)
|
|
35
|
+
.filter((name) => /\.(md|json)$/i.test(name))
|
|
36
|
+
.sort((a, b) => a.localeCompare(b));
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function resolveCaseFile(root, id) {
|
|
40
|
+
const slug = String(id || '').replace(/\.(md|json)$/i, '');
|
|
41
|
+
for (const ext of ['.md', '.json']) {
|
|
42
|
+
const candidate = path.join(root, `${slug}${ext}`);
|
|
43
|
+
if (fs.existsSync(candidate)) return candidate;
|
|
68
44
|
}
|
|
69
|
-
// `atris teach <id>` is shorthand for running that one case.
|
|
70
|
-
if (!subcommand.startsWith('-')) return ['run', '--case', subcommand, ...args];
|
|
71
45
|
return null;
|
|
72
46
|
}
|
|
73
47
|
|
|
74
|
-
|
|
75
|
-
const
|
|
76
|
-
if (
|
|
48
|
+
function teach(subcommand, ...args) {
|
|
49
|
+
const cwd = process.cwd();
|
|
50
|
+
if (!subcommand || subcommand === 'list') {
|
|
51
|
+
const root = teachRoot(cwd);
|
|
52
|
+
if (!fs.existsSync(root)) {
|
|
53
|
+
console.log('no atris/teach folder here. add cases under ./atris/teach.');
|
|
54
|
+
return 0;
|
|
55
|
+
}
|
|
56
|
+
const files = listCaseFiles(root);
|
|
57
|
+
if (!files.length) {
|
|
58
|
+
console.log('atris/teach is empty.');
|
|
59
|
+
return 0;
|
|
60
|
+
}
|
|
61
|
+
console.log(`cases in ${TEACH_DIR}:`);
|
|
62
|
+
for (const name of files) console.log(` ${name.replace(/\.(md|json)$/i, '')}`);
|
|
63
|
+
return 0;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if (subcommand === 'help' || subcommand === '--help' || subcommand === '-h') {
|
|
77
67
|
usage();
|
|
78
|
-
return;
|
|
68
|
+
return 0;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
if (subcommand === 'add' || subcommand === 'run' || subcommand === 'guards'
|
|
72
|
+
|| subcommand === 'mine' || subcommand === 'drafts' || subcommand === 'fix'
|
|
73
|
+
|| subcommand === 'scaffold') {
|
|
74
|
+
console.error('atris teach only reads ./atris/teach. backend teach verbs are disabled.');
|
|
75
|
+
console.error('add a case file under atris/teach/<id>.md and run: atris teach <id>');
|
|
76
|
+
return 2;
|
|
79
77
|
}
|
|
80
|
-
const result = spawnSync(requireVenvPython(), ['-m', 'rl.teach', ...moduleArgs], {
|
|
81
|
-
cwd: path.join(BACKEND_ROOT, 'backend'),
|
|
82
|
-
stdio: 'inherit',
|
|
83
|
-
});
|
|
84
|
-
process.exit(result.status === null ? 1 : result.status);
|
|
85
|
-
};
|
|
86
78
|
|
|
79
|
+
const root = teachRoot(cwd);
|
|
80
|
+
const file = resolveCaseFile(root, subcommand);
|
|
81
|
+
if (!file) {
|
|
82
|
+
console.error(`no case "${subcommand}" in ${TEACH_DIR}`);
|
|
83
|
+
return 1;
|
|
84
|
+
}
|
|
85
|
+
const extra = args.join(' ').trim();
|
|
86
|
+
if (extra && extra !== '--raw') {
|
|
87
|
+
console.error(`unknown teach option: ${extra}`);
|
|
88
|
+
return 2;
|
|
89
|
+
}
|
|
90
|
+
const body = fs.readFileSync(file, 'utf8');
|
|
91
|
+
process.stdout.write(body.endsWith('\n') ? body : `${body}\n`);
|
|
92
|
+
return 0;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
module.exports = teach;
|
package/commands/terminal.js
CHANGED
|
@@ -27,6 +27,8 @@ 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');
|
|
31
|
+
const { argsWantHelp, wantsJson } = require('../lib/noninteractive');
|
|
30
32
|
|
|
31
33
|
function sleep(ms) {
|
|
32
34
|
return new Promise((r) => setTimeout(r, ms));
|
|
@@ -103,17 +105,31 @@ async function terminalAtris() {
|
|
|
103
105
|
// atris terminal --help
|
|
104
106
|
const args = process.argv.slice(3);
|
|
105
107
|
|
|
106
|
-
if (args.length === 0 || args
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
108
|
+
if (args.length === 0 || argsWantHelp(args) || wantsJson(args)) {
|
|
109
|
+
const usage = 'Usage: atris terminal [business] <command> [--timeout N]';
|
|
110
|
+
if (wantsJson(args)) {
|
|
111
|
+
console.log(JSON.stringify({
|
|
112
|
+
ok: false,
|
|
113
|
+
command: 'terminal',
|
|
114
|
+
error: 'usage',
|
|
115
|
+
usage,
|
|
116
|
+
}, null, 2));
|
|
117
|
+
} else {
|
|
118
|
+
console.log(usage);
|
|
119
|
+
console.log('');
|
|
120
|
+
console.log(' atris terminal example-co "ls /workspace/atris/"');
|
|
121
|
+
console.log(' atris terminal "find /workspace -name \\"*.md\\"" # auto-detect business');
|
|
122
|
+
console.log(' atris terminal example-co "rm -rf /workspace/cruft" --timeout 30');
|
|
123
|
+
console.log('');
|
|
124
|
+
console.log(' --timeout N seconds to wait for the command (default 30, max 120)');
|
|
125
|
+
}
|
|
126
|
+
process.exit(2);
|
|
115
127
|
}
|
|
116
128
|
|
|
129
|
+
const gate = requireAccountBound(args);
|
|
130
|
+
if (!gate.ok) process.exit(refuseAccountGlobal());
|
|
131
|
+
args.splice(0, args.length, ...gate.args);
|
|
132
|
+
|
|
117
133
|
// Parse --timeout
|
|
118
134
|
let timeoutSec = 30;
|
|
119
135
|
const tIdx = args.indexOf('--timeout');
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { apiRequestJson } = require('../utils/api');
|
|
4
|
+
const { loadCredentials, openBrowser } = require('../utils/auth');
|
|
5
|
+
const {
|
|
6
|
+
NOT_LOGGED_IN,
|
|
7
|
+
wantsHelp,
|
|
8
|
+
wantsJson,
|
|
9
|
+
tokenFrom,
|
|
10
|
+
oneLine,
|
|
11
|
+
printResult,
|
|
12
|
+
formatDollars,
|
|
13
|
+
checkoutUrl,
|
|
14
|
+
parseAmountUsd,
|
|
15
|
+
} = require('../lib/developer-api');
|
|
16
|
+
|
|
17
|
+
function showTopupHelp() {
|
|
18
|
+
console.log('usage: atris topup [amount_usd] [--json]');
|
|
19
|
+
console.log('create a stripe checkout for credits and print the url. default amount is 10.');
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
async function topupCommand(args = [], deps = {}) {
|
|
23
|
+
if (wantsHelp(args)) {
|
|
24
|
+
showTopupHelp();
|
|
25
|
+
return 0;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const json = wantsJson(args);
|
|
29
|
+
const io = { log: deps.log || console.log, err: deps.err || console.error };
|
|
30
|
+
const parsed = parseAmountUsd(args, 10);
|
|
31
|
+
if (!parsed.ok) {
|
|
32
|
+
printResult({ json, ok: false, error: parsed.error, payload: { ok: false, error: parsed.error } }, io);
|
|
33
|
+
return 1;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const load = deps.loadCredentials || loadCredentials;
|
|
37
|
+
const request = deps.apiRequestJson || apiRequestJson;
|
|
38
|
+
const open = deps.openBrowser || openBrowser;
|
|
39
|
+
const token = tokenFrom(load());
|
|
40
|
+
if (!token) {
|
|
41
|
+
printResult({ json, ok: false, error: NOT_LOGGED_IN, payload: { ok: false, error: NOT_LOGGED_IN } }, io);
|
|
42
|
+
return 1;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
try {
|
|
46
|
+
const result = await request('/credits/purchase', {
|
|
47
|
+
method: 'POST',
|
|
48
|
+
token,
|
|
49
|
+
body: { amount_usd: parsed.amount },
|
|
50
|
+
});
|
|
51
|
+
if (!result || !result.ok) {
|
|
52
|
+
const error = oneLine(result && result.error);
|
|
53
|
+
printResult({ json, ok: false, error, payload: { ok: false, error, status: result && result.status } }, io);
|
|
54
|
+
return 1;
|
|
55
|
+
}
|
|
56
|
+
const data = result.data || {};
|
|
57
|
+
const url = checkoutUrl(data);
|
|
58
|
+
if (!url) {
|
|
59
|
+
const error = 'checkout url was missing from the response';
|
|
60
|
+
printResult({ json, ok: false, error, payload: { ok: false, error } }, io);
|
|
61
|
+
return 1;
|
|
62
|
+
}
|
|
63
|
+
const credits = data.credits_to_add != null ? data.credits_to_add : parsed.amount * 100;
|
|
64
|
+
const text = `open this checkout to add ${formatDollars(parsed.amount)} (${credits} credits):\n${url}`;
|
|
65
|
+
printResult({
|
|
66
|
+
json,
|
|
67
|
+
ok: true,
|
|
68
|
+
text,
|
|
69
|
+
payload: {
|
|
70
|
+
ok: true,
|
|
71
|
+
amount_usd: parsed.amount,
|
|
72
|
+
credits_to_add: credits,
|
|
73
|
+
checkout_url: url,
|
|
74
|
+
},
|
|
75
|
+
}, io);
|
|
76
|
+
if (!json) {
|
|
77
|
+
const tty = deps.isTty != null ? deps.isTty : Boolean(process.stdout && process.stdout.isTTY);
|
|
78
|
+
if (tty) open(url);
|
|
79
|
+
}
|
|
80
|
+
return 0;
|
|
81
|
+
} catch (error) {
|
|
82
|
+
const message = oneLine(error && error.message);
|
|
83
|
+
printResult({ json, ok: false, error: message, payload: { ok: false, error: message } }, io);
|
|
84
|
+
return 1;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
module.exports = { topupCommand, showTopupHelp };
|
package/commands/truth.js
CHANGED
|
@@ -3,13 +3,18 @@
|
|
|
3
3
|
// 1. .atris/state/missions.jsonl — mission states (dedupe by id, latest wins)
|
|
4
4
|
// 2. ~/.atris/tasks.db — task counts by status, scoped to this workspace unless --all is passed
|
|
5
5
|
// 3. atris/features/*/ — validate.md frontmatter + newest proof/ receipt age
|
|
6
|
-
// 4.
|
|
6
|
+
// 4. atris/loops/*.md — workspace loops by default; pass --global for ~/.atris/heartbeat
|
|
7
7
|
|
|
8
8
|
const fs = require('fs');
|
|
9
9
|
const path = require('path');
|
|
10
10
|
const os = require('os');
|
|
11
11
|
const { execFileSync } = require('child_process');
|
|
12
12
|
const taskDb = require('../lib/task-db');
|
|
13
|
+
const { parseScopeFlag } = require('../lib/cli-scope');
|
|
14
|
+
const {
|
|
15
|
+
requireAccountBound,
|
|
16
|
+
refuseAccountGlobal,
|
|
17
|
+
} = require('../lib/account-bound');
|
|
13
18
|
|
|
14
19
|
const STALE_DAYS = 7;
|
|
15
20
|
|
|
@@ -186,20 +191,61 @@ function loadHeartbeats() {
|
|
|
186
191
|
});
|
|
187
192
|
}
|
|
188
193
|
|
|
194
|
+
// Workspace loops live in atris/loops/*.md (not ~/.atris/heartbeat).
|
|
195
|
+
function loadWorkspaceLoops(cwd) {
|
|
196
|
+
const dir = path.join(cwd, 'atris', 'loops');
|
|
197
|
+
if (!fs.existsSync(dir)) return [];
|
|
198
|
+
const out = [];
|
|
199
|
+
for (const name of fs.readdirSync(dir).sort()) {
|
|
200
|
+
if (!name.endsWith('.md') || name === 'LOOPS.md' || name === 'TICK.md') continue;
|
|
201
|
+
const filePath = path.join(dir, name);
|
|
202
|
+
let text = '';
|
|
203
|
+
try { text = fs.readFileSync(filePath, 'utf8'); } catch { continue; }
|
|
204
|
+
const logDates = [...text.matchAll(/^- (\d{4}-\d{2}-\d{2}):/gm)].map((m) => m[1]).sort();
|
|
205
|
+
let verdict;
|
|
206
|
+
let lastRunAgeDays = null;
|
|
207
|
+
if (logDates.length === 0) {
|
|
208
|
+
verdict = 'never-ran';
|
|
209
|
+
} else {
|
|
210
|
+
const newest = logDates[logDates.length - 1];
|
|
211
|
+
lastRunAgeDays = daysAgo(Date.parse(`${newest}T00:00:00Z`));
|
|
212
|
+
if (lastRunAgeDays != null && lastRunAgeDays > STALE_DAYS) verdict = 'stale';
|
|
213
|
+
else verdict = 'proven';
|
|
214
|
+
}
|
|
215
|
+
out.push({
|
|
216
|
+
id: name.replace(/\.md$/, ''),
|
|
217
|
+
verdict,
|
|
218
|
+
lastRunAgeDays,
|
|
219
|
+
fails: 0,
|
|
220
|
+
});
|
|
221
|
+
}
|
|
222
|
+
return out;
|
|
223
|
+
}
|
|
224
|
+
|
|
189
225
|
function truthCommand(args = []) {
|
|
190
226
|
const cwd = process.cwd();
|
|
191
|
-
const
|
|
192
|
-
const
|
|
193
|
-
const
|
|
227
|
+
const scopeFlag = parseScopeFlag(args);
|
|
228
|
+
const rest = scopeFlag.args;
|
|
229
|
+
const json = rest.includes('--json') || args.includes('--json');
|
|
230
|
+
const summary = rest.includes('--summary') || args.includes('--summary');
|
|
231
|
+
const all = rest.includes('--all') || args.includes('--all');
|
|
232
|
+
const hasAtris = fs.existsSync(path.join(cwd, 'atris'));
|
|
233
|
+
|
|
234
|
+
// Unbound scratch folders must not dump machine-global heartbeats.
|
|
235
|
+
// A local atris/ tree (or --global / --account) is enough to continue.
|
|
236
|
+
if (!hasAtris && !scopeFlag.global) {
|
|
237
|
+
const gate = requireAccountBound(rest);
|
|
238
|
+
if (!gate.ok) return refuseAccountGlobal();
|
|
239
|
+
}
|
|
194
240
|
|
|
195
241
|
const missions = loadMissions(cwd);
|
|
196
242
|
const taskCounts = loadTaskCounts({ cwd, all });
|
|
197
|
-
const scope = all
|
|
243
|
+
const scope = all || scopeFlag.global
|
|
198
244
|
? { kind: 'global' }
|
|
199
245
|
: { kind: 'workspace', workspace_root: taskCounts.workspaceRoot };
|
|
200
246
|
const tasks = taskCounts.counts;
|
|
201
247
|
const features = loadFeatures(cwd);
|
|
202
|
-
const heartbeats = loadHeartbeats();
|
|
248
|
+
const heartbeats = scopeFlag.global ? loadHeartbeats() : loadWorkspaceLoops(cwd);
|
|
203
249
|
|
|
204
250
|
const featureTally = {};
|
|
205
251
|
for (const f of features) featureTally[f.verdict] = (featureTally[f.verdict] || 0) + 1;
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { apiRequestJson } = require('../utils/api');
|
|
4
|
+
const { loadCredentials } = require('../utils/auth');
|
|
5
|
+
const {
|
|
6
|
+
NOT_LOGGED_IN,
|
|
7
|
+
ROLLOUT_MESSAGE,
|
|
8
|
+
wantsHelp,
|
|
9
|
+
wantsJson,
|
|
10
|
+
tokenFrom,
|
|
11
|
+
isNotFound,
|
|
12
|
+
oneLine,
|
|
13
|
+
printResult,
|
|
14
|
+
} = require('../lib/developer-api');
|
|
15
|
+
|
|
16
|
+
function showUsageHelp() {
|
|
17
|
+
console.log('usage: atris usage [--json]');
|
|
18
|
+
console.log('show developer api usage for the logged-in account.');
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function renderUsage(data = {}) {
|
|
22
|
+
const raw = data && typeof data === 'object' ? data : {};
|
|
23
|
+
const usage = raw.usage && typeof raw.usage === 'object' ? raw.usage : raw;
|
|
24
|
+
const lines = [];
|
|
25
|
+
if (raw.agent_name || raw.agent_id) {
|
|
26
|
+
lines.push(`${raw.agent_name || 'agent'} ${raw.agent_id || ''}`.trim());
|
|
27
|
+
}
|
|
28
|
+
const keys = Object.keys(usage).filter((key) => key !== 'agent_name' && key !== 'agent_id');
|
|
29
|
+
if (keys.length === 0) {
|
|
30
|
+
lines.push('no usage recorded.');
|
|
31
|
+
return lines.join('\n');
|
|
32
|
+
}
|
|
33
|
+
for (const key of keys) {
|
|
34
|
+
const value = usage[key];
|
|
35
|
+
if (value && typeof value === 'object') {
|
|
36
|
+
lines.push(`${key}: ${oneLine(value)}`);
|
|
37
|
+
} else {
|
|
38
|
+
lines.push(`${key}: ${value}`);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
return lines.join('\n');
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
async function usageCommand(args = [], deps = {}) {
|
|
45
|
+
if (wantsHelp(args)) {
|
|
46
|
+
showUsageHelp();
|
|
47
|
+
return 0;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const json = wantsJson(args);
|
|
51
|
+
const io = { log: deps.log || console.log, err: deps.err || console.error };
|
|
52
|
+
const load = deps.loadCredentials || loadCredentials;
|
|
53
|
+
const request = deps.apiRequestJson || apiRequestJson;
|
|
54
|
+
const token = tokenFrom(load());
|
|
55
|
+
if (!token) {
|
|
56
|
+
printResult({ json, ok: false, error: NOT_LOGGED_IN, payload: { ok: false, error: NOT_LOGGED_IN } }, io);
|
|
57
|
+
return 1;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
try {
|
|
61
|
+
const result = await request('/developer/usage', { method: 'GET', token });
|
|
62
|
+
if (isNotFound(result)) {
|
|
63
|
+
printResult({
|
|
64
|
+
json,
|
|
65
|
+
ok: false,
|
|
66
|
+
error: ROLLOUT_MESSAGE,
|
|
67
|
+
payload: { ok: false, error: ROLLOUT_MESSAGE, status: 404 },
|
|
68
|
+
}, io);
|
|
69
|
+
return 1;
|
|
70
|
+
}
|
|
71
|
+
if (!result || !result.ok) {
|
|
72
|
+
const error = oneLine(result && result.error);
|
|
73
|
+
printResult({ json, ok: false, error, payload: { ok: false, error, status: result && result.status } }, io);
|
|
74
|
+
return 1;
|
|
75
|
+
}
|
|
76
|
+
const data = result.data || {};
|
|
77
|
+
printResult({
|
|
78
|
+
json,
|
|
79
|
+
ok: true,
|
|
80
|
+
text: renderUsage(data),
|
|
81
|
+
payload: { ok: true, ...data },
|
|
82
|
+
}, io);
|
|
83
|
+
return 0;
|
|
84
|
+
} catch (error) {
|
|
85
|
+
const message = oneLine(error && error.message);
|
|
86
|
+
printResult({ json, ok: false, error: message, payload: { ok: false, error: message } }, io);
|
|
87
|
+
return 1;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
module.exports = { usageCommand, showUsageHelp, renderUsage };
|
package/commands/verify.js
CHANGED
|
@@ -119,7 +119,7 @@ function verifyWorkspace(cwd, atrisDir) {
|
|
|
119
119
|
}
|
|
120
120
|
console.log('');
|
|
121
121
|
|
|
122
|
-
return results;
|
|
122
|
+
return results.issues.length === 0 ? 0 : 1;
|
|
123
123
|
}
|
|
124
124
|
|
|
125
125
|
/**
|
|
@@ -208,7 +208,8 @@ function verifyTask(cwd, atrisDir, taskId) {
|
|
|
208
208
|
}
|
|
209
209
|
console.log('');
|
|
210
210
|
|
|
211
|
-
|
|
211
|
+
const mapOk = mapCheck.documented !== false;
|
|
212
|
+
return (passed === total && testPass && mapOk) ? 0 : 1;
|
|
212
213
|
}
|
|
213
214
|
|
|
214
215
|
/**
|
package/commands/version.js
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
const fs = require('fs');
|
|
2
2
|
const path = require('path');
|
|
3
|
+
const { rejectUnsupportedJson } = require('../lib/noninteractive');
|
|
3
4
|
|
|
4
5
|
const PACKAGE_JSON_PATH = path.join(__dirname, '..', 'package.json');
|
|
5
6
|
|
|
6
|
-
function showVersion() {
|
|
7
|
+
function showVersion(args = process.argv.slice(3)) {
|
|
8
|
+
rejectUnsupportedJson('version', args);
|
|
7
9
|
try {
|
|
8
10
|
const packageJson = JSON.parse(fs.readFileSync(PACKAGE_JSON_PATH, 'utf8'));
|
|
9
11
|
console.log(`atris v${packageJson.version}`);
|
package/commands/voice.js
CHANGED
|
@@ -176,7 +176,11 @@ function voiceCommand(args, options = {}) {
|
|
|
176
176
|
const subcommand = args[0];
|
|
177
177
|
const stdout = options.stdout || process.stdout;
|
|
178
178
|
const stderr = options.stderr || process.stderr;
|
|
179
|
-
if (!subcommand
|
|
179
|
+
if (!subcommand) {
|
|
180
|
+
showVoiceHelp(stderr);
|
|
181
|
+
return 2;
|
|
182
|
+
}
|
|
183
|
+
if (subcommand === 'help' || subcommand === '--help' || subcommand === '-h') {
|
|
180
184
|
showVoiceHelp(stdout);
|
|
181
185
|
return 0;
|
|
182
186
|
}
|
package/commands/who.js
CHANGED
|
@@ -5,6 +5,7 @@ const path = require('node:path');
|
|
|
5
5
|
const { execFileSync } = require('node:child_process');
|
|
6
6
|
|
|
7
7
|
const { repoRoot } = require('./stream');
|
|
8
|
+
const { parseScopeFlag, pathUnderRoot } = require('../lib/cli-scope');
|
|
8
9
|
const {
|
|
9
10
|
buildWorkforcePresence,
|
|
10
11
|
isFinishedReceipt,
|
|
@@ -73,6 +74,34 @@ function collectReceipts(root, fsModule = fs) {
|
|
|
73
74
|
return receipts;
|
|
74
75
|
}
|
|
75
76
|
|
|
77
|
+
function processCwds(pids, deps = {}) {
|
|
78
|
+
const byPid = new Map();
|
|
79
|
+
const list = (Array.isArray(pids) ? pids : []).map(String).filter(Boolean);
|
|
80
|
+
if (!list.length) return byPid;
|
|
81
|
+
const readlink = deps.readlink || fs.readlinkSync;
|
|
82
|
+
const execFile = deps.execFile || execFileSync;
|
|
83
|
+
if (process.platform === 'linux') {
|
|
84
|
+
for (const pid of list) {
|
|
85
|
+
try { byPid.set(String(pid), readlink(`/proc/${pid}/cwd`)); } catch {}
|
|
86
|
+
}
|
|
87
|
+
return byPid;
|
|
88
|
+
}
|
|
89
|
+
try {
|
|
90
|
+
const out = execFile('lsof', ['-a', '-d', 'cwd', '-Fn', '-p', list.join(',')], {
|
|
91
|
+
encoding: 'utf8',
|
|
92
|
+
stdio: ['pipe', 'pipe', 'ignore'],
|
|
93
|
+
});
|
|
94
|
+
let current = null;
|
|
95
|
+
for (const line of String(out || '').split(/\r?\n/)) {
|
|
96
|
+
if (line.startsWith('p')) current = line.slice(1);
|
|
97
|
+
else if (line.startsWith('n') && current) byPid.set(current, line.slice(1));
|
|
98
|
+
}
|
|
99
|
+
} catch {
|
|
100
|
+
// keep empty map
|
|
101
|
+
}
|
|
102
|
+
return byPid;
|
|
103
|
+
}
|
|
104
|
+
|
|
76
105
|
function collectProcesses(deps = {}) {
|
|
77
106
|
if (Array.isArray(deps.processes)) return deps.processes;
|
|
78
107
|
const execFile = deps.execFile || execFileSync;
|
|
@@ -81,24 +110,50 @@ function collectProcesses(deps = {}) {
|
|
|
81
110
|
encoding: 'utf8',
|
|
82
111
|
stdio: ['ignore', 'pipe', 'ignore'],
|
|
83
112
|
});
|
|
84
|
-
|
|
113
|
+
const rows = parsePsOutput(output);
|
|
114
|
+
const cwdByPid = processCwds(rows.map((row) => row.pid), deps);
|
|
115
|
+
return rows.map((row) => ({
|
|
116
|
+
...row,
|
|
117
|
+
cwd: cwdByPid.get(String(row.pid)) || null,
|
|
118
|
+
}));
|
|
85
119
|
} catch {
|
|
86
120
|
return [];
|
|
87
121
|
}
|
|
88
122
|
}
|
|
89
123
|
|
|
124
|
+
function filterProcessesToWorkspace(processes, root) {
|
|
125
|
+
const resolved = path.resolve(root);
|
|
126
|
+
return (Array.isArray(processes) ? processes : []).filter((row) => {
|
|
127
|
+
if (row.cwd && pathUnderRoot(row.cwd, resolved)) return true;
|
|
128
|
+
const command = String(row.command || '');
|
|
129
|
+
return command.includes(resolved) || command.includes(root);
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
|
|
90
133
|
function collectWorkforcePresence(deps = {}) {
|
|
91
134
|
const fsModule = deps.fs || fs;
|
|
92
135
|
const root = deps.root || repoRoot(deps.cwd || process.cwd());
|
|
136
|
+
const scope = deps.scope === 'global' ? 'global' : 'workspace';
|
|
93
137
|
const receipts = Array.isArray(deps.receipts) ? deps.receipts : collectReceipts(root, fsModule);
|
|
94
|
-
|
|
138
|
+
let processes;
|
|
139
|
+
if (Array.isArray(deps.processes)) {
|
|
140
|
+
// Caller-owned fixture/list: do not re-scope.
|
|
141
|
+
processes = deps.processes;
|
|
142
|
+
} else {
|
|
143
|
+
processes = collectProcesses(deps);
|
|
144
|
+
if (scope !== 'global') {
|
|
145
|
+
processes = filterProcessesToWorkspace(processes, root);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
const presence = buildWorkforcePresence({
|
|
95
149
|
nowMs: typeof deps.now === 'function' ? deps.now() : Date.now(),
|
|
96
150
|
staleAfterMs: deps.staleAfterMs,
|
|
97
|
-
processes
|
|
151
|
+
processes,
|
|
98
152
|
tasks: Array.isArray(deps.tasks) ? deps.tasks : collectTasks(root, fsModule),
|
|
99
153
|
missions: Array.isArray(deps.missions) ? deps.missions : collectMissions(root, fsModule),
|
|
100
154
|
receipts,
|
|
101
155
|
});
|
|
156
|
+
return { ...presence, scope };
|
|
102
157
|
}
|
|
103
158
|
|
|
104
159
|
function archiveFinishedRuns(root, receipts, fsModule = fs) {
|
|
@@ -132,11 +187,12 @@ function helpText() {
|
|
|
132
187
|
return [
|
|
133
188
|
'atris who - show local engines and team members from live process and final run state',
|
|
134
189
|
'',
|
|
135
|
-
'usage: atris who [--json]',
|
|
190
|
+
'usage: atris who [--json] [--global]',
|
|
136
191
|
'usage: atris who --clear [--json]',
|
|
137
192
|
'',
|
|
193
|
+
'default scope is this workspace. pass --global for the whole machine.',
|
|
138
194
|
'--clear archives finished run receipts under atris/runs/archive/.',
|
|
139
|
-
].join('
|
|
195
|
+
].join('\\n');
|
|
140
196
|
}
|
|
141
197
|
|
|
142
198
|
function whoCommand(args = [], deps = {}) {
|
|
@@ -146,25 +202,27 @@ function whoCommand(args = [], deps = {}) {
|
|
|
146
202
|
write(`${helpText()}\n`);
|
|
147
203
|
return 0;
|
|
148
204
|
}
|
|
149
|
-
|
|
150
|
-
|
|
205
|
+
const scope = parseScopeFlag(args);
|
|
206
|
+
const rest = scope.args;
|
|
207
|
+
if (rest.some((arg) => !['--json', '--clear'].includes(arg))) {
|
|
208
|
+
error('usage: atris who [--json] [--clear] [--global]\n');
|
|
151
209
|
return 2;
|
|
152
210
|
}
|
|
153
211
|
|
|
154
212
|
const fsModule = deps.fs || fs;
|
|
155
213
|
const root = deps.root || repoRoot(deps.cwd || process.cwd());
|
|
156
214
|
const receipts = Array.isArray(deps.receipts) ? deps.receipts : collectReceipts(root, fsModule);
|
|
157
|
-
if (
|
|
215
|
+
if (rest.includes('--clear')) {
|
|
158
216
|
const summary = archiveFinishedRuns(root, receipts, fsModule);
|
|
159
|
-
if (
|
|
217
|
+
if (rest.includes('--json')) write(`${JSON.stringify(summary, null, 2)}\n`);
|
|
160
218
|
else if (summary.failed.length) write(`archived ${summary.archived} finished runs; ${summary.failed.length} could not be archived.\n`);
|
|
161
219
|
else if (summary.archived) write(`archived ${summary.archived} finished run${summary.archived === 1 ? '' : 's'} in ${summary.archive_dir}.\n`);
|
|
162
220
|
else write('no finished runs to clear.\n');
|
|
163
221
|
return summary.failed.length ? 1 : 0;
|
|
164
222
|
}
|
|
165
223
|
|
|
166
|
-
const presence = collectWorkforcePresence({ ...deps, root, receipts });
|
|
167
|
-
const output =
|
|
224
|
+
const presence = collectWorkforcePresence({ ...deps, root, receipts, scope: scope.kind });
|
|
225
|
+
const output = rest.includes('--json')
|
|
168
226
|
? JSON.stringify(presence, null, 2)
|
|
169
227
|
: renderWorkforcePresence(presence);
|
|
170
228
|
write(`${output}\n`);
|