atris 3.58.6 → 3.58.8

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 (90) hide show
  1. package/GETTING_STARTED.md +2 -2
  2. package/PERSONA.md +11 -11
  3. package/README.md +9 -1
  4. package/atris/GEMINI.md +7 -6
  5. package/atris/PERSONA.md +4 -4
  6. package/atris/features/README.md +77 -6
  7. package/atris/policies/engineering-principles.md +129 -0
  8. package/atris/policies/genesis.md +112 -0
  9. package/atris/policies/product-design-principles.md +100 -0
  10. package/atris/skills/README.md +44 -15
  11. package/atris/skills/aeo/SKILL.md +10 -10
  12. package/atris/skills/atris/SKILL.md +1 -1
  13. package/atris/skills/design/SKILL.md +3 -1
  14. package/atris/skills/engines/SKILL.md +42 -50
  15. package/atris/skills/meta/SKILL.md +4 -4
  16. package/atris/skills/youtube/SKILL.md +11 -11
  17. package/bin/atris.js +60 -8
  18. package/commands/aeo.js +9 -12
  19. package/commands/analytics.js +8 -11
  20. package/commands/auth.js +8 -2
  21. package/commands/autopilot.js +4 -3
  22. package/commands/avail.js +1 -1
  23. package/commands/brain.js +4 -1
  24. package/commands/business.js +13 -8
  25. package/commands/close.js +2 -1
  26. package/commands/computer.js +15 -10
  27. package/commands/context-sync.js +3 -3
  28. package/commands/design.js +362 -0
  29. package/commands/doc-health.js +377 -0
  30. package/commands/drive.js +32 -0
  31. package/commands/experiments.js +13 -4
  32. package/commands/improve.js +68 -2
  33. package/commands/integrations.js +2 -5
  34. package/commands/land.js +144 -4
  35. package/commands/learn.js +7 -2
  36. package/commands/member.js +239 -58
  37. package/commands/mission.js +94 -18
  38. package/commands/pack.js +2 -2
  39. package/commands/probe.js +11 -5
  40. package/commands/pull.js +1 -1
  41. package/commands/pulse.js +38 -0
  42. package/commands/push.js +1 -1
  43. package/commands/rainmaker.js +3 -16
  44. package/commands/rsi.js +156 -0
  45. package/commands/spaceship.js +1 -1
  46. package/commands/stream.js +27 -2
  47. package/commands/study.js +15 -9
  48. package/commands/sync.js +1 -1
  49. package/commands/task.js +54 -3
  50. package/commands/workflow.js +17 -7
  51. package/commands/youtube.js +76 -8
  52. package/lib/apply-gate.js +22 -4
  53. package/lib/codex-flight.js +28 -5
  54. package/lib/daily-log.js +88 -0
  55. package/lib/design-api.js +130 -0
  56. package/lib/engine-ask.js +1 -1
  57. package/lib/first-minute.js +1 -6
  58. package/lib/fleet.js +4 -1
  59. package/lib/improve-vitals-html.js +3 -2
  60. package/lib/known-commands.js +3 -3
  61. package/lib/lesson-contradiction.js +2 -2
  62. package/lib/map-refs.js +183 -0
  63. package/lib/member-context.js +42 -0
  64. package/lib/mission-room.js +4 -4
  65. package/lib/noninteractive.js +15 -4
  66. package/lib/policy-lessons.js +1 -1
  67. package/lib/receipt-block.js +137 -28
  68. package/lib/rsi-record.js +335 -0
  69. package/lib/runner-command.js +1 -1
  70. package/lib/scorecard.js +4 -4
  71. package/lib/sleep-sync.js +11 -0
  72. package/lib/state-detection.js +36 -17
  73. package/lib/task-db.js +74 -53
  74. package/lib/task-list-keeper.js +192 -0
  75. package/lib/todo-fallback.js +10 -4
  76. package/lib/todo.js +22 -10
  77. package/lib/wish-delegate.js +19 -5
  78. package/mcp/atris-mcp/index.mjs +227 -0
  79. package/package.json +4 -2
  80. package/scripts/det/det.js +2 -2
  81. package/scripts/det/pr-description.js +2 -2
  82. package/scripts/det/test.js +2 -2
  83. package/scripts/det/ytnotes +43 -8
  84. package/utils/api.js +1 -1
  85. package/utils/auth.js +79 -14
  86. package/utils/backend-root.js +31 -0
  87. package/utils/config.js +5 -4
  88. package/utils/owner-identity.js +61 -0
  89. package/atris/policies/LESSONS.md +0 -13
  90. package/atris/skills/autopilot/atris-autopilot.md +0 -71
