atris 3.15.31 → 3.15.36

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.
@@ -6,6 +6,7 @@
6
6
  * atris computer create <name> — Create and wake a business computer
7
7
  * atris computer wake — Start the computer
8
8
  * atris computer sleep — Stop (files persist)
9
+ * atris computer delete — Sleep, confirm, and delete a business computer
9
10
  * atris computer card — Show the local computer card
10
11
  * atris computer run <command> — Run bash on EC2 (no LLM)
11
12
  * atris computer grep <pattern> — Search files on EC2
@@ -24,6 +25,7 @@ const { apiRequestJson, getApiBaseUrl, getAppBaseUrl } = require('../utils/api')
24
25
  const { loadBusinesses, saveBusinesses } = require('./business');
25
26
  const { consoleCommand, gatherAtrisContext, buildSystemPrompt } = require('./console');
26
27
  const { streamSession } = require('./serve');
28
+ const { buildRemoteAtrisBootstrapCommand } = require('../lib/runtime-bootstrap');
27
29
 
28
30
  function getToken() {
29
31
  const creds = loadCredentials();
@@ -150,6 +152,38 @@ function formatBillingMode(worker) {
150
152
  : 'Claude subscription lane';
151
153
  }
152
154
 
155
+ function extractAttachedWorkspaceMismatch(...values) {
156
+ const text = values
157
+ .filter((value) => value !== null && value !== undefined)
158
+ .map((value) => {
159
+ if (typeof value === 'string') return value;
160
+ try {
161
+ return JSON.stringify(value);
162
+ } catch {
163
+ return String(value);
164
+ }
165
+ })
166
+ .join('\n');
167
+ const match = text.match(/attached to workspace\s+([a-z0-9-]+)\.\s*Activate workspace\s+([a-z0-9-]+)\s+to switch/i);
168
+ if (!match) return null;
169
+ return {
170
+ attachedWorkspaceId: match[1],
171
+ requestedWorkspaceId: match[2],
172
+ };
173
+ }
174
+
175
+ function contextForAttachedWorkspaceMismatch(ctx, failure) {
176
+ const mismatch = extractAttachedWorkspaceMismatch(
177
+ failure?.result?.error,
178
+ failure?.result?.errorMessage,
179
+ failure?.result?.data,
180
+ failure?.fallback?.error,
181
+ failure?.fallback?.payload
182
+ );
183
+ if (!mismatch?.attachedWorkspaceId || mismatch.attachedWorkspaceId === ctx?.workspaceId) return null;
184
+ return { ...ctx, workspaceId: mismatch.attachedWorkspaceId };
185
+ }
186
+
153
187
  async function describeClaudeAuth(token, ctx) {
154
188
  try {
155
189
  const status = await fetchBusinessClaudeLoginStatus(token, ctx);
@@ -506,9 +540,33 @@ function parseComputerOptions(argv) {
506
540
  const positional = [];
507
541
  let worker = process.env.ATRIS_CLOUD_WORKER || null;
508
542
  let model = process.env.ATRIS_CLOUD_MODEL || null;
543
+ let businessSlug = null;
544
+ let workspaceId = null;
509
545
 
510
546
  for (let i = 0; i < argv.length; i++) {
511
547
  const arg = argv[i];
548
+ if ((arg === '--business' || arg === '-b') && argv[i + 1]) {
549
+ businessSlug = argv[i + 1];
550
+ i++;
551
+ continue;
552
+ }
553
+ if (arg.startsWith('--business=')) {
554
+ businessSlug = arg.split('=', 2)[1] || null;
555
+ continue;
556
+ }
557
+ if ((arg === '--workspace' || arg === '--workspace-id') && argv[i + 1]) {
558
+ workspaceId = argv[i + 1];
559
+ i++;
560
+ continue;
561
+ }
562
+ if (arg.startsWith('--workspace=')) {
563
+ workspaceId = arg.split('=', 2)[1] || null;
564
+ continue;
565
+ }
566
+ if (arg.startsWith('--workspace-id=')) {
567
+ workspaceId = arg.split('=', 2)[1] || null;
568
+ continue;
569
+ }
512
570
  if (arg === '--worker' && argv[i + 1]) {
513
571
  worker = argv[i + 1];
514
572
  i++;
@@ -541,6 +599,8 @@ function parseComputerOptions(argv) {
541
599
  options: {
542
600
  worker: worker || null,
543
601
  model: model || null,
602
+ businessSlug: businessSlug ? String(businessSlug).trim() : null,
603
+ workspaceId: workspaceId ? String(workspaceId).trim() : null,
544
604
  },
545
605
  };
546
606
  }
@@ -575,6 +635,29 @@ function parseComputerCreateArgs(argv = []) {
575
635
  };
576
636
  }
577
637
 
638
+ function parseComputerDeleteArgs(argv = []) {
639
+ const options = { help: false, confirm: null };
640
+
641
+ for (let i = 0; i < argv.length; i++) {
642
+ const arg = argv[i];
643
+ if (arg === '--help' || arg === '-h' || arg === 'help') {
644
+ options.help = true;
645
+ continue;
646
+ }
647
+ if (arg === '--confirm' && argv[i + 1]) {
648
+ options.confirm = argv[i + 1];
649
+ i++;
650
+ continue;
651
+ }
652
+ if (arg.startsWith('--confirm=')) {
653
+ options.confirm = arg.slice('--confirm='.length);
654
+ continue;
655
+ }
656
+ }
657
+
658
+ return options;
659
+ }
660
+
578
661
  function formatCloudSelection(options = {}) {
579
662
  const worker = activeWorker(options.worker);
580
663
  const parts = [`worker=${worker}`];
@@ -954,9 +1037,33 @@ async function resolveBusinessContext(token) {
954
1037
  return null;
955
1038
  }
956
1039
 
957
- async function resolveBusinessContextBySlug(token, slug) {
1040
+ function cachedBusinessContext(slug) {
1041
+ if (!slug) return null;
1042
+ const wanted = String(slug).toLowerCase();
1043
+ const businesses = loadBusinesses();
1044
+ const cached = businesses[slug] || Object.values(businesses).find((entry) => {
1045
+ if (!entry) return false;
1046
+ return String(entry.slug || '').toLowerCase() === wanted
1047
+ || String(entry.canonical_slug || '').toLowerCase() === wanted
1048
+ || String(entry.name || '').toLowerCase() === wanted;
1049
+ });
1050
+ if (!cached?.business_id) return null;
1051
+ return {
1052
+ slug: cached.slug || slug,
1053
+ businessId: cached.business_id,
1054
+ workspaceId: cached.workspace_id || null,
1055
+ businessName: cached.name || cached.slug || slug,
1056
+ };
1057
+ }
1058
+
1059
+ async function resolveBusinessContextBySlug(token, slug, options = {}) {
958
1060
  if (!slug) return null;
959
1061
 
1062
+ if (options.preferCache) {
1063
+ const cached = cachedBusinessContext(slug);
1064
+ if (cached?.workspaceId) return cached;
1065
+ }
1066
+
960
1067
  const businesses = loadBusinesses();
961
1068
  const list = await apiRequestJson('/business/', { method: 'GET', token });
962
1069
  if (list.ok) {
@@ -984,6 +1091,21 @@ async function resolveBusinessContextBySlug(token, slug) {
984
1091
  return null;
985
1092
  }
986
1093
 
1094
+ async function resolveComputerCommandContext(token, options = {}) {
1095
+ if (options.businessSlug || options.workspaceId) {
1096
+ const ctx = options.businessSlug
1097
+ ? await resolveBusinessContextBySlug(token, options.businessSlug, { preferCache: true })
1098
+ : await resolveBusinessContext(token);
1099
+ if (!ctx?.businessId) return null;
1100
+ return {
1101
+ ...ctx,
1102
+ workspaceId: options.workspaceId || ctx.workspaceId,
1103
+ };
1104
+ }
1105
+
1106
+ return resolveBusinessContext(token);
1107
+ }
1108
+
987
1109
  async function resolveBusinessOwnerForCreate(token, businessSlug = null) {
988
1110
  const wantedSlug = businessSlug ? String(businessSlug).trim() : null;
989
1111
  if (wantedSlug) {
@@ -1054,6 +1176,42 @@ async function runBusinessTerminalCommand(token, ctx, command, timeout = 30) {
1054
1176
  );
1055
1177
  }
1056
1178
 
1179
+ async function bootstrapBusinessComputerRuntime(token, ctx, boundary = 'computer-wake') {
1180
+ if (!ctx?.businessId || !ctx?.workspaceId) {
1181
+ return { ok: false, skipped: true, reason: 'missing_workspace' };
1182
+ }
1183
+ if (process.env.ATRIS_SKIP_RUNTIME_BOOTSTRAP === '1') {
1184
+ return { ok: true, skipped: true, reason: 'env' };
1185
+ }
1186
+
1187
+ const command = buildRemoteAtrisBootstrapCommand({
1188
+ boundary,
1189
+ businessSlug: ctx.slug || '',
1190
+ businessId: ctx.businessId,
1191
+ workspaceId: ctx.workspaceId,
1192
+ });
1193
+ const result = await runBusinessTerminalCommand(token, ctx, command, 120);
1194
+ if (!result.ok) {
1195
+ console.log(' Runtime: bootstrap could not run.');
1196
+ console.log(` Recovery: atris computer run "npm install -g atris@latest" --business ${ctx.slug || ctx.businessId} --workspace ${ctx.workspaceId}`);
1197
+ return { ok: false, result };
1198
+ }
1199
+
1200
+ const data = result.data || {};
1201
+ const output = String(data.stdout || data.output || data.result || '').trim();
1202
+ const line = output.split('\n').find((entry) => entry.includes('atris_runtime_bootstrap'));
1203
+ const recovery = output.split('\n').find((entry) => entry.startsWith('recovery='));
1204
+ if (line) {
1205
+ console.log(` Runtime: ${line.replace(/^atris_runtime_bootstrap\s*/, '')}`);
1206
+ } else {
1207
+ console.log(' Runtime: Atris bootstrap receipt written.');
1208
+ }
1209
+ if (recovery) {
1210
+ console.log(` Recovery: atris computer run "${recovery.slice('recovery='.length)}" --business ${ctx.slug || ctx.businessId} --workspace ${ctx.workspaceId}`);
1211
+ }
1212
+ return { ok: true, output };
1213
+ }
1214
+
1057
1215
  async function readBusinessWorkspaceFile(token, ctx, remotePath, timeoutMs = 15000) {
1058
1216
  return apiRequestJson(
1059
1217
  `/business/${ctx.businessId}/workspaces/${ctx.workspaceId}/file?path=${encodeURIComponent(remotePath)}`,
@@ -1217,6 +1375,7 @@ async function ensureBusinessAwake(token, ctx, maxWaitSec = 90) {
1217
1375
  if (next.ok && next.data && next.data.status === 'running' && next.data.endpoint) {
1218
1376
  const elapsed = Math.floor((Date.now() - start) / 1000);
1219
1377
  console.log(`awake (${elapsed}s)`);
1378
+ await bootstrapBusinessComputerRuntime(token, ctx, 'computer-auto-wake');
1220
1379
  return true;
1221
1380
  }
1222
1381
  }
@@ -1287,6 +1446,8 @@ async function computerWake(token, ctx = null) {
1287
1446
  }
1288
1447
  console.log(` Status: ${result.data.status}`);
1289
1448
  if (result.data.endpoint) console.log(` Endpoint: ${result.data.endpoint}`);
1449
+ await bootstrapBusinessComputerRuntime(token, ctx, 'computer-wake');
1450
+ console.log(' Computer is awake.');
1290
1451
  return;
1291
1452
  }
1292
1453
 
@@ -1302,10 +1463,14 @@ async function computerWake(token, ctx = null) {
1302
1463
  }
1303
1464
  console.log(` Status: ${result.data.status}`);
1304
1465
  console.log(` Endpoint: ${result.data.endpoint}`);
1466
+ console.log(' Computer is awake.');
1305
1467
  }
1306
1468
 
1307
- async function computerCreate(token, args = []) {
1469
+ async function computerCreate(token, args = [], defaults = {}) {
1308
1470
  const options = parseComputerCreateArgs(args);
1471
+ if (!options.businessSlug && defaults.businessSlug) {
1472
+ options.businessSlug = defaults.businessSlug;
1473
+ }
1309
1474
  if (options.help || !options.name) {
1310
1475
  console.log('Usage: atris computer create <name> --business <slug>');
1311
1476
  console.log('');
@@ -1373,6 +1538,7 @@ async function computerCreate(token, args = []) {
1373
1538
  ? 'running'
1374
1539
  : (wake.data?.status || (activate.ok ? 'activated' : 'warming_up'));
1375
1540
  rememberCreatedComputer(ctx, { ...workspace, id: workspaceId, name: workspace.name || options.name }, endpoint);
1541
+ await bootstrapBusinessComputerRuntime(token, { ...ctx, workspaceId }, 'computer-create');
1376
1542
 
1377
1543
  const appBase = getAppBaseUrl();
1378
1544
  console.log('');
@@ -1383,9 +1549,20 @@ async function computerCreate(token, args = []) {
1383
1549
  if (endpoint) console.log(` Endpoint: ${endpoint}`);
1384
1550
  console.log(` Dashboard: ${appBase}/dashboard/gm/${ctx.businessId}`);
1385
1551
  console.log('');
1386
- console.log('Next:');
1387
- console.log(` atris pull ${ctx.slug || ctx.businessId}`);
1388
- console.log(' atris computer');
1552
+ const owner = ctx.slug || ctx.businessId;
1553
+ console.log('Start here:');
1554
+ console.log(` atris computer --business ${owner} --workspace ${workspaceId}`);
1555
+ console.log('');
1556
+ console.log('Org workspace:');
1557
+ console.log(` cd ~/arena/atris-business/${owner}`);
1558
+ console.log(' atris member activate operator');
1559
+ console.log(' atris member activate validator');
1560
+ console.log('');
1561
+ console.log('If the org workspace does not exist yet:');
1562
+ console.log(` atris business init "${ctx.businessName}"`);
1563
+ console.log('');
1564
+ console.log('Cost control:');
1565
+ console.log(` atris computer sleep --business ${owner} --workspace ${workspaceId}`);
1389
1566
  }
1390
1567
 
1391
1568
  async function computerSleep(token, ctx = null) {
@@ -1401,6 +1578,7 @@ async function computerSleep(token, ctx = null) {
1401
1578
  return;
1402
1579
  }
1403
1580
  console.log(' Computer is sleeping. Files persist.');
1581
+ console.log(' No compute cost while sleeping.');
1404
1582
  return;
1405
1583
  }
1406
1584
 
@@ -1415,6 +1593,113 @@ async function computerSleep(token, ctx = null) {
1415
1593
  return;
1416
1594
  }
1417
1595
  console.log(' Computer is sleeping. Files persist.');
1596
+ console.log(' No compute cost while sleeping.');
1597
+ }
1598
+
1599
+ function rememberDeletedComputer(ctx) {
1600
+ const businesses = loadBusinesses();
1601
+ let changed = false;
1602
+ for (const [slug, entry] of Object.entries(businesses)) {
1603
+ if (!entry) continue;
1604
+ const sameBusiness = entry.business_id === ctx.businessId || slug === ctx.slug;
1605
+ const sameWorkspace = entry.workspace_id === ctx.workspaceId;
1606
+ if (sameBusiness && sameWorkspace) {
1607
+ delete entry.workspace_id;
1608
+ delete entry.computer_name;
1609
+ delete entry.endpoint;
1610
+ entry.deleted_workspace_id = ctx.workspaceId;
1611
+ entry.updated_at = new Date().toISOString();
1612
+ changed = true;
1613
+ }
1614
+ }
1615
+ if (changed) saveBusinesses(businesses);
1616
+ }
1617
+
1618
+ async function confirmComputerDelete(ctx, options) {
1619
+ const expected = `delete ${ctx.workspaceId}`;
1620
+ if (String(options.confirm || '').trim() === expected) return true;
1621
+
1622
+ console.log('');
1623
+ console.log('This will sleep the computer first, then delete the workspace record.');
1624
+ console.log(`Business: ${ctx.businessName}`);
1625
+ console.log(`Workspace: ${ctx.workspaceId}`);
1626
+ console.log(`Type "${expected}" to continue.`);
1627
+
1628
+ if (!useInteractiveTerminalUi()) {
1629
+ console.error(`Confirmation required. Re-run with: --confirm "${expected}"`);
1630
+ return false;
1631
+ }
1632
+
1633
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
1634
+ const answer = String(await questionAsync(rl, 'Confirm: ') || '').trim();
1635
+ rl.close();
1636
+ if (answer === expected) return true;
1637
+
1638
+ console.error('Delete cancelled.');
1639
+ return false;
1640
+ }
1641
+
1642
+ async function computerDelete(token, ctx, options = {}, args = []) {
1643
+ const deleteOptions = parseComputerDeleteArgs(args);
1644
+ if (deleteOptions.help) {
1645
+ console.log('Usage: atris computer delete --business <slug> --workspace <workspace-id>');
1646
+ console.log('');
1647
+ console.log('Sleeps the computer first, then deletes the non-default workspace after confirmation.');
1648
+ console.log('');
1649
+ console.log('Examples:');
1650
+ console.log(' atris computer delete --business atris-labs --workspace ws_123');
1651
+ console.log(' atris computer delete --business atris-labs --workspace ws_123 --confirm "delete ws_123"');
1652
+ return;
1653
+ }
1654
+
1655
+ if (!ctx?.businessId) {
1656
+ console.error('No business found.');
1657
+ console.error('Pass: --business <slug> --workspace <workspace-id>');
1658
+ process.exitCode = 1;
1659
+ return;
1660
+ }
1661
+
1662
+ if (!options.workspaceId || !ctx.workspaceId) {
1663
+ console.error('Refusing to delete without an explicit workspace id.');
1664
+ console.error('Pass: --workspace <workspace-id>');
1665
+ process.exitCode = 1;
1666
+ return;
1667
+ }
1668
+
1669
+ const confirmed = await confirmComputerDelete(ctx, deleteOptions);
1670
+ if (!confirmed) {
1671
+ process.exitCode = 1;
1672
+ return;
1673
+ }
1674
+
1675
+ console.log(`Sleeping computer for ${ctx.businessName}...`);
1676
+ const slept = await apiRequestJson(`/business/${ctx.businessId}/ai-computer/sleep`, {
1677
+ method: 'POST',
1678
+ token,
1679
+ body: {},
1680
+ });
1681
+ if (!slept.ok) {
1682
+ console.error(`Failed to sleep computer: ${slept.errorMessage || slept.error || slept.status}`);
1683
+ process.exitCode = 1;
1684
+ return;
1685
+ }
1686
+
1687
+ console.log(' Computer is sleeping. Files persist.');
1688
+ console.log(`Deleting workspace ${ctx.workspaceId}...`);
1689
+ const deleted = await apiRequestJson(`/business/${ctx.businessId}/workspaces/${ctx.workspaceId}`, {
1690
+ method: 'DELETE',
1691
+ token,
1692
+ });
1693
+ if (!deleted.ok) {
1694
+ console.error(`Failed to delete computer: ${deleted.errorMessage || deleted.error || deleted.status}`);
1695
+ if (deleted.status === 400) console.error('Default workspaces cannot be deleted.');
1696
+ process.exitCode = 1;
1697
+ return;
1698
+ }
1699
+
1700
+ rememberDeletedComputer(ctx);
1701
+ console.log(' Computer deleted.');
1702
+ console.log(' Cost gate: sleeping before delete completed.');
1418
1703
  }
1419
1704
 
1420
1705
  async function computerRun(token, command, ctx = null) {
@@ -2105,6 +2390,9 @@ async function sendBusinessChat(token, ctx, message, sessionId, resetContext = f
2105
2390
  maxTurns: 25,
2106
2391
  });
2107
2392
  if (!fallback.ok) {
2393
+ if (typeof options.onFailure === 'function') {
2394
+ options.onFailure({ result, fallback });
2395
+ }
2108
2396
  console.error(`Failed: ${result.error || result.status}`);
2109
2397
  if (fallback.error) {
2110
2398
  console.error(`Fallback failed: ${fallback.error}`);
@@ -2616,12 +2904,33 @@ async function computerProof(token, ctx, initialOptions = {}) {
2616
2904
 
2617
2905
  console.log(ui.bold('Run'));
2618
2906
  console.log(` prompt: ${prompt}`);
2619
- const nextSessionId = await sendBusinessChat(token, ctx, prompt, sessionId, true, null, {
2907
+ let activeCtx = ctx;
2908
+ let chatFailure = null;
2909
+ let nextSessionId = await sendBusinessChat(token, activeCtx, prompt, sessionId, true, null, {
2620
2910
  worker,
2621
2911
  model,
2622
2912
  systemPrompt,
2623
2913
  localCliSessionId: bridge.sessionId,
2914
+ onFailure: (failure) => {
2915
+ chatFailure = failure;
2916
+ },
2624
2917
  });
2918
+ const retryCtx = contextForAttachedWorkspaceMismatch(activeCtx, chatFailure);
2919
+ if (retryCtx) {
2920
+ console.log('');
2921
+ console.log(`Retrying proof against attached workspace ${retryCtx.workspaceId}...`);
2922
+ activeCtx = retryCtx;
2923
+ chatFailure = null;
2924
+ nextSessionId = await sendBusinessChat(token, activeCtx, prompt, `${sessionId}-attached`, true, null, {
2925
+ worker,
2926
+ model,
2927
+ systemPrompt,
2928
+ localCliSessionId: bridge.sessionId,
2929
+ onFailure: (failure) => {
2930
+ chatFailure = failure;
2931
+ },
2932
+ });
2933
+ }
2625
2934
 
2626
2935
  const localPath = path.join(bridge.workingDir, fileName);
2627
2936
  let localContent = '';
@@ -2632,10 +2941,10 @@ async function computerProof(token, ctx, initialOptions = {}) {
2632
2941
  }
2633
2942
  const localOk = localContent === expected;
2634
2943
 
2635
- const cloudFile = await readBusinessWorkspaceFile(token, ctx, fileName, 15000);
2944
+ const cloudFile = await readBusinessWorkspaceFile(token, activeCtx, fileName, 15000);
2636
2945
  const cloudClear = !cloudFile.ok && cloudFile.status === 404;
2637
2946
 
2638
- const audit = await fetchBusinessChatAudit(token, ctx, 5);
2947
+ const audit = await fetchBusinessChatAudit(token, activeCtx, 5);
2639
2948
  const rows = audit.ok ? (audit.data?.rows || []) : [];
2640
2949
  const auditRow = rows.find((row) => row.session_id === nextSessionId || row.preview?.includes(fileName)) || rows[0] || {};
2641
2950
  const auditOk = audit.ok && auditRow.status === 'completed' && String(auditRow.result_preview || '').includes('ATRIS COMPUTER PROOF OK');
@@ -2669,6 +2978,13 @@ async function runComputer() {
2669
2978
  const sub = args[0];
2670
2979
 
2671
2980
  if (!sub) {
2981
+ if (cloudOptions.businessSlug || cloudOptions.workspaceId) {
2982
+ const token = getToken();
2983
+ const ctx = await resolveComputerCommandContext(token, cloudOptions);
2984
+ await computerChat(token, ctx, cloudOptions);
2985
+ return;
2986
+ }
2987
+
2672
2988
  const hasBusinessBinding = Boolean(readBusinessBinding());
2673
2989
  const hasLocalHarness = Boolean(findAtrisCodeTerminal());
2674
2990
  const surface = await chooseComputerSurface(hasBusinessBinding, hasLocalHarness);
@@ -2695,7 +3011,7 @@ async function runComputer() {
2695
3011
 
2696
3012
  if (sub === '--local' || sub === 'local') {
2697
3013
  const token = getToken();
2698
- const ctx = await resolveBusinessContext(token);
3014
+ const ctx = await resolveComputerCommandContext(token, cloudOptions);
2699
3015
  if (ctx) {
2700
3016
  await computerLocalAtris(token, ctx, cloudOptions);
2701
3017
  return;
@@ -2706,7 +3022,7 @@ async function runComputer() {
2706
3022
 
2707
3023
  if (sub === 'local-atris') {
2708
3024
  const token = getToken();
2709
- const ctx = await resolveBusinessContext(token);
3025
+ const ctx = await resolveComputerCommandContext(token, cloudOptions);
2710
3026
  await computerLocalAtris(token, ctx, cloudOptions);
2711
3027
  return;
2712
3028
  }
@@ -2760,20 +3076,23 @@ async function runComputer() {
2760
3076
  console.log(' --cloud Open CLOUD workspace mode in the bound business workspace');
2761
3077
  console.log(' cloud Open CLOUD workspace mode in the bound business workspace');
2762
3078
  console.log(' codeops Open Atris CodeOps workflow computer if your account has access');
3079
+ console.log(' --business Select a business by slug');
3080
+ console.log(' --workspace Select a specific workspace/computer id');
2763
3081
  console.log(' --worker Cloud worker override: claude | openai');
2764
3082
  console.log(' --model Cloud model override');
2765
3083
  console.log(' claude|codex Legacy local console backends');
2766
3084
  console.log('');
2767
3085
  console.log('Cloud commands:');
2768
- console.log(' create <name> Create and wake a business computer');
3086
+ console.log(' create <name> Create and wake an extra business computer');
2769
3087
  console.log(' chat Interactive cloud workspace chat');
2770
3088
  console.log(' Ctrl-C during a cloud run interrupts it');
2771
3089
  console.log(' /start shows the beginner flow');
2772
3090
  console.log(' /status shows lane, Claude auth, and billing');
2773
3091
  console.log(' /audit [n] shows recent cloud runs inside chat');
2774
3092
  console.log(' status Show computer status');
2775
- console.log(' wake Start the computer');
3093
+ console.log(' up|wake Start the computer');
2776
3094
  console.log(' sleep Stop the computer (files persist)');
3095
+ console.log(' delete Sleep, confirm, and delete a business computer');
2777
3096
  console.log(' run <cmd> Run bash on EC2 (no LLM cost)');
2778
3097
  console.log(' grep <pattern> Search files on EC2');
2779
3098
  console.log(' ls [path] List files');
@@ -2787,8 +3106,11 @@ async function runComputer() {
2787
3106
  console.log('Examples:');
2788
3107
  console.log(' atris computer');
2789
3108
  console.log(' atris computer card --write');
2790
- console.log(' atris computer create "My Business Computer" --business atris-labs');
2791
- console.log(' atris business init "My Lab" # shared owner + first/default computer');
3109
+ console.log(' atris business init "My Lab" # first/default computer with Atris + operator');
3110
+ console.log(' atris computer create "Recruiting Computer" --business atris-labs');
3111
+ console.log(' atris computer --business atris-labs --workspace <workspace-id>');
3112
+ console.log(' atris computer sleep --business atris-labs --workspace <workspace-id>');
3113
+ console.log(' atris computer delete --business atris-labs --workspace <workspace-id>');
2792
3114
  console.log(' atris computer proof');
2793
3115
  console.log(' atris computer local');
2794
3116
  console.log(' atris computer codex');
@@ -2809,7 +3131,11 @@ async function runComputer() {
2809
3131
  }
2810
3132
 
2811
3133
  const token = getToken();
2812
- const ctx = await resolveBusinessContext(token);
3134
+ if (sub === 'create') {
3135
+ return computerCreate(token, args.slice(1), cloudOptions);
3136
+ }
3137
+
3138
+ const ctx = await resolveComputerCommandContext(token, cloudOptions);
2813
3139
 
2814
3140
  if (sub === 'codeops') {
2815
3141
  const codeopsCtx = await resolveBusinessContextBySlug(token, 'atris-codeops');
@@ -2869,11 +3195,13 @@ async function runComputer() {
2869
3195
  switch (sub) {
2870
3196
  case 'chat': return computerChat(token, ctx, cloudOptions);
2871
3197
  case 'card': return computerCard(args.slice(1));
2872
- case 'create': return computerCreate(token, args.slice(1));
2873
3198
  case 'proof': return computerProof(token, ctx, cloudOptions);
2874
3199
  case 'status': return computerStatus(token, ctx);
3200
+ case 'up':
2875
3201
  case 'wake': return computerWake(token, ctx);
2876
3202
  case 'sleep': return computerSleep(token, ctx);
3203
+ case 'delete':
3204
+ case 'rm': return computerDelete(token, ctx, cloudOptions, args.slice(1));
2877
3205
  case 'run': return computerRun(token, rest, ctx);
2878
3206
  case 'grep': return computerGrep(token, rest, ctx);
2879
3207
  case 'ls': return computerLs(token, rest || undefined, ctx);
@@ -2897,4 +3225,6 @@ module.exports = {
2897
3225
  buildComputerCard,
2898
3226
  renderComputerCard,
2899
3227
  renderComputerCardMarkdown,
3228
+ extractAttachedWorkspaceMismatch,
3229
+ contextForAttachedWorkspaceMismatch,
2900
3230
  };
package/commands/gm.js CHANGED
@@ -5,7 +5,7 @@ const os = require('os');
5
5
  const path = require('path');
6
6
 
7
7
  const AGENTXP_LEADERBOARD_URL = 'https://api.atris.ai/api/agentxp/leaderboard';
8
- const AGENTXP_GLOBAL_SYNC_RULE = 'Use the owner-provided sync token first; fallback is atris login before sync.';
8
+ const AGENTXP_GLOBAL_SYNC_RULE = 'Run atris login, then sync. Owner-provided sync tokens are guided-demo fallback only.';
9
9
 
10
10
  const ROLE_PLAYERS_TO_IGNORE = new Set([
11
11
  'game-manager',
@@ -266,9 +266,9 @@ function compactTask(task) {
266
266
 
267
267
  function globalSyncCommands(player) {
268
268
  return [
269
- `atris xp sync --local --as ${player} --token <owner-provided-token>`,
270
269
  'atris login',
271
270
  `atris xp sync --local --as ${player}`,
271
+ `atris xp sync --local --as ${player} --token <owner-provided-token>`,
272
272
  ];
273
273
  }
274
274
 
@@ -367,7 +367,7 @@ function render(state) {
367
367
 
368
368
  console.log('');
369
369
  console.log('XP rule: no proof, no AgentXP; accept/revise stays human-gated.');
370
- console.log('Global sync: use owner token first; fallback to atris login before hosted leaderboard sync.');
370
+ console.log('Global sync: run atris login, then sync; owner tokens are guided-demo fallback only.');
371
371
  console.log(`Leaderboard: ${state.leaderboard_url}`);
372
372
  console.log('');
373
373
  console.log('Next commands:');