atris 3.52.0 → 3.55.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.
Files changed (131) hide show
  1. package/FOR_AGENTS.md +7 -0
  2. package/README.md +1 -1
  3. package/atris/policies/ANTISLOP.md +2 -1
  4. package/atris/skills/copy-editor/SKILL.md +2 -1
  5. package/atris/skills/design/SKILL.md +1 -1
  6. package/atris/skills/engines/SKILL.md +8 -7
  7. package/atris/skills/x-search/SKILL.md +24 -22
  8. package/atris/skills/youtube/SKILL.md +89 -40
  9. package/atris/team/_template/SOUL.md +40 -0
  10. package/atris/team/validator/MEMBER.md +1 -2
  11. package/ax +592 -91
  12. package/bin/atris +21 -0
  13. package/bin/atris.js +580 -378
  14. package/commands/api-key.js +170 -0
  15. package/commands/auth.js +453 -43
  16. package/commands/autoland.js +3 -1
  17. package/commands/autopilot-front.js +58 -11
  18. package/commands/avail.js +5 -4
  19. package/commands/balance.js +55 -0
  20. package/commands/bench.js +29 -1
  21. package/commands/brain.js +63 -12
  22. package/commands/brainstorm.js +18 -0
  23. package/commands/business.js +149 -1
  24. package/commands/chat-scan.js +17 -4
  25. package/commands/close.js +5 -1
  26. package/commands/computer.js +2 -2
  27. package/commands/doctor.js +174 -0
  28. package/commands/dream.js +7 -0
  29. package/commands/drill.js +34 -2
  30. package/commands/engine.js +47 -35
  31. package/commands/errors.js +58 -8
  32. package/commands/experiments.js +14 -0
  33. package/commands/feedback.js +61 -2
  34. package/commands/fleet-report.js +27 -7
  35. package/commands/fleet.js +98 -11
  36. package/commands/founder.js +60 -21
  37. package/commands/human-missions.js +79 -4
  38. package/commands/improve.js +54 -4
  39. package/commands/init.js +68 -92
  40. package/commands/install.js +40 -0
  41. package/commands/integrations.js +399 -30
  42. package/commands/interview.js +8 -0
  43. package/commands/land.js +48 -17
  44. package/commands/learn.js +29 -4
  45. package/commands/log.js +51 -0
  46. package/commands/member.js +68 -10
  47. package/commands/mission.js +339 -74
  48. package/commands/now.js +25 -6
  49. package/commands/orb.js +97 -2
  50. package/commands/pack.js +83 -9
  51. package/commands/playbook.js +381 -0
  52. package/commands/plugin.js +16 -0
  53. package/commands/radar.js +96 -40
  54. package/commands/recap.js +128 -21
  55. package/commands/release.js +102 -2
  56. package/commands/review.js +5 -10
  57. package/commands/revisions.js +55 -0
  58. package/commands/run-front.js +12 -4
  59. package/commands/scout.js +12 -1
  60. package/commands/search.js +123 -28
  61. package/commands/sign.js +2 -2
  62. package/commands/signup.js +12 -3
  63. package/commands/site-deploy.js +17 -4
  64. package/commands/site.js +2 -2
  65. package/commands/skill-eval.js +289 -0
  66. package/commands/skill.js +56 -6
  67. package/commands/social.js +603 -0
  68. package/commands/spaceship.js +69 -6
  69. package/commands/stream.js +8 -1
  70. package/commands/study.js +32 -19
  71. package/commands/sync.js +28 -22
  72. package/commands/task.js +418 -60
  73. package/commands/teach.js +72 -63
  74. package/commands/terminal.js +25 -9
  75. package/commands/topup.js +88 -0
  76. package/commands/truth.js +52 -6
  77. package/commands/usage.js +91 -0
  78. package/commands/verify.js +3 -2
  79. package/commands/version.js +3 -1
  80. package/commands/voice.js +5 -1
  81. package/commands/who.js +69 -11
  82. package/commands/wiki.js +7 -4
  83. package/commands/wish.js +76 -5
  84. package/commands/workflow.js +327 -145
  85. package/commands/worktree.js +1 -0
  86. package/commands/write.js +5 -0
  87. package/commands/x-search.js +340 -0
  88. package/commands/xp.js +5 -1
  89. package/commands/youtube.js +551 -33
  90. package/lib/account-bound.js +47 -0
  91. package/lib/auto-accept-certified.js +10 -2
  92. package/lib/auto-lessons.js +186 -0
  93. package/lib/autoland.js +4 -2
  94. package/lib/chat-log-scan.js +7 -4
  95. package/lib/cli-json.js +77 -0
  96. package/lib/cli-scope.js +26 -0
  97. package/lib/codex-flight.js +1 -0
  98. package/lib/conductor-artifacts.js +1 -1
  99. package/lib/context-gatherer.js +11 -0
  100. package/lib/developer-api.js +116 -0
  101. package/lib/engine-ask.js +11 -3
  102. package/lib/engine-registry.js +32 -7
  103. package/lib/feedback-triage.js +274 -0
  104. package/lib/first-minute.js +398 -0
  105. package/lib/fleet.js +115 -34
  106. package/lib/functional-owner.js +22 -0
  107. package/lib/known-commands.js +31 -5
  108. package/lib/member-scaffold.js +197 -0
  109. package/lib/mission-ledger-compact.js +127 -0
  110. package/lib/mission-root.js +4 -2
  111. package/lib/mission-runtime-loop.js +30 -2
  112. package/lib/next-moves.js +34 -34
  113. package/lib/noninteractive.js +88 -0
  114. package/lib/permission-grants.js +10 -1
  115. package/lib/policy-lessons.js +4 -1
  116. package/lib/revision-metric.js +279 -0
  117. package/lib/runner-command.js +5 -4
  118. package/lib/scratch-root.js +48 -0
  119. package/lib/skill-eval-gate.js +249 -0
  120. package/lib/task-db.js +22 -2
  121. package/lib/task-proof.js +75 -9
  122. package/lib/task-receipt.js +5 -1
  123. package/lib/verifier-quality.js +69 -0
  124. package/lib/wish-audit.js +2 -2
  125. package/lib/wish-delegate.js +10 -1
  126. package/lib/workspace-scaffold.js +270 -0
  127. package/package.json +4 -2
  128. package/scripts/det/checklist-score.js +191 -0
  129. package/scripts/det/ytsearch +31 -0
  130. package/scripts/outbound-artifact-gate.js +227 -0
  131. 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 — turn one bad turn into a permanent guard.