package/commands/aeo.js CHANGED
@@ -15,32 +15,29 @@
15
15
  * atris aeo discover <source> [...] # discovery audit
16
16
  * atris aeo audit <source> [...] # agent-usability audit
17
17
  *
18
- * Local workspace resolution: $ATRIS_AEO_ROOT or legacy $ATRIS_BACKEND_ROOT.
18
+ * Local workspace resolution: $ATRIS_AEO_ROOT or the configured backend root.
19
19
  */
20
20
 
21
21
  const fs = require('fs');
22
22
  const os = require('os');
23
23
  const path = require('path');
24
24
  const { spawnSync } = require('child_process');
25
+ const { BACKEND_ROOT_HINT, resolveBackendRoot: resolveConfiguredBackendRoot } = require('../utils/backend-root');
25
26
  const { loadCredentials, abortOnAuthFailure } = require('../utils/auth');
26
27
  const { apiRequestJson } = require('../utils/api');
27
28
  const { loadBusinesses, saveBusinesses } = require('./business');
28
29
 
29
- function resolveBackendRoot() {
30
- const candidates = [
31
- process.env.ATRIS_AEO_ROOT,
32
- process.env.ATRIS_BACKEND_ROOT,
33
- ].filter(Boolean);
34
- for (const root of candidates) {
35
- if (fs.existsSync(path.join(root, 'atris', 'features', 'aeo'))) return root;
36
- }
37
- return null;
30
+ function resolveAeoRoot() {
31
+ const explicit = process.env.ATRIS_AEO_ROOT;
32
+ if (explicit && fs.existsSync(path.join(explicit, 'atris', 'features', 'aeo'))) return explicit;
33
+ const backend = resolveConfiguredBackendRoot();
34
+ return backend && fs.existsSync(path.join(backend, 'atris', 'features', 'aeo')) ? backend : null;
38
35
  }
39
36
 
40
37
  function requireBackendRoot() {
41
- const root = resolveBackendRoot();
38
+ const root = resolveAeoRoot();
42
39
  if (!root) {
43
- console.error('Cannot find local AEO workspace. Set ATRIS_AEO_ROOT to a workspace containing atris/features/aeo.');
40
+ console.error(`cannot find local aeo workspace; ${BACKEND_ROOT_HINT}`);
44
41
  process.exit(1);
45
42
  }
46
43
  return root;
@@ -68,17 +68,14 @@ function analyticsAtris() {
68
68
  }
69
69
 
70
70
  // Parse timestamps for productivity hours. Match the journal heading
71
- // format `### Title -- HH:MM` (the dashed form) and the legacy `**HH:MM:SS**` form.
72
- const timestampMatches = content.match(/(?:\u2014|--)\s*(\d{2}):\d{2}(?::\d{2})?\b|\*\*(\d{2}):\d{2}(?::\d{2})?\*\*/g);
73
- if (timestampMatches) {
74
- timestampMatches.forEach(ts => {
75
- const m = ts.match(/(\d{2}):/);
76
- if (!m) return;
77
- const hour = parseInt(m[1], 10);
78
- if (Number.isFinite(hour) && hour >= 0 && hour < 24) {
79
- hourCounts[hour] = (hourCounts[hour] || 0) + 1;
80
- }
81
- });
71
+ // formats `### Title -- HH:MM`, `### Title - HH:MM`, and legacy `**HH:MM:SS**`.
72
+ // Only heading lines count, so a prose range like "10:00 - 12:30" is not a timestamp.
73
+ const timestampPattern = /^#{2,4} .*(?:\u2014|--| - )\s*(\d{2}):\d{2}(?::\d{2})?\s*$|\*\*(\d{2}):\d{2}(?::\d{2})?\*\*/gm;
74
+ for (const match of content.matchAll(timestampPattern)) {
75
+ const hour = parseInt(match[1] || match[2], 10);
76
+ if (Number.isFinite(hour) && hour >= 0 && hour < 24) {
77
+ hourCounts[hour] = (hourCounts[hour] || 0) + 1;
78
+ }
82
79
  }
83
80
  });
84
81
 
package/commands/auth.js CHANGED
@@ -102,6 +102,10 @@ function scopedTokenCandidate(credentials = {}) {
102
102
  }
103
103
 
104
104
  function canMintFromLogin(credentials = {}) {
105
+ if (credentials.source === 'agent_token_file') return false;
106
+ if (credentials.source === 'env' && Array.isArray(credentials.scopes) && credentials.scopes.length) {
107
+ return false;
108
+ }
105
109
  const login = firstNonEmptyString(credentials.token);
106
110
  const refresh = firstNonEmptyString(credentials.refresh_token);
107
111
  if (isAgentAccessToken(login) && !refresh) return false;
@@ -227,8 +231,10 @@ async function ensureBilledCommandAuth(scope, deps = {}) {
227
231
  const credentials = ensured?.credentials || await load(api) || {};
228
232
  const candidate = scopedTokenCandidate(credentials);
229
233
  const claims = decodeJwtClaims(candidate);
230
- const scopes = claims?.scopes || credentials.agent_token_scopes || [];
231
- const expiry = claims?.exp ? claims.exp * 1000 : Date.parse(credentials.agent_token_expires_at);
234
+ const scopes = claims?.scopes || credentials.agent_token_scopes || credentials.scopes || [];
235
+ const expiry = claims?.exp
236
+ ? claims.exp * 1000
237
+ : Date.parse(credentials.agent_token_expires_at || credentials.expires_at);
232
238
  if (!deps.forceMint && candidate && Array.isArray(scopes) && scopes.includes(wanted) && Number.isFinite(expiry) && expiry > Date.now()) {
233
239
  return { ok: true, token: candidate, minted: false, credentials };
234
240
  }
@@ -25,14 +25,15 @@ const {
25
25
  detectEndgameCompletion
26
26
  } = require('../lib/scorecard');
27
27
  const { REWARD_CONFIG, REWARD_CHECKSUM } = require('../lib/reward-config');
28
+ const { ownerIdentity } = require('../utils/owner-identity');
28
29
 
29
30
  const pkg = require('../package.json');
30
31
 
31
32
  const PHASE_TIMEOUT = 600000; // 10 min per phase
32
33
 
33
34
  function looksOwnerClaimed(claimed) {
34
- const text = String(claimed || '').toLowerCase();
35
- return /\bkeshav(?:rao)?\b/.test(text) || /\b(owner|human|operator)\b/.test(text);
35
+ const text = String(claimed || '').trim().toLowerCase();
36
+ return /\b(owner|human|operator)\b/.test(text) || ownerIdentity().names.includes(text.split(/\s+/)[0]);
36
37
  }
37
38
 
38
39
  function looksOwnerGatedTitle(title) {
@@ -640,7 +641,7 @@ function getContextFiles(phase, options = {}) {
640
641
  // checkout is forbidden (COORDINATION.md Rule 4). Interpolated into the
641
642
  // default and self-heal do prompts, never the benchmark prompt (it never
642
643
  // commits).
643
- const SHARED_CHECKOUT_GIT_CONTRACT = `- Shared-checkout git safety (COORDINATION.md Rule 4): edits to any repo OTHER than this tick's cwd (../atrisos-backend-style sibling repos) go through a per-tick worktree, start with \`atris worktree start --member <member> --task "<task>"\`, land with \`atris worktree ship --message "<msg>" --verify "<cmd>"\`. Never edit a sibling repo's shared checkout directly.
644
+ const SHARED_CHECKOUT_GIT_CONTRACT = `- Shared-checkout git safety (COORDINATION.md Rule 4): edits to any repo OTHER than this tick's cwd (including sibling repos) go through a per-tick worktree, start with \`atris worktree start --member <member> --task "<task>"\`, land with \`atris worktree ship --message "<msg>" --verify "<cmd>"\`. Never edit a sibling repo's shared checkout directly.
644
645
  - On a shared checkout, \`git reset\`, \`git checkout --\`, \`git clean\`, and stashing other agents' work are FORBIDDEN, concurrent ticks' uncommitted work lives there.`;
645
646
 
646
647
  /**
package/commands/avail.js CHANGED
@@ -308,7 +308,7 @@ async function slotsAvail(argv) {
308
308
  if (!username) {
309
309
  const identity = await apiRequestJson('/identity/me', { method: 'GET', token });
310
310
  if (!identity.ok || !identity.data?.username) {
311
- console.error('Could not resolve username, pass --user keshav');
311
+ console.error('could not resolve username; pass --user <username>.');
312
312
  return 1;
313
313
  }
314
314
  username = identity.data.username;
package/commands/brain.js CHANGED
@@ -3,6 +3,7 @@ const path = require('path');
3
3
  const crypto = require('crypto');
4
4
  const { spawnSync } = require('child_process');
5
5
  const { refreshNowFile } = require('./now');
6
+ const { ownerIdentity } = require('../utils/owner-identity');
6
7
  const escapeRegExp = require('../lib/escape-regexp');
7
8
  const { hasRenderedSections, isOpenSection, isDoneSection } = require('../lib/todo-sections');
8
9
  const { isNonInteractive } = require('../lib/noninteractive');
@@ -794,7 +795,8 @@ function memberNextMove(member, state = null) {
794
795
  if (member.slug === 'justin' || /justin/i.test(member.name || '')) {
795
796
  return `${name}: run one customer-moving GTM rep, update the relevant workspace state within 10 minutes, and leave a scorecard.`;
796
797
  }
797
- if (member.slug === 'keshav' || /keshav/i.test(member.name || '')) {
798
+ const ownerNames = ownerIdentity().names;
799
+ if (ownerNames.includes(String(member.slug || '').toLowerCase()) || ownerNames.includes(String(member.name || '').toLowerCase())) {
798
800
  return `${name}: act as Customer 0: choose one allocation, narrative, system, or talent leverage move; leave proof and route the next owner.`;
799
801
  }
800
802
  if (/opus|overnight/i.test(identity)) {
@@ -1774,6 +1776,7 @@ function brainCommand(args = process.argv.slice(3)) {
1774
1776
  }
1775
1777
 
1776
1778
  module.exports = {
1779
+ readText,
1777
1780
  brainCommand,
1778
1781
  collectState,
1779
1782
  renderStatus,
@@ -13,6 +13,10 @@ const {
13
13
  renderDryRun,
14
14
  } = require('../lib/business-simulate');
15
15
 
16
+ function businessWorkspaceBase() {
17
+ return process.env.ATRIS_BUSINESS_ROOT || path.join(os.homedir(), 'arena', 'atris-business');
18
+ }
19
+
16
20
  function getBusinessConfigPath() {
17
21
  const home = require('os').homedir();
18
22
  const dir = path.join(home, '.atris');
@@ -179,7 +183,7 @@ function buildBusinessCacheEntry(business, localSlug, existing = {}) {
179
183
  return entry;
180
184
  }
181
185
 
182
- function readBusinessFolderBindings(rootDir = path.join(os.homedir(), 'arena', 'atris-business')) {
186
+ function readBusinessFolderBindings(rootDir = businessWorkspaceBase()) {
183
187
  const skip = new Set([
184
188
  '.git', '.DS_Store', 'archive', 'archives', '_archive', 'bench', 'deals',
185
189
  'node_modules', 'shelf', '_shelf', 'templates',
@@ -510,7 +514,7 @@ function resolveWorkspaceRoot(slug, options = {}) {
510
514
  if (options.noLocal) return null;
511
515
  if (options.here) return options.cwd || process.cwd();
512
516
  if (options.root) return path.join(options.root, slug);
513
- return path.join(os.homedir(), 'arena', 'atris-business', slug);
517
+ return path.join(businessWorkspaceBase(), slug);
514
518
  }
515
519
 
516
520
  function createCanonicalBusinessWorkspace(targetRoot, bizMeta, options = {}) {
@@ -1851,7 +1855,7 @@ async function addBusiness(slug) {
1851
1855
  }
1852
1856
 
1853
1857
  async function listBusinesses(opts = {}) {
1854
- // --local mode: walk ~/arena/atris-business/ and show fleet status table
1858
+ // --local mode: walk the configured business workspace folder
1855
1859
  // (no API calls, rate-limit safe). Different from API-mode below which lists
1856
1860
  // businesses cached from the API.
1857
1861
  if (opts.local) {
@@ -1877,7 +1881,7 @@ async function listBusinesses(opts = {}) {
1877
1881
  }
1878
1882
 
1879
1883
  /**
1880
- * Walk ~/arena/atris-business/ and print a fleet status table for every
1884
+ * Walk the configured business folder and print a fleet status table for every
1881
1885
  * customer workspace. Pure local, no API calls, no rate-limit risk.
1882
1886
  *
1883
1887
  * Classifies each dir as: ready, flat, unbound, nested, bare, or superseded.
@@ -1891,7 +1895,7 @@ function listBusinessesLocal(opts = {}) {
1891
1895
  const SKIP_DIRS = new Set(['deals', 'archive', 'archives', '_archive', 'templates', 'node_modules', '.git']);
1892
1896
  const SKIP_FILES = new Set(['.DS_Store', 'Thumbs.db']);
1893
1897
 
1894
- const rootDir = opts.root || path.join(os.homedir(), 'arena', 'atris-business');
1898
+ const rootDir = opts.root || businessWorkspaceBase();
1895
1899
  const jsonMode = opts.json === true;
1896
1900
 
1897
1901
  if (!fs.existsSync(rootDir)) {
@@ -2609,7 +2613,7 @@ function parseBusinessDoctorOptions(args = []) {
2609
2613
  const options = {
2610
2614
  fix: args.includes('--fix'),
2611
2615
  json: args.includes('--json'),
2612
- root: path.join(os.homedir(), 'arena', 'atris-business'),
2616
+ root: businessWorkspaceBase(),
2613
2617
  };
2614
2618
  const rootIdx = args.indexOf('--root');
2615
2619
  if (rootIdx !== -1 && args[rootIdx + 1]) {
@@ -2623,7 +2627,7 @@ function printBusinessDoctorHelp() {
2623
2627
  console.log('');
2624
2628
  console.log('Checks cloud-active businesses against:');
2625
2629
  console.log(' - ~/.atris/businesses.json');
2626
- console.log(' - ~/arena/atris-business/*/.atris/business.json');
2630
+ console.log(` - ${businessWorkspaceBase()}/*/.atris/business.json`);
2627
2631
  console.log(' - canonical slug + alias bindings');
2628
2632
  console.log('');
2629
2633
  console.log('--fix rewrites only safe local cache entries. It does not rename folders or touch cloud data.');
@@ -3454,7 +3458,7 @@ async function quickstart() {
3454
3458
  atris business init "My Company" --template saas
3455
3459
 
3456
3460
  2. Open the local workspace:
3457
- cd ~/arena/atris-business/my-company
3461
+ cd ${businessWorkspaceBase()}/my-company
3458
3462
 
3459
3463
  3. See what Atris knows:
3460
3464
  atris
@@ -3675,6 +3679,7 @@ async function businessCommand(subcommand, ...args) {
3675
3679
 
3676
3680
  module.exports = {
3677
3681
  businessCommand,
3682
+ businessWorkspaceBase,
3678
3683
  loadBusinesses,
3679
3684
  saveBusinesses,
3680
3685
  businessMatchesSlug,
package/commands/close.js CHANGED
@@ -5,6 +5,7 @@ const { spawnSync } = require('child_process');
5
5
  const { knownCommands } = require('../lib/known-commands');
6
6
  const { readUsage, usagePath } = require('../lib/usage');
7
7
  const pulse = require('../lib/pulse');
8
+ const { isOwner } = require('../utils/owner-identity');
8
9
  const { parseLessons } = require('./autopilot');
9
10
 
10
11
  const DAY_MS = 24 * 60 * 60 * 1000;
@@ -240,7 +241,7 @@ function parkedFlags(cwd = process.cwd(), options = {}) {
240
241
  }
241
242
 
242
243
  function ownerWaitRank(flag) {
243
- return /^(you|me|operator|human|keshav)$/.test(String(flag.owner || '').toLowerCase()) ? 0 : 1;
244
+ return isOwner(flag.owner) ? 0 : 1;
244
245
  }
245
246
 
246
247
  function compareOpenFlags(a, b) {
@@ -23,8 +23,9 @@ const path = require('path');
23
23
  const readline = require('readline');
24
24
  const { spawnSync } = require('child_process');
25
25
  const { loadCredentials, decodeJwtClaims, promptUser, abortOnAuthFailure } = require('../utils/auth');
26
+ const { resolveBackendRoot } = require('../utils/backend-root');
26
27
  const { apiRequestJson, getApiBaseUrl, getAppBaseUrl } = require('../utils/api');
27
- const { loadBusinesses, saveBusinesses } = require('./business');
28
+ const { businessWorkspaceBase, loadBusinesses, saveBusinesses } = require('./business');
28
29
  const {
29
30
  engineLoginSeatError,
30
31
  formatEngineSeats,
@@ -37,6 +38,7 @@ const {
37
38
  const { consoleCommand, gatherAtrisContext, buildSystemPrompt } = require('./console');
38
39
  const { streamSession } = require('./serve');
39
40
  const { buildRemoteAtrisBootstrapCommand } = require('../lib/runtime-bootstrap');
41
+ const { envForcesNonInteractive } = require('../lib/noninteractive');
40
42
 
41
43
  function getToken() {
42
44
  const creds = loadCredentials();
@@ -270,11 +272,11 @@ const ui = {
270
272
  };
271
273
 
272
274
  function useInteractiveCloudUi() {
273
- return Boolean(process.stdin.isTTY && process.stdout.isTTY && !process.env.ATRIS_NO_INTERACTIVE);
275
+ return Boolean(process.stdin.isTTY && process.stdout.isTTY && !envForcesNonInteractive());
274
276
  }
275
277
 
276
278
  function useInteractiveTerminalUi() {
277
- return Boolean(process.stdin.isTTY && process.stdout.isTTY && !process.env.ATRIS_NO_INTERACTIVE);
279
+ return Boolean(process.stdin.isTTY && process.stdout.isTTY && !envForcesNonInteractive());
278
280
  }
279
281
 
280
282
  async function readPipedStdin() {
@@ -517,7 +519,7 @@ function displayHomeRelativePath(targetPath) {
517
519
  }
518
520
 
519
521
  function recruitingBusinessWorkspacePath(slug = RECRUITING_BUSINESS_SLUG) {
520
- const root = process.env.ATRIS_BUSINESS_ROOT || path.join(os.homedir(), 'arena', 'atris-business');
522
+ const root = businessWorkspaceBase();
521
523
  return path.join(root, slug);
522
524
  }
523
525
 
@@ -1371,11 +1373,12 @@ function printModeBanner(mode, root, lines = []) {
1371
1373
 
1372
1374
  function findAtrisCodeTerminal() {
1373
1375
  const envPath = process.env.ATRIS_CODE_PY;
1376
+ const backend = resolveBackendRoot();
1374
1377
  const candidates = [
1375
1378
  envPath,
1376
1379
  path.join(__dirname, '..', 'cli', 'atris_code.py'),
1377
1380
  path.join(process.cwd(), 'cli', 'atris_code.py'),
1378
- path.join(os.homedir(), 'arena', 'atrisos-backend', 'cli', 'atris_code.py'),
1381
+ ...(backend ? [path.join(backend, 'cli', 'atris_code.py')] : []),
1379
1382
  ].filter(Boolean);
1380
1383
 
1381
1384
  let dir = process.cwd();
@@ -2534,7 +2537,7 @@ async function computerCreate(token, args = [], defaults = {}) {
2534
2537
  console.log(` atris computer --business ${owner} --workspace ${workspaceId}`);
2535
2538
  console.log('');
2536
2539
  console.log('Org workspace:');
2537
- console.log(` cd ~/arena/atris-business/${owner}`);
2540
+ console.log(` cd ${businessWorkspaceBase()}/${owner}`);
2538
2541
  console.log(' atris member activate operator');
2539
2542
  console.log(' atris member activate validator');
2540
2543
  console.log('');
@@ -3500,7 +3503,7 @@ async function sendBusinessChat(token, ctx, message, sessionId, resetContext = f
3500
3503
  async function computerChat(token, ctx, initialOptions = {}) {
3501
3504
  if (!ctx) {
3502
3505
  console.error('Cloud computer mode requires a bound business workspace.');
3503
- console.error('Run this inside ~/arena/atris-business/<slug>/, or use `atris computer --local` for local mode.');
3506
+ console.error('run this inside a bound business workspace, or use `atris computer --local` for local mode.');
3504
3507
  return;
3505
3508
  }
3506
3509
 
@@ -3741,7 +3744,7 @@ async function computerChat(token, ctx, initialOptions = {}) {
3741
3744
  async function computerLocalAtris(token, ctx, initialOptions = {}) {
3742
3745
  if (!ctx) {
3743
3746
  console.error('Atris local mode needs a bound business workspace for the cloud brain.');
3744
- console.error('Run inside ~/arena/atris-business/<slug>/, or use `atris computer local-byo`.');
3747
+ console.error('run inside a bound business workspace, or use `atris computer local-byo`.');
3745
3748
  return;
3746
3749
  }
3747
3750
 
@@ -3976,7 +3979,7 @@ async function computerLocalAtris(token, ctx, initialOptions = {}) {
3976
3979
  async function computerProof(token, ctx, initialOptions = {}) {
3977
3980
  if (!ctx) {
3978
3981
  console.error('Atris computer proof needs a bound business workspace.');
3979
- console.error('Run inside ~/arena/atris-business/<slug>/ first.');
3982
+ console.error('run inside a bound business workspace first.');
3980
3983
  process.exitCode = 1;
3981
3984
  return;
3982
3985
  }
@@ -4290,7 +4293,7 @@ async function runComputer(argv = process.argv.slice(3), deps = {}) {
4290
4293
  console.log('A business can be a company, lab, collective, community, artist, team, or project.');
4291
4294
  console.log('');
4292
4295
  console.log('First use:');
4293
- console.log(' cd ~/arena/atris-business/<business>');
4296
+ console.log(` cd ${businessWorkspaceBase()}/<business>`);
4294
4297
  console.log(' atris computer');
4295
4298
  console.log(' Choose Cloud workspace or Local folder, then type the outcome in plain English.');
4296
4299
  console.log('');
@@ -4470,6 +4473,8 @@ module.exports = {
4470
4473
  extractAttachedWorkspaceMismatch,
4471
4474
  contextForAttachedWorkspaceMismatch,
4472
4475
  printRecruitingLocalSyncOutcome,
4476
+ useInteractiveCloudUi,
4477
+ useInteractiveTerminalUi,
4473
4478
  // Hermetic parsing/formatting layer, exported for test/computer.test.js.
4474
4479
  parseComputerOptions,
4475
4480
  parseComputerCreateArgs,
@@ -64,7 +64,7 @@ async function businessStatus(slug) {
64
64
  const timeSince = manifest ? _timeSince(manifest.last_sync) : null;
65
65
 
66
66
  console.log('');
67
- console.log(`${biz.name}` + (timeSince ? ` \u2014 last synced ${timeSince}` : ' \u2014 never synced'));
67
+ console.log(`${biz.name}` + (timeSince ? `: last synced ${timeSince}` : ': never synced'));
68
68
 
69
69
  // Determine local directory
70
70
  let localDir = null;
@@ -254,11 +254,11 @@ async function businessLog(slug) {
254
254
 
255
255
  const commits = result.data.commits || [];
256
256
  if (commits.length === 0) {
257
- console.log(`\n ${biz.name} \u2014 no history yet.\n`);
257
+ console.log(`\n ${biz.name}: no history yet.\n`);
258
258
  return;
259
259
  }
260
260
 
261
- console.log(`\n ${biz.name} \u2014 history\n`);
261
+ console.log(`\n ${biz.name}: history\n`);
262
262
 
263
263
  for (const commit of commits) {
264
264
  const date = _timeSince(commit.date) || commit.date;