atris 3.53.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 (119) 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/x-search/SKILL.md +24 -22
  7. package/atris/skills/youtube/SKILL.md +82 -33
  8. package/atris/team/_template/SOUL.md +40 -0
  9. package/atris/team/validator/MEMBER.md +1 -2
  10. package/bin/atris +21 -0
  11. package/bin/atris.js +563 -373
  12. package/commands/api-key.js +170 -0
  13. package/commands/auth.js +453 -43
  14. package/commands/autoland.js +3 -1
  15. package/commands/autopilot-front.js +58 -11
  16. package/commands/avail.js +5 -4
  17. package/commands/balance.js +55 -0
  18. package/commands/bench.js +29 -1
  19. package/commands/brain.js +63 -12
  20. package/commands/brainstorm.js +18 -0
  21. package/commands/business.js +149 -1
  22. package/commands/chat-scan.js +17 -4
  23. package/commands/close.js +5 -1
  24. package/commands/doctor.js +174 -0
  25. package/commands/dream.js +7 -0
  26. package/commands/drill.js +34 -2
  27. package/commands/engine.js +47 -35
  28. package/commands/errors.js +58 -8
  29. package/commands/experiments.js +14 -0
  30. package/commands/feedback.js +61 -2
  31. package/commands/fleet-report.js +27 -7
  32. package/commands/fleet.js +98 -11
  33. package/commands/founder.js +60 -21
  34. package/commands/human-missions.js +79 -4
  35. package/commands/improve.js +54 -4
  36. package/commands/init.js +68 -92
  37. package/commands/install.js +40 -0
  38. package/commands/integrations.js +399 -30
  39. package/commands/interview.js +8 -0
  40. package/commands/land.js +48 -17
  41. package/commands/learn.js +29 -4
  42. package/commands/log.js +51 -0
  43. package/commands/member.js +68 -10
  44. package/commands/mission.js +287 -50
  45. package/commands/now.js +15 -3
  46. package/commands/orb.js +97 -2
  47. package/commands/pack.js +83 -9
  48. package/commands/playbook.js +381 -0
  49. package/commands/plugin.js +16 -0
  50. package/commands/radar.js +31 -7
  51. package/commands/recap.js +128 -21
  52. package/commands/release.js +102 -2
  53. package/commands/review.js +5 -10
  54. package/commands/revisions.js +55 -0
  55. package/commands/run-front.js +12 -4
  56. package/commands/scout.js +12 -1
  57. package/commands/search.js +123 -28
  58. package/commands/sign.js +2 -2
  59. package/commands/signup.js +12 -3
  60. package/commands/site-deploy.js +17 -4
  61. package/commands/site.js +2 -2
  62. package/commands/skill-eval.js +289 -0
  63. package/commands/skill.js +56 -6
  64. package/commands/social.js +603 -0
  65. package/commands/spaceship.js +69 -6
  66. package/commands/stream.js +8 -1
  67. package/commands/study.js +32 -19
  68. package/commands/sync.js +28 -22
  69. package/commands/task.js +418 -60
  70. package/commands/teach.js +72 -63
  71. package/commands/terminal.js +25 -9
  72. package/commands/topup.js +88 -0
  73. package/commands/truth.js +52 -6
  74. package/commands/usage.js +91 -0
  75. package/commands/verify.js +3 -2
  76. package/commands/version.js +3 -1
  77. package/commands/voice.js +5 -1
  78. package/commands/who.js +69 -11
  79. package/commands/wiki.js +7 -4
  80. package/commands/wish.js +76 -5
  81. package/commands/workflow.js +327 -145
  82. package/commands/worktree.js +1 -0
  83. package/commands/write.js +5 -0
  84. package/commands/x-search.js +340 -0
  85. package/commands/xp.js +5 -1
  86. package/commands/youtube.js +551 -25
  87. package/lib/account-bound.js +47 -0
  88. package/lib/auto-accept-certified.js +8 -1
  89. package/lib/auto-lessons.js +186 -0
  90. package/lib/autoland.js +4 -2
  91. package/lib/chat-log-scan.js +7 -4
  92. package/lib/cli-json.js +77 -0
  93. package/lib/cli-scope.js +26 -0
  94. package/lib/codex-flight.js +1 -0
  95. package/lib/conductor-artifacts.js +1 -1
  96. package/lib/context-gatherer.js +11 -0
  97. package/lib/developer-api.js +116 -0
  98. package/lib/feedback-triage.js +274 -0
  99. package/lib/first-minute.js +398 -0
  100. package/lib/fleet.js +115 -34
  101. package/lib/functional-owner.js +22 -0
  102. package/lib/known-commands.js +31 -5
  103. package/lib/member-scaffold.js +197 -0
  104. package/lib/mission-root.js +4 -2
  105. package/lib/noninteractive.js +88 -0
  106. package/lib/policy-lessons.js +4 -1
  107. package/lib/revision-metric.js +279 -0
  108. package/lib/scratch-root.js +48 -0
  109. package/lib/skill-eval-gate.js +249 -0
  110. package/lib/task-db.js +4 -0
  111. package/lib/task-proof.js +69 -8
  112. package/lib/verifier-quality.js +69 -0
  113. package/lib/wish-audit.js +2 -2
  114. package/lib/wish-delegate.js +10 -1
  115. package/lib/workspace-scaffold.js +270 -0
  116. package/package.json +3 -2
  117. package/scripts/det/checklist-score.js +191 -0
  118. package/scripts/det/ytsearch +31 -0
  119. package/utils/update-check.js +15 -0
