atris 3.15.30 → 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.
- package/bin/atris.js +1 -1
- package/commands/business.js +21 -2
- package/commands/computer.js +515 -10
- package/commands/gm.js +4 -2
- package/commands/lifecycle.js +115 -0
- package/commands/mission.js +9 -3
- package/commands/play.js +4 -2
- package/commands/xp.js +309 -3
- package/lib/runtime-bootstrap.js +107 -0
- package/package.json +1 -1
package/commands/computer.js
CHANGED
|
@@ -3,8 +3,10 @@
|
|
|
3
3
|
*
|
|
4
4
|
* atris computer — Open SMART mode (cloud in business workspace, local elsewhere)
|
|
5
5
|
* atris computer --cloud — Open CLOUD workspace mode
|
|
6
|
+
* atris computer create <name> — Create and wake a business computer
|
|
6
7
|
* atris computer wake — Start the computer
|
|
7
8
|
* atris computer sleep — Stop (files persist)
|
|
9
|
+
* atris computer delete — Sleep, confirm, and delete a business computer
|
|
8
10
|
* atris computer card — Show the local computer card
|
|
9
11
|
* atris computer run <command> — Run bash on EC2 (no LLM)
|
|
10
12
|
* atris computer grep <pattern> — Search files on EC2
|
|
@@ -19,10 +21,11 @@ const path = require('path');
|
|
|
19
21
|
const readline = require('readline');
|
|
20
22
|
const { spawnSync } = require('child_process');
|
|
21
23
|
const { loadCredentials, decodeJwtClaims } = require('../utils/auth');
|
|
22
|
-
const { apiRequestJson, getApiBaseUrl } = require('../utils/api');
|
|
24
|
+
const { apiRequestJson, getApiBaseUrl, getAppBaseUrl } = require('../utils/api');
|
|
23
25
|
const { loadBusinesses, saveBusinesses } = require('./business');
|
|
24
26
|
const { consoleCommand, gatherAtrisContext, buildSystemPrompt } = require('./console');
|
|
25
27
|
const { streamSession } = require('./serve');
|
|
28
|
+
const { buildRemoteAtrisBootstrapCommand } = require('../lib/runtime-bootstrap');
|
|
26
29
|
|
|
27
30
|
function getToken() {
|
|
28
31
|
const creds = loadCredentials();
|
|
@@ -149,6 +152,38 @@ function formatBillingMode(worker) {
|
|
|
149
152
|
: 'Claude subscription lane';
|
|
150
153
|
}
|
|
151
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
|
+
|
|
152
187
|
async function describeClaudeAuth(token, ctx) {
|
|
153
188
|
try {
|
|
154
189
|
const status = await fetchBusinessClaudeLoginStatus(token, ctx);
|
|
@@ -505,9 +540,33 @@ function parseComputerOptions(argv) {
|
|
|
505
540
|
const positional = [];
|
|
506
541
|
let worker = process.env.ATRIS_CLOUD_WORKER || null;
|
|
507
542
|
let model = process.env.ATRIS_CLOUD_MODEL || null;
|
|
543
|
+
let businessSlug = null;
|
|
544
|
+
let workspaceId = null;
|
|
508
545
|
|
|
509
546
|
for (let i = 0; i < argv.length; i++) {
|
|
510
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
|
+
}
|
|
511
570
|
if (arg === '--worker' && argv[i + 1]) {
|
|
512
571
|
worker = argv[i + 1];
|
|
513
572
|
i++;
|
|
@@ -540,10 +599,65 @@ function parseComputerOptions(argv) {
|
|
|
540
599
|
options: {
|
|
541
600
|
worker: worker || null,
|
|
542
601
|
model: model || null,
|
|
602
|
+
businessSlug: businessSlug ? String(businessSlug).trim() : null,
|
|
603
|
+
workspaceId: workspaceId ? String(workspaceId).trim() : null,
|
|
543
604
|
},
|
|
544
605
|
};
|
|
545
606
|
}
|
|
546
607
|
|
|
608
|
+
function parseComputerCreateArgs(argv = []) {
|
|
609
|
+
const nameParts = [];
|
|
610
|
+
let businessSlug = null;
|
|
611
|
+
let help = false;
|
|
612
|
+
|
|
613
|
+
for (let i = 0; i < argv.length; i++) {
|
|
614
|
+
const arg = argv[i];
|
|
615
|
+
if (arg === '--help' || arg === '-h' || arg === 'help') {
|
|
616
|
+
help = true;
|
|
617
|
+
continue;
|
|
618
|
+
}
|
|
619
|
+
if ((arg === '--business' || arg === '-b') && argv[i + 1]) {
|
|
620
|
+
businessSlug = argv[i + 1];
|
|
621
|
+
i++;
|
|
622
|
+
continue;
|
|
623
|
+
}
|
|
624
|
+
if (arg.startsWith('--business=')) {
|
|
625
|
+
businessSlug = arg.split('=', 2)[1] || null;
|
|
626
|
+
continue;
|
|
627
|
+
}
|
|
628
|
+
nameParts.push(arg);
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
return {
|
|
632
|
+
name: nameParts.join(' ').trim(),
|
|
633
|
+
businessSlug: businessSlug ? String(businessSlug).trim() : null,
|
|
634
|
+
help,
|
|
635
|
+
};
|
|
636
|
+
}
|
|
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
|
+
|
|
547
661
|
function formatCloudSelection(options = {}) {
|
|
548
662
|
const worker = activeWorker(options.worker);
|
|
549
663
|
const parts = [`worker=${worker}`];
|
|
@@ -923,8 +1037,32 @@ async function resolveBusinessContext(token) {
|
|
|
923
1037
|
return null;
|
|
924
1038
|
}
|
|
925
1039
|
|
|
926
|
-
|
|
1040
|
+
function cachedBusinessContext(slug) {
|
|
927
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 = {}) {
|
|
1060
|
+
if (!slug) return null;
|
|
1061
|
+
|
|
1062
|
+
if (options.preferCache) {
|
|
1063
|
+
const cached = cachedBusinessContext(slug);
|
|
1064
|
+
if (cached?.workspaceId) return cached;
|
|
1065
|
+
}
|
|
928
1066
|
|
|
929
1067
|
const businesses = loadBusinesses();
|
|
930
1068
|
const list = await apiRequestJson('/business/', { method: 'GET', token });
|
|
@@ -953,6 +1091,70 @@ async function resolveBusinessContextBySlug(token, slug) {
|
|
|
953
1091
|
return null;
|
|
954
1092
|
}
|
|
955
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
|
+
|
|
1109
|
+
async function resolveBusinessOwnerForCreate(token, businessSlug = null) {
|
|
1110
|
+
const wantedSlug = businessSlug ? String(businessSlug).trim() : null;
|
|
1111
|
+
if (wantedSlug) {
|
|
1112
|
+
const fromApi = await resolveBusinessContextBySlug(token, wantedSlug);
|
|
1113
|
+
if (fromApi) return fromApi;
|
|
1114
|
+
|
|
1115
|
+
const cached = loadBusinesses()[wantedSlug];
|
|
1116
|
+
if (cached?.business_id) {
|
|
1117
|
+
return {
|
|
1118
|
+
slug: cached.slug || wantedSlug,
|
|
1119
|
+
businessId: cached.business_id,
|
|
1120
|
+
workspaceId: cached.workspace_id || null,
|
|
1121
|
+
businessName: cached.name || cached.slug || wantedSlug,
|
|
1122
|
+
};
|
|
1123
|
+
}
|
|
1124
|
+
return null;
|
|
1125
|
+
}
|
|
1126
|
+
|
|
1127
|
+
const binding = readBusinessBinding();
|
|
1128
|
+
if (binding?.business_id) {
|
|
1129
|
+
return {
|
|
1130
|
+
slug: binding.slug || binding.canonical_slug || null,
|
|
1131
|
+
businessId: binding.business_id,
|
|
1132
|
+
workspaceId: binding.workspace_id || null,
|
|
1133
|
+
businessName: binding.name || binding.slug || 'business',
|
|
1134
|
+
};
|
|
1135
|
+
}
|
|
1136
|
+
|
|
1137
|
+
return resolveBusinessContext(token);
|
|
1138
|
+
}
|
|
1139
|
+
|
|
1140
|
+
function rememberCreatedComputer(ctx, workspace, endpoint = null) {
|
|
1141
|
+
const slug = ctx.slug || (ctx.businessName || '').toLowerCase().replace(/\s+/g, '-');
|
|
1142
|
+
if (!slug) return;
|
|
1143
|
+
const businesses = loadBusinesses();
|
|
1144
|
+
businesses[slug] = {
|
|
1145
|
+
...(businesses[slug] || {}),
|
|
1146
|
+
business_id: ctx.businessId,
|
|
1147
|
+
workspace_id: workspace.id,
|
|
1148
|
+
name: ctx.businessName,
|
|
1149
|
+
slug,
|
|
1150
|
+
computer_name: workspace.name,
|
|
1151
|
+
endpoint: endpoint || undefined,
|
|
1152
|
+
added_at: businesses[slug]?.added_at || new Date().toISOString(),
|
|
1153
|
+
updated_at: new Date().toISOString(),
|
|
1154
|
+
};
|
|
1155
|
+
saveBusinesses(businesses);
|
|
1156
|
+
}
|
|
1157
|
+
|
|
956
1158
|
function shellQuote(value) {
|
|
957
1159
|
return `'${String(value).replace(/'/g, `'\\''`)}'`;
|
|
958
1160
|
}
|
|
@@ -974,6 +1176,42 @@ async function runBusinessTerminalCommand(token, ctx, command, timeout = 30) {
|
|
|
974
1176
|
);
|
|
975
1177
|
}
|
|
976
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
|
+
|
|
977
1215
|
async function readBusinessWorkspaceFile(token, ctx, remotePath, timeoutMs = 15000) {
|
|
978
1216
|
return apiRequestJson(
|
|
979
1217
|
`/business/${ctx.businessId}/workspaces/${ctx.workspaceId}/file?path=${encodeURIComponent(remotePath)}`,
|
|
@@ -1137,6 +1375,7 @@ async function ensureBusinessAwake(token, ctx, maxWaitSec = 90) {
|
|
|
1137
1375
|
if (next.ok && next.data && next.data.status === 'running' && next.data.endpoint) {
|
|
1138
1376
|
const elapsed = Math.floor((Date.now() - start) / 1000);
|
|
1139
1377
|
console.log(`awake (${elapsed}s)`);
|
|
1378
|
+
await bootstrapBusinessComputerRuntime(token, ctx, 'computer-auto-wake');
|
|
1140
1379
|
return true;
|
|
1141
1380
|
}
|
|
1142
1381
|
}
|
|
@@ -1207,6 +1446,8 @@ async function computerWake(token, ctx = null) {
|
|
|
1207
1446
|
}
|
|
1208
1447
|
console.log(` Status: ${result.data.status}`);
|
|
1209
1448
|
if (result.data.endpoint) console.log(` Endpoint: ${result.data.endpoint}`);
|
|
1449
|
+
await bootstrapBusinessComputerRuntime(token, ctx, 'computer-wake');
|
|
1450
|
+
console.log(' Computer is awake.');
|
|
1210
1451
|
return;
|
|
1211
1452
|
}
|
|
1212
1453
|
|
|
@@ -1222,6 +1463,106 @@ async function computerWake(token, ctx = null) {
|
|
|
1222
1463
|
}
|
|
1223
1464
|
console.log(` Status: ${result.data.status}`);
|
|
1224
1465
|
console.log(` Endpoint: ${result.data.endpoint}`);
|
|
1466
|
+
console.log(' Computer is awake.');
|
|
1467
|
+
}
|
|
1468
|
+
|
|
1469
|
+
async function computerCreate(token, args = [], defaults = {}) {
|
|
1470
|
+
const options = parseComputerCreateArgs(args);
|
|
1471
|
+
if (!options.businessSlug && defaults.businessSlug) {
|
|
1472
|
+
options.businessSlug = defaults.businessSlug;
|
|
1473
|
+
}
|
|
1474
|
+
if (options.help || !options.name) {
|
|
1475
|
+
console.log('Usage: atris computer create <name> --business <slug>');
|
|
1476
|
+
console.log('');
|
|
1477
|
+
console.log('Create a business computer, activate it, and wake it in one command.');
|
|
1478
|
+
console.log('');
|
|
1479
|
+
console.log('Examples:');
|
|
1480
|
+
console.log(' atris computer create "My Business Computer" --business atris-labs');
|
|
1481
|
+
console.log(' atris computer create "Recruiting Computer"');
|
|
1482
|
+
if (!options.name && !options.help) process.exitCode = 1;
|
|
1483
|
+
return;
|
|
1484
|
+
}
|
|
1485
|
+
|
|
1486
|
+
const ctx = await resolveBusinessOwnerForCreate(token, options.businessSlug);
|
|
1487
|
+
if (!ctx?.businessId) {
|
|
1488
|
+
console.error('No business found.');
|
|
1489
|
+
console.error('Run inside a bound business workspace or pass: --business <slug>');
|
|
1490
|
+
process.exitCode = 1;
|
|
1491
|
+
return;
|
|
1492
|
+
}
|
|
1493
|
+
|
|
1494
|
+
console.log(`Creating computer "${options.name}" for ${ctx.businessName}...`);
|
|
1495
|
+
const created = await apiRequestJson(`/business/${ctx.businessId}/workspaces`, {
|
|
1496
|
+
method: 'POST',
|
|
1497
|
+
token,
|
|
1498
|
+
body: { name: options.name, type: 'general' },
|
|
1499
|
+
});
|
|
1500
|
+
if (!created.ok) {
|
|
1501
|
+
console.error(`Failed to create workspace: ${created.errorMessage || created.error || created.status}`);
|
|
1502
|
+
process.exitCode = 1;
|
|
1503
|
+
return;
|
|
1504
|
+
}
|
|
1505
|
+
|
|
1506
|
+
const workspace = created.data || {};
|
|
1507
|
+
const workspaceId = workspace.id || workspace.workspace_id;
|
|
1508
|
+
if (!workspaceId) {
|
|
1509
|
+
console.error('Failed to create workspace: response did not include workspace id');
|
|
1510
|
+
process.exitCode = 1;
|
|
1511
|
+
return;
|
|
1512
|
+
}
|
|
1513
|
+
|
|
1514
|
+
const activate = await apiRequestJson(`/business/${ctx.businessId}/workspaces/${workspaceId}/activate`, {
|
|
1515
|
+
method: 'POST',
|
|
1516
|
+
token,
|
|
1517
|
+
body: {},
|
|
1518
|
+
});
|
|
1519
|
+
if (!activate.ok && activate.status !== 409) {
|
|
1520
|
+
console.error(`Failed to activate computer: ${activate.errorMessage || activate.error || activate.status}`);
|
|
1521
|
+
process.exitCode = 1;
|
|
1522
|
+
return;
|
|
1523
|
+
}
|
|
1524
|
+
|
|
1525
|
+
const wake = await apiRequestJson(`/business/${ctx.businessId}/ai-computer/wake`, {
|
|
1526
|
+
method: 'POST',
|
|
1527
|
+
token,
|
|
1528
|
+
body: {},
|
|
1529
|
+
});
|
|
1530
|
+
if (!wake.ok && !activate.ok) {
|
|
1531
|
+
console.error(`Failed to wake computer: ${wake.errorMessage || wake.error || wake.status}`);
|
|
1532
|
+
process.exitCode = 1;
|
|
1533
|
+
return;
|
|
1534
|
+
}
|
|
1535
|
+
|
|
1536
|
+
const endpoint = activate.data?.endpoint || wake.data?.endpoint || null;
|
|
1537
|
+
const status = endpoint
|
|
1538
|
+
? 'running'
|
|
1539
|
+
: (wake.data?.status || (activate.ok ? 'activated' : 'warming_up'));
|
|
1540
|
+
rememberCreatedComputer(ctx, { ...workspace, id: workspaceId, name: workspace.name || options.name }, endpoint);
|
|
1541
|
+
await bootstrapBusinessComputerRuntime(token, { ...ctx, workspaceId }, 'computer-create');
|
|
1542
|
+
|
|
1543
|
+
const appBase = getAppBaseUrl();
|
|
1544
|
+
console.log('');
|
|
1545
|
+
console.log(`Computer created: ${workspaceId}`);
|
|
1546
|
+
console.log(` Name: ${workspace.name || options.name}`);
|
|
1547
|
+
console.log(` Business: ${ctx.businessName}`);
|
|
1548
|
+
console.log(` Status: ${status}`);
|
|
1549
|
+
if (endpoint) console.log(` Endpoint: ${endpoint}`);
|
|
1550
|
+
console.log(` Dashboard: ${appBase}/dashboard/gm/${ctx.businessId}`);
|
|
1551
|
+
console.log('');
|
|
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}`);
|
|
1225
1566
|
}
|
|
1226
1567
|
|
|
1227
1568
|
async function computerSleep(token, ctx = null) {
|
|
@@ -1237,6 +1578,7 @@ async function computerSleep(token, ctx = null) {
|
|
|
1237
1578
|
return;
|
|
1238
1579
|
}
|
|
1239
1580
|
console.log(' Computer is sleeping. Files persist.');
|
|
1581
|
+
console.log(' No compute cost while sleeping.');
|
|
1240
1582
|
return;
|
|
1241
1583
|
}
|
|
1242
1584
|
|
|
@@ -1251,6 +1593,113 @@ async function computerSleep(token, ctx = null) {
|
|
|
1251
1593
|
return;
|
|
1252
1594
|
}
|
|
1253
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.');
|
|
1254
1703
|
}
|
|
1255
1704
|
|
|
1256
1705
|
async function computerRun(token, command, ctx = null) {
|
|
@@ -1941,6 +2390,9 @@ async function sendBusinessChat(token, ctx, message, sessionId, resetContext = f
|
|
|
1941
2390
|
maxTurns: 25,
|
|
1942
2391
|
});
|
|
1943
2392
|
if (!fallback.ok) {
|
|
2393
|
+
if (typeof options.onFailure === 'function') {
|
|
2394
|
+
options.onFailure({ result, fallback });
|
|
2395
|
+
}
|
|
1944
2396
|
console.error(`Failed: ${result.error || result.status}`);
|
|
1945
2397
|
if (fallback.error) {
|
|
1946
2398
|
console.error(`Fallback failed: ${fallback.error}`);
|
|
@@ -2452,12 +2904,33 @@ async function computerProof(token, ctx, initialOptions = {}) {
|
|
|
2452
2904
|
|
|
2453
2905
|
console.log(ui.bold('Run'));
|
|
2454
2906
|
console.log(` prompt: ${prompt}`);
|
|
2455
|
-
|
|
2907
|
+
let activeCtx = ctx;
|
|
2908
|
+
let chatFailure = null;
|
|
2909
|
+
let nextSessionId = await sendBusinessChat(token, activeCtx, prompt, sessionId, true, null, {
|
|
2456
2910
|
worker,
|
|
2457
2911
|
model,
|
|
2458
2912
|
systemPrompt,
|
|
2459
2913
|
localCliSessionId: bridge.sessionId,
|
|
2914
|
+
onFailure: (failure) => {
|
|
2915
|
+
chatFailure = failure;
|
|
2916
|
+
},
|
|
2460
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
|
+
}
|
|
2461
2934
|
|
|
2462
2935
|
const localPath = path.join(bridge.workingDir, fileName);
|
|
2463
2936
|
let localContent = '';
|
|
@@ -2468,10 +2941,10 @@ async function computerProof(token, ctx, initialOptions = {}) {
|
|
|
2468
2941
|
}
|
|
2469
2942
|
const localOk = localContent === expected;
|
|
2470
2943
|
|
|
2471
|
-
const cloudFile = await readBusinessWorkspaceFile(token,
|
|
2944
|
+
const cloudFile = await readBusinessWorkspaceFile(token, activeCtx, fileName, 15000);
|
|
2472
2945
|
const cloudClear = !cloudFile.ok && cloudFile.status === 404;
|
|
2473
2946
|
|
|
2474
|
-
const audit = await fetchBusinessChatAudit(token,
|
|
2947
|
+
const audit = await fetchBusinessChatAudit(token, activeCtx, 5);
|
|
2475
2948
|
const rows = audit.ok ? (audit.data?.rows || []) : [];
|
|
2476
2949
|
const auditRow = rows.find((row) => row.session_id === nextSessionId || row.preview?.includes(fileName)) || rows[0] || {};
|
|
2477
2950
|
const auditOk = audit.ok && auditRow.status === 'completed' && String(auditRow.result_preview || '').includes('ATRIS COMPUTER PROOF OK');
|
|
@@ -2505,6 +2978,13 @@ async function runComputer() {
|
|
|
2505
2978
|
const sub = args[0];
|
|
2506
2979
|
|
|
2507
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
|
+
|
|
2508
2988
|
const hasBusinessBinding = Boolean(readBusinessBinding());
|
|
2509
2989
|
const hasLocalHarness = Boolean(findAtrisCodeTerminal());
|
|
2510
2990
|
const surface = await chooseComputerSurface(hasBusinessBinding, hasLocalHarness);
|
|
@@ -2531,7 +3011,7 @@ async function runComputer() {
|
|
|
2531
3011
|
|
|
2532
3012
|
if (sub === '--local' || sub === 'local') {
|
|
2533
3013
|
const token = getToken();
|
|
2534
|
-
const ctx = await
|
|
3014
|
+
const ctx = await resolveComputerCommandContext(token, cloudOptions);
|
|
2535
3015
|
if (ctx) {
|
|
2536
3016
|
await computerLocalAtris(token, ctx, cloudOptions);
|
|
2537
3017
|
return;
|
|
@@ -2542,7 +3022,7 @@ async function runComputer() {
|
|
|
2542
3022
|
|
|
2543
3023
|
if (sub === 'local-atris') {
|
|
2544
3024
|
const token = getToken();
|
|
2545
|
-
const ctx = await
|
|
3025
|
+
const ctx = await resolveComputerCommandContext(token, cloudOptions);
|
|
2546
3026
|
await computerLocalAtris(token, ctx, cloudOptions);
|
|
2547
3027
|
return;
|
|
2548
3028
|
}
|
|
@@ -2562,6 +3042,14 @@ async function runComputer() {
|
|
|
2562
3042
|
return;
|
|
2563
3043
|
}
|
|
2564
3044
|
|
|
3045
|
+
if (sub === 'create') {
|
|
3046
|
+
const createOptions = parseComputerCreateArgs(args.slice(1));
|
|
3047
|
+
if (createOptions.help || !createOptions.name) {
|
|
3048
|
+
await computerCreate(null, args.slice(1));
|
|
3049
|
+
return;
|
|
3050
|
+
}
|
|
3051
|
+
}
|
|
3052
|
+
|
|
2565
3053
|
if (sub === '--help') {
|
|
2566
3054
|
console.log('Usage: atris computer [mode|command]');
|
|
2567
3055
|
console.log('');
|
|
@@ -2588,19 +3076,23 @@ async function runComputer() {
|
|
|
2588
3076
|
console.log(' --cloud Open CLOUD workspace mode in the bound business workspace');
|
|
2589
3077
|
console.log(' cloud Open CLOUD workspace mode in the bound business workspace');
|
|
2590
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');
|
|
2591
3081
|
console.log(' --worker Cloud worker override: claude | openai');
|
|
2592
3082
|
console.log(' --model Cloud model override');
|
|
2593
3083
|
console.log(' claude|codex Legacy local console backends');
|
|
2594
3084
|
console.log('');
|
|
2595
3085
|
console.log('Cloud commands:');
|
|
3086
|
+
console.log(' create <name> Create and wake an extra business computer');
|
|
2596
3087
|
console.log(' chat Interactive cloud workspace chat');
|
|
2597
3088
|
console.log(' Ctrl-C during a cloud run interrupts it');
|
|
2598
3089
|
console.log(' /start shows the beginner flow');
|
|
2599
3090
|
console.log(' /status shows lane, Claude auth, and billing');
|
|
2600
3091
|
console.log(' /audit [n] shows recent cloud runs inside chat');
|
|
2601
3092
|
console.log(' status Show computer status');
|
|
2602
|
-
console.log(' wake
|
|
3093
|
+
console.log(' up|wake Start the computer');
|
|
2603
3094
|
console.log(' sleep Stop the computer (files persist)');
|
|
3095
|
+
console.log(' delete Sleep, confirm, and delete a business computer');
|
|
2604
3096
|
console.log(' run <cmd> Run bash on EC2 (no LLM cost)');
|
|
2605
3097
|
console.log(' grep <pattern> Search files on EC2');
|
|
2606
3098
|
console.log(' ls [path] List files');
|
|
@@ -2614,7 +3106,11 @@ async function runComputer() {
|
|
|
2614
3106
|
console.log('Examples:');
|
|
2615
3107
|
console.log(' atris computer');
|
|
2616
3108
|
console.log(' atris computer card --write');
|
|
2617
|
-
console.log(' atris business init "My Lab" #
|
|
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>');
|
|
2618
3114
|
console.log(' atris computer proof');
|
|
2619
3115
|
console.log(' atris computer local');
|
|
2620
3116
|
console.log(' atris computer codex');
|
|
@@ -2635,7 +3131,11 @@ async function runComputer() {
|
|
|
2635
3131
|
}
|
|
2636
3132
|
|
|
2637
3133
|
const token = getToken();
|
|
2638
|
-
|
|
3134
|
+
if (sub === 'create') {
|
|
3135
|
+
return computerCreate(token, args.slice(1), cloudOptions);
|
|
3136
|
+
}
|
|
3137
|
+
|
|
3138
|
+
const ctx = await resolveComputerCommandContext(token, cloudOptions);
|
|
2639
3139
|
|
|
2640
3140
|
if (sub === 'codeops') {
|
|
2641
3141
|
const codeopsCtx = await resolveBusinessContextBySlug(token, 'atris-codeops');
|
|
@@ -2697,8 +3197,11 @@ async function runComputer() {
|
|
|
2697
3197
|
case 'card': return computerCard(args.slice(1));
|
|
2698
3198
|
case 'proof': return computerProof(token, ctx, cloudOptions);
|
|
2699
3199
|
case 'status': return computerStatus(token, ctx);
|
|
3200
|
+
case 'up':
|
|
2700
3201
|
case 'wake': return computerWake(token, ctx);
|
|
2701
3202
|
case 'sleep': return computerSleep(token, ctx);
|
|
3203
|
+
case 'delete':
|
|
3204
|
+
case 'rm': return computerDelete(token, ctx, cloudOptions, args.slice(1));
|
|
2702
3205
|
case 'run': return computerRun(token, rest, ctx);
|
|
2703
3206
|
case 'grep': return computerGrep(token, rest, ctx);
|
|
2704
3207
|
case 'ls': return computerLs(token, rest || undefined, ctx);
|
|
@@ -2722,4 +3225,6 @@ module.exports = {
|
|
|
2722
3225
|
buildComputerCard,
|
|
2723
3226
|
renderComputerCard,
|
|
2724
3227
|
renderComputerCardMarkdown,
|
|
3228
|
+
extractAttachedWorkspaceMismatch,
|
|
3229
|
+
contextForAttachedWorkspaceMismatch,
|
|
2725
3230
|
};
|