4
+ * atris teach — local cases under ./atris/teach only.
5
5
  *
6
- * atris teach list cases and which ones are guards
7
- * atris teach add --id <id> --prompt "…" --require "<fragment>" [--complaint "…"]
8
- * atris teach run [<id>] run one case (or all) and advance state
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
- * Thin shim over `python -m rl.teach` in the backend venv, which owns the
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 BACKEND_ROOT = '/Users/keshavrao/arena/atrisos-backend';
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 — a bad turn becomes a failing benchmark, then a guard');
19
+ console.log('atris teach — local cases under ./atris/teach');
33
20
  console.log('');
34
- console.log(' atris teach list cases (red = not yet fixed, guard = protected)');
35
- console.log(' atris teach add … record a misbehavior as a red case');
36
- console.log(' atris teach run [<id>] run a case; a red case is SUPPOSED to fail');
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(' add flags: --id <kebab> --prompt "…" --require "<fragment>" (repeatable)');
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
- * Map the operator-facing verbs onto `python -m rl.teach` arguments.
49
- * Returns null for help.
50
- */
51
- function buildArgs(subcommand, args) {
52
- if (!subcommand || subcommand === 'list') return ['list'];
53
- if (subcommand === 'help' || subcommand === '--help' || subcommand === '-h') return null;
54
- if (subcommand === 'add') return ['add', ...args];
55
- if (subcommand === 'guards') return ['run', '--guards', ...args];
56
- if (subcommand === 'mine') return ['mine', ...args];
57
- if (subcommand === 'drafts') return ['drafts', ...args];
58
- if (subcommand === 'fix') {
59
- // `atris teach fix <id>` -> `fix --case <id>`
60
- if (args.length && !args[0].startsWith('-')) return ['fix', '--case', args[0], ...args.slice(1)];
61
- return ['fix', ...args];
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
- module.exports = function teach(subcommand, ...args) {
75
- const moduleArgs = buildArgs(subcommand, args);
76
- if (moduleArgs === null) {
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;
@@ -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[0] === '--help' || args[0] === '-h') {
107
- console.log('Usage: atris terminal [business] <command> [--timeout N]');
108
- console.log('');
109
- console.log(' atris terminal example-co "ls /workspace/atris/"');
110
- console.log(' atris terminal "find /workspace -name \\"*.md\\"" # auto-detect business');
111
- console.log(' atris terminal example-co "rm -rf /workspace/cruft" --timeout 30');
112
- console.log('');
113
- console.log(' --timeout N seconds to wait for the command (default 30, max 120)');
114
- process.exit(0);
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. ~/.atris/heartbeat/{registry,state}.json declared loops vs last_run / fails
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 json = args.includes('--json');
192
- const summary = args.includes('--summary');
193
- const all = args.includes('--all');
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 };
@@ -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
- return results;
211
+ const mapOk = mapCheck.documented !== false;
212
+ return (passed === total && testPass && mapOk) ? 0 : 1;
212
213
  }
213
214
 
214
215
  /**
@@ -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 || subcommand === 'help' || subcommand === '--help' || subcommand === '-h') {
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
- return parsePsOutput(output);
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
- return buildWorkforcePresence({
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: collectProcesses(deps),
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('\n');
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
- if (args.some((arg) => !['--json', '--clear'].includes(arg))) {
150
- error('usage: atris who [--json] [--clear]\n');
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 (args.includes('--clear')) {
215
+ if (rest.includes('--clear')) {
158
216
  const summary = archiveFinishedRuns(root, receipts, fsModule);
159
- if (args.includes('--json')) write(`${JSON.stringify(summary, null, 2)}\n`);
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 = args.includes('--json')
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`);