@@ -0,0 +1,174 @@
1
+ 'use strict';
2
+
3
+ const fs = require('fs');
4
+ const path = require('path');
5
+ const os = require('os');
6
+ const {
7
+ inspectInstallGitState,
8
+ formatCliVersionLine,
9
+ isGitCheckout,
10
+ } = require('../utils/update-check');
11
+
12
+ const TASK_NODE_MAJOR = 22;
13
+
14
+ function parseNodeVersion(raw = process.version) {
15
+ const match = String(raw || '').match(/v?(\d+)\.(\d+)\.(\d+)/);
16
+ if (!match) return { raw: String(raw || ''), major: 0, minor: 0, patch: 0 };
17
+ return {
18
+ raw: String(raw || ''),
19
+ major: Number(match[1]),
20
+ minor: Number(match[2]),
21
+ patch: Number(match[3]),
22
+ };
23
+ }
24
+
25
+ function detectWorkspaceRoot(cwd = process.cwd()) {
26
+ let current = path.resolve(cwd);
27
+ for (;;) {
28
+ if (fs.existsSync(path.join(current, 'atris', 'atris.md'))
29
+ || fs.existsSync(path.join(current, '.atris'))) {
30
+ return current;
31
+ }
32
+ const parent = path.dirname(current);
33
+ if (parent === current) return path.resolve(cwd);
34
+ current = parent;
35
+ }
36
+ }
37
+
38
+ function authState() {
39
+ try {
40
+ const { loadCredentials } = require('../utils/auth');
41
+ const creds = loadCredentials();
42
+ if (!creds) return { signed_in: false, email: null, source: null };
43
+ return {
44
+ signed_in: true,
45
+ email: creds.email || null,
46
+ source: process.env.ATRIS_TOKEN ? 'env' : (process.env.ATRIS_PROFILE ? 'profile' : 'credentials'),
47
+ };
48
+ } catch {
49
+ return { signed_in: false, email: null, source: null };
50
+ }
51
+ }
52
+
53
+ function publicBinInfo() {
54
+ const shimPath = path.join(__dirname, '..', 'bin', 'atris');
55
+ const entryPath = path.join(__dirname, '..', 'bin', 'atris.js');
56
+ let shimBytes = null;
57
+ try { shimBytes = fs.statSync(shimPath).size; } catch { shimBytes = null; }
58
+ let entryBytes = null;
59
+ try { entryBytes = fs.statSync(entryPath).size; } catch { entryBytes = null; }
60
+ return {
61
+ public_bin: 'bin/atris',
62
+ shim: Boolean(shimBytes != null && shimBytes < 2048),
63
+ shim_bytes: shimBytes,
64
+ entry: 'bin/atris.js',
65
+ entry_bytes: entryBytes,
66
+ note: 'public bin is a tiny POSIX shim that execs node on bin/atris.js',
67
+ };
68
+ }
69
+
70
+ function readCliVersion(packageRoot) {
71
+ try {
72
+ const pkg = JSON.parse(fs.readFileSync(path.join(packageRoot, 'package.json'), 'utf8'));
73
+ return typeof pkg.version === 'string' ? pkg.version : 'unknown';
74
+ } catch {
75
+ return 'unknown';
76
+ }
77
+ }
78
+
79
+ function installWarnings(packageRoot) {
80
+ const warnings = [];
81
+ if (!isGitCheckout(packageRoot)) return warnings;
82
+ const state = inspectInstallGitState(packageRoot);
83
+ if (state.dirty) {
84
+ warnings.push(`cli install is a dirty git checkout (${state.branch || 'detached'} @ ${state.head || 'unknown'})`);
85
+ } else if (state.detached) {
86
+ warnings.push(`cli install is a detached git checkout (@ ${state.head || 'unknown'})`);
87
+ }
88
+ return warnings;
89
+ }
90
+
91
+ function collectDoctor(options = {}) {
92
+ const packageRoot = options.packageRoot || path.join(__dirname, '..');
93
+ const node = parseNodeVersion(options.nodeVersion || process.version);
94
+ const workspace_root = detectWorkspaceRoot(options.cwd || process.cwd());
95
+ const task_support = node.major >= TASK_NODE_MAJOR;
96
+ const auth = authState();
97
+ const bin = publicBinInfo();
98
+ const cli_runnable = Boolean(process.execPath) && fs.existsSync(path.join(packageRoot, 'bin', 'atris.js'));
99
+ const version = readCliVersion(packageRoot);
100
+ const version_line = formatCliVersionLine(version, packageRoot);
101
+ const install_git = inspectInstallGitState(packageRoot);
102
+ const warnings = installWarnings(packageRoot);
103
+ return {
104
+ ok: true,
105
+ action: 'doctor',
106
+ version,
107
+ version_line,
108
+ node: {
109
+ version: node.raw.startsWith('v') ? node.raw : `v${node.raw}`,
110
+ major: node.major,
111
+ minor: node.minor,
112
+ patch: node.patch,
113
+ exec_path: process.execPath || null,
114
+ },
115
+ task_support,
116
+ task_support_requires: `node ${TASK_NODE_MAJOR}+`,
117
+ auth,
118
+ workspace_root,
119
+ home: os.homedir(),
120
+ cli_runnable,
121
+ bin,
122
+ install_git: {
123
+ is_git_repo: Boolean(install_git.isGitRepo),
124
+ dirty: Boolean(install_git.dirty),
125
+ branch: install_git.branch || null,
126
+ head: install_git.head || null,
127
+ },
128
+ warnings,
129
+ };
130
+ }
131
+
132
+ function renderDoctor(payload) {
133
+ const lines = [];
134
+ lines.push('atris doctor');
135
+ lines.push('');
136
+ lines.push(`version: ${payload.version_line || payload.version}`);
137
+ lines.push(`node: ${payload.node.version} (${payload.node.exec_path || 'unknown'})`);
138
+ lines.push(`task support (22+): ${payload.task_support ? 'yes' : 'no'}`);
139
+ lines.push(`auth: ${payload.auth.signed_in ? `signed in as ${payload.auth.email || 'unknown'}` : 'signed out'}`);
140
+ lines.push(`workspace: ${payload.workspace_root}`);
141
+ lines.push(`cli runnable: ${payload.cli_runnable ? 'yes' : 'no'}`);
142
+ lines.push(`public bin: ${payload.bin.public_bin} (${payload.bin.shim ? `${payload.bin.shim_bytes} byte shim` : 'not a shim'})`);
143
+ if (payload.warnings && payload.warnings.length) {
144
+ lines.push('');
145
+ for (const warning of payload.warnings) lines.push(`warning: ${warning}`);
146
+ }
147
+ if (!payload.task_support) {
148
+ lines.push('');
149
+ lines.push(`install Node ${TASK_NODE_MAJOR}+ before running atris task.`);
150
+ }
151
+ return lines.join('\n');
152
+ }
153
+
154
+ function doctorCommand(args = [], options = {}) {
155
+ if (args.includes('--help') || args.includes('-h') || args[0] === 'help') {
156
+ console.log('Usage: atris doctor [--json]');
157
+ console.log('');
158
+ console.log('Reports node version, task-support (22+), auth, workspace root, and whether the CLI can run.');
159
+ console.log('Public install entry is bin/atris (tiny POSIX shim) -> bin/atris.js.');
160
+ return 0;
161
+ }
162
+ const payload = collectDoctor(options);
163
+ if (args.includes('--json')) {
164
+ console.log(JSON.stringify(payload, null, 2));
165
+ } else {
166
+ console.log(renderDoctor(payload));
167
+ }
168
+ const healthy = payload.cli_runnable && payload.task_support;
169
+ return healthy ? 0 : 1;
170
+ }
171
+
172
+ module.exports = {
173
+ doctorCommand,
174
+ };
package/commands/dream.js CHANGED
@@ -341,6 +341,13 @@ function showHelp(log = console.log) {
341
341
  async function dreamCommand(args = [], root = process.cwd(), options = {}) {
342
342
  const log = options.log || console.log;
343
343
  if (args.includes('--help') || args.includes('-h') || args[0] === 'help') return showHelp(log);
344
+ const { refuseHeadlessUnless } = require('../lib/noninteractive');
345
+ if (refuseHeadlessUnless(args, {
346
+ allowYes: true,
347
+ usage: 'Usage: atris dream [--yes]\nHeadless dream needs --yes.',
348
+ })) {
349
+ return 2;
350
+ }
344
351
  const result = await runDream(root, options);
345
352
  if (result.cards && result.cards.length) {
346
353
  const noun = result.cards.length === 1 ? 'card' : 'cards';
package/commands/drill.js CHANGED
@@ -19,15 +19,42 @@ function assertOk(condition, message) {
19
19
  if (!condition) throw new Error(message);
20
20
  }
21
21
 
22
+ function textFromWriteSync(data, offset, length) {
23
+ if (Buffer.isBuffer(data)) {
24
+ const start = Number.isInteger(offset) ? offset : 0;
25
+ const end = Number.isInteger(length) ? start + length : data.length;
26
+ return data.toString('utf8', start, end);
27
+ }
28
+ return String(data);
29
+ }
30
+
22
31
  async function captureCommand(fn) {
23
32
  const originalLog = console.log;
24
33
  const originalError = console.error;
25
34
  const originalExit = process.exit;
35
+ const originalWriteSync = fs.writeSync;
26
36
  const originalExitCode = process.exitCode;
27
37
  const stdout = [];
28
38
  const stderr = [];
39
+ let stdoutWrites = '';
40
+ let stderrWrites = '';
41
+ const stdoutFd = process.stdout.fd;
42
+ const stderrFd = process.stderr.fd;
29
43
  console.log = (...args) => stdout.push(args.map(String).join(' '));
30
44
  console.error = (...args) => stderr.push(args.map(String).join(' '));
45
+ fs.writeSync = function captureWriteSync(fd, data, offset, length) {
46
+ if (fd === stdoutFd || fd === process.stdout.fd) {
47
+ const text = textFromWriteSync(data, offset, length);
48
+ stdoutWrites += text;
49
+ return Buffer.byteLength(text);
50
+ }
51
+ if (fd === stderrFd || fd === process.stderr.fd) {
52
+ const text = textFromWriteSync(data, offset, length);
53
+ stderrWrites += text;
54
+ return Buffer.byteLength(text);
55
+ }
56
+ return originalWriteSync.apply(this, arguments);
57
+ };
31
58
  process.exitCode = undefined;
32
59
  process.exit = (code = 0) => {
33
60
  const error = new Error(`process.exit(${code})`);
@@ -37,16 +64,21 @@ async function captureCommand(fn) {
37
64
  };
38
65
  try {
39
66
  const value = await fn();
40
- return { value, stdout: stdout.join('\n'), stderr: stderr.join('\n') };
67
+ return {
68
+ value,
69
+ stdout: `${stdout.join('\n')}${stdoutWrites}`,
70
+ stderr: `${stderr.join('\n')}${stderrWrites}`,
71
+ };
41
72
  } catch (error) {
42
73
  if (error && error.isProcessExit) {
43
- const output = `${stderr.join('\n')}\n${stdout.join('\n')}`.trim();
74
+ const output = `${stderr.join('\n')}${stderrWrites}\n${stdout.join('\n')}${stdoutWrites}`.trim();
44
75
  error.message = output || `process.exit(${error.exitCode})`;
45
76
  }
46
77
  throw error;
47
78
  } finally {
48
79
  console.log = originalLog;
49
80
  console.error = originalError;
81
+ fs.writeSync = originalWriteSync;
50
82
  process.exit = originalExit;
51
83
  process.exitCode = originalExitCode;
52
84
  }
@@ -24,6 +24,7 @@ const {
24
24
  RUNNER_PROFILE_NAMES,
25
25
  buildRunnerCommand,
26
26
  } = require('../lib/runner-command');
27
+ const { parseScopeFlag } = require('../lib/cli-scope');
27
28
  const {
28
29
  ENGINE_ROLES,
29
30
  ENGINE_DUTIES,
@@ -901,15 +902,18 @@ async function runEngineSeedCommand(args, root, deps = {}) {
901
902
  return 0;
902
903
  }
903
904
 
904
- function printRoster(root) {
905
+ function printRoster(root, { scope = 'workspace' } = {}) {
905
906
  reconcileStaleEngineProbeErrors(root);
906
907
  const list = roster(root);
907
- const found = list.filter((e) => e.installed).length;
908
+ const scoped = scope === 'global'
909
+ ? list
910
+ : list.filter((engine) => engine.installed || engine.default || engine.health.status === 'ready');
911
+ const found = scoped.filter((e) => e.installed).length;
908
912
  const current = resolveDefaultEngine(root);
909
913
  console.log('');
910
- console.log(` engines: ${found} intelligence${found === 1 ? '' : 's'} found`);
914
+ console.log(` engines: ${found} intelligence${found === 1 ? '' : 's'} found (${scope})`);
911
915
  console.log('');
912
- for (const engine of list) {
916
+ for (const engine of scoped) {
913
917
  const mark = engine.default ? '→' : ' ';
914
918
  const state = engine.health.status === 'not_installed' ? 'not installed' : engine.health.status.replace(/_/g, ' ');
915
919
  const roles = engine.roles.join(',');
@@ -918,12 +922,33 @@ function printRoster(root) {
918
922
  if (engine.duty) details.push(`duty: ${engine.duty}`);
919
923
  console.log(` ${details.join(' ')}`);
920
924
  }
925
+ if (scope !== 'global' && list.length > scoped.length) {
926
+ console.log(` … ${list.length - scoped.length} not installed hidden (pass --global to list)`);
927
+ }
921
928
  console.log('');
922
929
  console.log(` default: ${current.name}${current.source === 'saved' ? ' (set here)' : current.source === 'env' ? ' (this session)' : ''}`);
923
930
  console.log(` switch: atris engine <name> · one run: --engine <name> on mission run / autopilot / run`);
924
931
  console.log('');
925
932
  }
926
933
 
934
+ function registryPayload(root, { scope = 'workspace' } = {}) {
935
+ const current = resolveDefaultEngine(root);
936
+ const registry = readEngineRegistry(root);
937
+ const engines = registry.engines.map((engine) => ({
938
+ ...engine,
939
+ default: engine.id === current.name,
940
+ }));
941
+ const scoped = scope === 'global'
942
+ ? engines
943
+ : engines.filter((engine) => engine.installed || engine.default || (engine.health && engine.health.status === 'ready'));
944
+ return {
945
+ scope,
946
+ default: current.name,
947
+ source: current.source,
948
+ engines: scoped,
949
+ };
950
+ }
951
+
927
952
  function chartEngineLabel(engine) {
928
953
  const id = String(engine && (engine.id || engine.name) || '').trim();
929
954
  const models = Array.isArray(engine && engine.models)
@@ -1057,19 +1082,6 @@ function runSetEngineCommand(args, root) {
1057
1082
  }
1058
1083
  }
1059
1084
 
1060
- function registryPayload(root) {
1061
- const current = resolveDefaultEngine(root);
1062
- const registry = readEngineRegistry(root);
1063
- return {
1064
- default: current.name,
1065
- source: current.source,
1066
- engines: registry.engines.map((engine) => ({
1067
- ...engine,
1068
- default: engine.id === current.name,
1069
- })),
1070
- };
1071
- }
1072
-
1073
1085
  function parseSetFlag(args) {
1074
1086
  const prefix = '--set=';
1075
1087
  for (let i = 0; i < args.length; i += 1) {
@@ -1491,16 +1503,17 @@ function engineCommand(args = [], deps = {}) {
1491
1503
  return runEngineAskCommand([...shorthandArgs, '--engine', canonical], root, deps.engineAsk || {});
1492
1504
  }
1493
1505
 
1494
- const json = args.includes('--json');
1495
- const positional = args.filter((a) => !a.startsWith('--'));
1506
+ const scope = parseScopeFlag(args);
1507
+ const json = scope.args.includes('--json') || args.includes('--json');
1508
+ const positional = scope.args.filter((a) => !a.startsWith('--'));
1496
1509
  const sub = (positional[0] || '').trim();
1497
1510
 
1498
1511
  if (sub === 'login') {
1499
- return runEngineLoginCommand(args.slice(args.indexOf('login') + 1), root);
1512
+ return runEngineLoginCommand(scope.args.slice(scope.args.indexOf('login') + 1), root);
1500
1513
  }
1501
1514
 
1502
1515
  if (sub === 'seed') {
1503
- return runEngineSeedCommand(args.slice(args.indexOf('seed') + 1), root);
1516
+ return runEngineSeedCommand(scope.args.slice(scope.args.indexOf('seed') + 1), root);
1504
1517
  }
1505
1518
 
1506
1519
  if (sub === 'seats') {
@@ -1512,32 +1525,37 @@ function engineCommand(args = [], deps = {}) {
1512
1525
  }
1513
1526
 
1514
1527
  if (sub === 'resolve') {
1515
- return runResolveCommand(args.slice(args.indexOf('resolve') + 1), root);
1528
+ return runResolveCommand(scope.args.slice(scope.args.indexOf('resolve') + 1), root);
1516
1529
  }
1517
1530
 
1518
1531
  if (sub === 'health') {
1519
- return runHealthCommand(args.slice(args.indexOf('health') + 1), root);
1532
+ return runHealthCommand(scope.args.slice(scope.args.indexOf('health') + 1), root);
1520
1533
  }
1521
1534
 
1522
1535
  if (sub === 'doctor') {
1523
- return runDoctorCommand(args.slice(args.indexOf('doctor') + 1), root);
1536
+ return runDoctorCommand(scope.args.slice(scope.args.indexOf('doctor') + 1), root);
1524
1537
  }
1525
1538
 
1526
1539
  if (sub === 'set') {
1527
- return runSetEngineCommand(args.slice(args.indexOf('set') + 1), root);
1540
+ return runSetEngineCommand(scope.args.slice(scope.args.indexOf('set') + 1), root);
1528
1541
  }
1529
1542
 
1530
- if (sub === 'chart' || args.includes('--chart')) {
1543
+ if (sub === 'chart' || scope.args.includes('--chart') || args.includes('--chart')) {
1531
1544
  printEngineChart(root);
1532
1545
  return 0;
1533
1546
  }
1534
1547
 
1535
- if (!sub || sub === 'list' || sub === 'status') {
1548
+ if (!sub || sub === 'list' || sub === 'status' || sub === 'help') {
1549
+ if (sub === 'help' || args.includes('--help') || args.includes('-h')) {
1550
+ console.log('\n atris engine watch [<id>|latest] [--no-follow]\n follow one live transcript or list running engine work');
1551
+ console.log('\n atris engine roster + current default\n atris engines --chart show the fleet as an org chart\n atris engine list --json full registry: default + engines with tier, roles, fallback, health\n atris engine set <name> --duty leader|errands|learning [--models "a, b"]\n arrange the fleet and save its model policy\n atris engine resolve <role> [--json]\n choose the best ready engine for navigator|executor|validator\n atris engine health <name> --set ready|not_installed|credit_out\n flip runtime health, for example when credits run out\n atris engine doctor [--json]\n probe which engine CLIs are installed here and sync that into health policy\n atris engine <name> make that engine the default here\n atris engine test [name] preflight: run the engine CLI headless, report pass/fail\n atris engine ask "<question>" --engine <name> [--engine <name> ...]\n ask several engines in parallel without allowing edits\n atris engine ask --jobs <jobs.json>\n ask different read-only questions in parallel\n atris engine validate <receipt-path|latest> [--engine <name>]\n check ask answers with a different read-only referee\n atris engine validate scoreboard\n show pass rates by worker engine\n atris engine dispatch <task-id> [<task-id> ...] --engine cursor|codex [--prompt-file <f>] [--yolo]\n one-command claim, worktree, build, verify, ship, ready\n atris engine login <provider> --yes\n upload a local provider CLI login to the backend vault\n atris engine login <provider> --computer [--seat <name>]\n atris engine login <provider> --business <id> [--seat <name>]\n sign in on an Atris computer by device flow\n atris engine login --list | --remove <provider>\n list or remove vaulted provider logins\n atris engine seats show which named accounts are ready to work\n atris engine seed <provider> --business <id>|--user\n push a vaulted login onto an Atris computer\n atris engine reset back to the house default\n --engine <name> one run on that engine (mission run / autopilot / run)\n');
1552
+ return 0;
1553
+ }
1536
1554
  if (json) {
1537
- console.log(JSON.stringify(registryPayload(root), null, 2));
1555
+ console.log(JSON.stringify(registryPayload(root, { scope: scope.kind }), null, 2));
1538
1556
  return 0;
1539
1557
  }
1540
- printRoster(root);
1558
+ printRoster(root, { scope: scope.kind });
1541
1559
  return 0;
1542
1560
  }
1543
1561
 
@@ -1549,12 +1567,6 @@ function engineCommand(args = [], deps = {}) {
1549
1567
  return 0;
1550
1568
  }
1551
1569
 
1552
- if (sub === 'help') {
1553
- console.log('\n atris engine watch [<id>|latest] [--no-follow]\n follow one live transcript or list running engine work');
1554
- console.log('\n atris engine roster + current default\n atris engines --chart show the fleet as an org chart\n atris engine list --json full registry: default + engines with tier, roles, fallback, health\n atris engine set <name> --duty leader|errands|learning [--models "a, b"]\n arrange the fleet and save its model policy\n atris engine resolve <role> [--json]\n choose the best ready engine for navigator|executor|validator\n atris engine health <name> --set ready|not_installed|credit_out\n flip runtime health, for example when credits run out\n atris engine doctor [--json]\n probe which engine CLIs are installed here and sync that into health policy\n atris engine <name> make that engine the default here\n atris engine test [name] preflight: run the engine CLI headless, report pass/fail\n atris engine ask "<question>" --engine <name> [--engine <name> ...]\n ask several engines in parallel without allowing edits\n atris engine ask --jobs <jobs.json>\n ask different read-only questions in parallel\n atris engine validate <receipt-path|latest> [--engine <name>]\n check ask answers with a different read-only referee\n atris engine validate scoreboard\n show pass rates by worker engine\n atris engine dispatch <task-id> [<task-id> ...] --engine cursor|codex [--prompt-file <f>] [--yolo]\n one-command claim, worktree, build, verify, ship, ready\n atris engine login <provider> --yes\n upload a local provider CLI login to the backend vault\n atris engine login <provider> --computer [--seat <name>]\n atris engine login <provider> --business <id> [--seat <name>]\n sign in on an Atris computer by device flow\n atris engine login --list | --remove <provider>\n list or remove vaulted provider logins\n atris engine seats show which named accounts are ready to work\n atris engine seed <provider> --business <id>|--user\n push a vaulted login onto an Atris computer\n atris engine reset back to the house default\n --engine <name> one run on that engine (mission run / autopilot / run)\n');
1555
- return 0;
1556
- }
1557
-
1558
1570
  // atris engine <name> — flip the default. An unknown name is a plain user
1559
1571
  // typo, not a crash: report it cleanly (and as JSON on demand) instead of
1560
1572
  // letting setEngine throw a raw stack trace at a new person.
@@ -2,14 +2,18 @@
2
2
  * Errors command for Atris CLI — admin dashboard over atris_error_events.
3
3
  *
4
4
  * Usage:
5
- * atris errors List errors from last 24h, grouped by signature
5
+ * atris errors Local-only by default (no cloud dump)
6
+ * atris errors --account List cloud errors from last 24h
6
7
  * atris errors --hours 72 Widen the window (max 720h / 30d)
7
8
  * atris errors --limit 1000 Raise the raw-event cap for grouping
8
9
  * atris errors show <id> Full detail (stack trace, message) for one event
9
10
  *
10
- * Requires admin role on the user row.
11
+ * Requires admin role on the user row for cloud reads.
12
+ * Account gate: unbound folders need --account (see lib/account-bound.js).
11
13
  */
12
14
 
15
+ const fs = require('fs');
16
+ const path = require('path');
13
17
  const { loadCredentials } = require('../utils/auth');
14
18
  const { apiRequestJson } = require('../utils/api');
15
19
 
@@ -38,6 +42,44 @@ function extractFlag(args, ...names) {
38
42
  return [value, remaining];
39
43
  }
40
44
 
45
+ function wantsLocal(args) {
46
+ if (args.includes('--account') || args.includes('--cloud')) return false;
47
+ // --local is the default; keep the flag recognized for explicit callers.
48
+ return true;
49
+ }
50
+
51
+ function listLocalErrors(json) {
52
+ const localPath = path.join(process.cwd(), '.atris', 'state', 'errors.jsonl');
53
+ const rows = [];
54
+ if (fs.existsSync(localPath)) {
55
+ for (const line of fs.readFileSync(localPath, 'utf8').split('\n')) {
56
+ if (!line.trim()) continue;
57
+ try { rows.push(JSON.parse(line)); } catch { /* skip */ }
58
+ }
59
+ }
60
+
61
+ if (json) {
62
+ console.log(JSON.stringify({
63
+ ok: true,
64
+ scope: 'local',
65
+ total_events: rows.length,
66
+ groups: [],
67
+ note: 'local scope. pass --account for cloud error events.',
68
+ }, null, 2));
69
+ return;
70
+ }
71
+
72
+ if (rows.length === 0) {
73
+ console.log('No local errors. Pass --account for cloud error events.');
74
+ return;
75
+ }
76
+
77
+ console.log(`Local errors (${rows.length}). Pass --account for cloud error events.\n`);
78
+ for (const row of rows.slice(0, 20)) {
79
+ console.log(` ${row.message || row.error || JSON.stringify(row)}`);
80
+ }
81
+ }
82
+
41
83
  async function listErrors(args) {
42
84
  const [hoursArg, r1] = extractFlag(args, '--hours', '-H');
43
85
  const [limitArg, r2] = extractFlag(r1, '--limit', '-L');
@@ -45,6 +87,11 @@ async function listErrors(args) {
45
87
  const hours = hoursArg ? parseInt(hoursArg, 10) : 24;
46
88
  const limit = limitArg ? parseInt(limitArg, 10) : 500;
47
89
 
90
+ if (wantsLocal(r2)) {
91
+ listLocalErrors(json);
92
+ return;
93
+ }
94
+
48
95
  const token = getToken();
49
96
  const result = await apiRequestJson(`/errors?hours=${hours}&limit=${limit}`, {
50
97
  method: 'GET',
@@ -64,7 +111,7 @@ async function listErrors(args) {
64
111
  const groups = data.groups || [];
65
112
 
66
113
  if (json) {
67
- console.log(JSON.stringify({ ok: true, ...data }, null, 2));
114
+ console.log(JSON.stringify({ ok: true, scope: 'account', ...data }, null, 2));
68
115
  return;
69
116
  }
70
117
 
@@ -136,12 +183,13 @@ async function showError(errorId) {
136
183
  function printHelp() {
137
184
  console.log('');
138
185
  console.log('Usage:');
139
- console.log(' atris errors List errors from last 24h, grouped');
140
- console.log(' atris errors --hours 72 Widen the window (max 720h / 30d)');
141
- console.log(' atris errors --limit 1000 Raise the raw-event cap');
142
- console.log(' atris errors show <full-uuid> Full detail for one event');
186
+ console.log(' atris errors Local errors (default; no cloud dump)');
187
+ console.log(' atris errors --account List cloud errors from last 24h');
188
+ console.log(' atris errors --account --hours 72 Widen the cloud window (max 720h / 30d)');
189
+ console.log(' atris errors --account --limit 1000 Raise the raw-event cap');
190
+ console.log(' atris errors show <full-uuid> Full detail for one cloud event');
143
191
  console.log('');
144
- console.log('Admin role required.');
192
+ console.log('Admin role required for cloud reads. Unbound folders need --account.');
145
193
  console.log('');
146
194
  }
147
195
 
@@ -155,6 +203,8 @@ async function errorsCommand() {
155
203
  }
156
204
 
157
205
  if (sub === 'show') {
206
+ // show is account-scoped detail; require explicit --account in the remaining args
207
+ // or that the caller already passed the account gate from a bound workspace.
158
208
  await showError(args[1]);
159
209
  return;
160
210
  }
@@ -407,6 +407,18 @@ function buildBenchmarkArtifact(name, packDir, options) {
407
407
  return { artifact, score };
408
408
  }
409
409
 
410
+ function recordExperimentRun(experimentId) {
411
+ const root = process.cwd();
412
+ const state = daily.readDailyState(root);
413
+ const today = new Date().toISOString().slice(0, 10);
414
+ state.last_run_date = today;
415
+ if (!Array.isArray(state.history)) state.history = [];
416
+ if (!state.history.includes(experimentId)) {
417
+ state.history.push(experimentId);
418
+ }
419
+ daily.writeDailyState(root, state);
420
+ }
421
+
410
422
  function experimentsRun(name, ...args) {
411
423
  const { experimentsDir } = ensureExperimentsFramework();
412
424
 
@@ -434,6 +446,7 @@ function experimentsRun(name, ...args) {
434
446
  return;
435
447
  }
436
448
  runPython(loopPath, [], packDir);
449
+ recordExperimentRun(name);
437
450
  return;
438
451
  }
439
452
 
@@ -447,6 +460,7 @@ function experimentsRun(name, ...args) {
447
460
  const { artifact, score } = buildBenchmarkArtifact(name, packDir, options);
448
461
  const artifactPath = writeArtifact(packDir, artifact);
449
462
  appendResultsRow(path.join(packDir, 'results.tsv'), artifactPath, artifact, score);
463
+ recordExperimentRun(name);
450
464
 
451
465
  console.log(`✓ Endstate ${artifact.track} run recorded`);
452
466
  console.log(` artifact: ${path.relative(process.cwd(), artifactPath)}`);