atris 3.15.30 → 3.15.31
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/ax +21 -479
- package/commands/aeo.js +13 -377
- package/commands/computer.js +176 -1
- package/commands/gm.js +4 -2
- package/commands/play.js +4 -2
- package/commands/xp.js +14 -1
- package/package.json +1 -1
package/commands/aeo.js
CHANGED
|
@@ -1,98 +1,24 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* atris aeo — AI Engine Optimization commands
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
* atris aeo
|
|
6
|
-
* atris aeo draft "<topic>" [opts] # generate citation-optimized article
|
|
4
|
+
* atris aeo init # create entity-graph skeleton in workspace
|
|
5
|
+
* atris aeo draft "<topic>" [opts] # generate citation-optimized article (credit-metered)
|
|
7
6
|
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
* atris aeo packet <slug> # buyer packet for a surface
|
|
12
|
-
* atris aeo proofs [--filter X] # list proof receipt categories
|
|
7
|
+
* Hits the backend endpoints registered under:
|
|
8
|
+
* POST /api/business/{id}/workspaces/{ws}/aeo/init
|
|
9
|
+
* POST /api/business/{id}/workspaces/{ws}/aeo/draft
|
|
13
10
|
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
* Backend root resolution: $ATRIS_BACKEND_ROOT or ~/arena/atrisos-backend.
|
|
11
|
+
* Business resolution mirrors `atris terminal`: explicit --workspace slug,
|
|
12
|
+
* else cwd .atris/business.json. The endpoint itself takes care of running
|
|
13
|
+
* Claude Sonnet 4.6 with the 10 AEO rules and writing to /workspace/atris/aeo/drafts/.
|
|
19
14
|
*/
|
|
20
15
|
|
|
21
16
|
const fs = require('fs');
|
|
22
|
-
const os = require('os');
|
|
23
17
|
const path = require('path');
|
|
24
|
-
const { spawnSync } = require('child_process');
|
|
25
18
|
const { loadCredentials } = require('../utils/auth');
|
|
26
19
|
const { apiRequestJson } = require('../utils/api');
|
|
27
20
|
const { loadBusinesses, saveBusinesses } = require('./business');
|
|
28
21
|
|
|
29
|
-
function resolveBackendRoot() {
|
|
30
|
-
const candidates = [
|
|
31
|
-
process.env.ATRIS_BACKEND_ROOT,
|
|
32
|
-
path.join(os.homedir(), 'arena', 'atrisos-backend'),
|
|
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;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
function requireBackendRoot() {
|
|
41
|
-
const root = resolveBackendRoot();
|
|
42
|
-
if (!root) {
|
|
43
|
-
console.error('Cannot find atrisos-backend. Set $ATRIS_BACKEND_ROOT or clone to ~/arena/atrisos-backend.');
|
|
44
|
-
process.exit(1);
|
|
45
|
-
}
|
|
46
|
-
return root;
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
function readJsonSafe(p) {
|
|
50
|
-
if (!fs.existsSync(p)) return null;
|
|
51
|
-
try {
|
|
52
|
-
return JSON.parse(fs.readFileSync(p, 'utf8'));
|
|
53
|
-
} catch (err) {
|
|
54
|
-
console.error(` warning: malformed JSON at ${p} (${err.message})`);
|
|
55
|
-
return null;
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
function pad(s, n) { s = String(s); return s.length >= n ? s.slice(0, n) : s + ' '.repeat(n - s.length); }
|
|
60
|
-
|
|
61
|
-
function assertNoExtras(sub, args, allowedFlags) {
|
|
62
|
-
const allowed = new Set(allowedFlags);
|
|
63
|
-
const unknownFlags = args.filter((a) => a.startsWith('--') && !allowed.has(a));
|
|
64
|
-
const positional = args.filter((a) => !a.startsWith('--'));
|
|
65
|
-
if (unknownFlags.length) {
|
|
66
|
-
console.error(`Unknown flag for aeo ${sub}: ${unknownFlags.join(' ')}. Supported: ${[...allowed].join(' ') || '(none)'}`);
|
|
67
|
-
process.exit(1);
|
|
68
|
-
}
|
|
69
|
-
if (positional.length) {
|
|
70
|
-
console.error(`Unexpected argument for aeo ${sub}: ${positional.join(' ')}`);
|
|
71
|
-
process.exit(1);
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
function readArg(args, ...keys) {
|
|
76
|
-
for (const k of keys) {
|
|
77
|
-
const eqIdx = args.findIndex((a) => a.startsWith(`${k}=`));
|
|
78
|
-
if (eqIdx !== -1) {
|
|
79
|
-
const v = args[eqIdx].slice(k.length + 1);
|
|
80
|
-
args.splice(eqIdx, 1);
|
|
81
|
-
return v;
|
|
82
|
-
}
|
|
83
|
-
const i = args.findIndex((a) => a === k);
|
|
84
|
-
if (i === -1) continue;
|
|
85
|
-
const v = args[i + 1];
|
|
86
|
-
if (v === undefined || v.startsWith('--')) {
|
|
87
|
-
console.error(`Flag ${k} requires a value.`);
|
|
88
|
-
process.exit(1);
|
|
89
|
-
}
|
|
90
|
-
args.splice(i, 2);
|
|
91
|
-
return v;
|
|
92
|
-
}
|
|
93
|
-
return null;
|
|
94
|
-
}
|
|
95
|
-
|
|
96
22
|
function sleep(ms) { return new Promise((r) => setTimeout(r, ms)); }
|
|
97
23
|
|
|
98
24
|
async function ensureAwake(token, businessId, maxWaitSec = 90) {
|
|
@@ -157,25 +83,13 @@ function pickSlug(args) {
|
|
|
157
83
|
|
|
158
84
|
function printHelp() {
|
|
159
85
|
console.log('Usage:');
|
|
160
|
-
console.log('
|
|
161
|
-
console.log('
|
|
162
|
-
console.log(' atris aeo draft "<topic>" [--workspace <slug>] [--queries q1,q2] [--slug X] [--url URL]');
|
|
163
|
-
console.log('');
|
|
164
|
-
console.log(' Local read (atris/features/aeo/proof/):');
|
|
165
|
-
console.log(' atris aeo log [--engine X] [--limit N] [--json]');
|
|
166
|
-
console.log(' atris aeo status [--json]');
|
|
167
|
-
console.log(' atris aeo packet <slug> [--json]');
|
|
168
|
-
console.log(' atris aeo proofs [--filter X]');
|
|
169
|
-
console.log('');
|
|
170
|
-
console.log(' Script wrappers (scripts/aeo_*.py):');
|
|
171
|
-
console.log(' atris aeo discover <source> [--question Q ...] [--canonical-url URL] [--out-dir DIR]');
|
|
172
|
-
console.log(' atris aeo audit <source> [--baseline B] [--canonical-url URL] [--out-dir DIR]');
|
|
86
|
+
console.log(' atris aeo init [--workspace <slug>]');
|
|
87
|
+
console.log(' atris aeo draft "<topic>" [--workspace <slug>] [--queries q1,q2] [--slug X] [--url URL]');
|
|
173
88
|
console.log('');
|
|
174
89
|
console.log('Examples:');
|
|
175
|
-
console.log(' atris aeo
|
|
176
|
-
console.log(' atris aeo
|
|
177
|
-
console.log(' atris aeo
|
|
178
|
-
console.log(' atris aeo discover https://atris.ai/aeo --canonical-url https://atris.ai/aeo');
|
|
90
|
+
console.log(' atris aeo init');
|
|
91
|
+
console.log(' atris aeo draft "what is example-co" --queries "what is example-co,best freight platform"');
|
|
92
|
+
console.log(' atris aeo draft "how does atris work" --workspace doordash --slug atris-overview');
|
|
179
93
|
}
|
|
180
94
|
|
|
181
95
|
async function aeoInit(args) {
|
|
@@ -269,290 +183,12 @@ async function aeoDraft(args) {
|
|
|
269
183
|
if (data.hint) console.log(` hint: ${data.hint}`);
|
|
270
184
|
}
|
|
271
185
|
|
|
272
|
-
// ---------- LOCAL READ SUBCOMMANDS ----------
|
|
273
|
-
|
|
274
|
-
function loadCitationAttempts(root) {
|
|
275
|
-
const dir = path.join(root, 'atris', 'features', 'aeo', 'proof', 'live-citation-attempts');
|
|
276
|
-
if (!fs.existsSync(dir)) return [];
|
|
277
|
-
const rows = [];
|
|
278
|
-
for (const file of fs.readdirSync(dir)) {
|
|
279
|
-
if (!file.endsWith('.json')) continue;
|
|
280
|
-
const data = readJsonSafe(path.join(dir, file));
|
|
281
|
-
if (!data || !Array.isArray(data.attempts)) continue;
|
|
282
|
-
for (const attempt of data.attempts) {
|
|
283
|
-
if (!attempt || typeof attempt !== 'object') continue;
|
|
284
|
-
const cited = attempt.answer_cites_target_url === true;
|
|
285
|
-
const mentioned = attempt.answer_mentions_target_entity === true;
|
|
286
|
-
let status;
|
|
287
|
-
if (cited) status = 'verified';
|
|
288
|
-
else if (mentioned) status = 'pending';
|
|
289
|
-
else status = 'failed';
|
|
290
|
-
const str = (v) => (typeof v === 'string' ? v : '');
|
|
291
|
-
const arr = (v) => (Array.isArray(v) ? v : []);
|
|
292
|
-
rows.push({
|
|
293
|
-
file,
|
|
294
|
-
attempted_at: str(data.attempted_at),
|
|
295
|
-
engine: str(data.engine),
|
|
296
|
-
prompt_id: str(attempt.prompt_id),
|
|
297
|
-
prompt: str(attempt.exact_prompt),
|
|
298
|
-
target_entity: str(data.target_entity),
|
|
299
|
-
target_urls: arr(data.target_url_candidates),
|
|
300
|
-
answer_evidence_uri: str(attempt.answer_evidence_uri),
|
|
301
|
-
status,
|
|
302
|
-
competitors: arr(attempt.observed_competitor_or_alternative_entities),
|
|
303
|
-
});
|
|
304
|
-
}
|
|
305
|
-
}
|
|
306
|
-
rows.sort((a, b) => (b.attempted_at || '').localeCompare(a.attempted_at || ''));
|
|
307
|
-
return rows;
|
|
308
|
-
}
|
|
309
|
-
|
|
310
|
-
async function aeoLog(args) {
|
|
311
|
-
const engine = readArg(args, '--engine', '-e');
|
|
312
|
-
const limitRaw = readArg(args, '--limit', '-n');
|
|
313
|
-
assertNoExtras('log', args, ['--json']);
|
|
314
|
-
const wantJson = args.includes('--json');
|
|
315
|
-
let limit = 20;
|
|
316
|
-
if (limitRaw != null) {
|
|
317
|
-
const trimmed = String(limitRaw).trim();
|
|
318
|
-
const parsed = parseInt(trimmed, 10);
|
|
319
|
-
if (!/^[+-]?\d+$/.test(trimmed) || !Number.isFinite(parsed) || parsed < 1) {
|
|
320
|
-
console.error(`Invalid --limit value: "${limitRaw}". Expected a positive integer.`);
|
|
321
|
-
process.exit(1);
|
|
322
|
-
}
|
|
323
|
-
limit = parsed;
|
|
324
|
-
}
|
|
325
|
-
const root = requireBackendRoot();
|
|
326
|
-
let rows = loadCitationAttempts(root);
|
|
327
|
-
if (engine) rows = rows.filter((r) => r.engine.toLowerCase() === engine.toLowerCase());
|
|
328
|
-
rows = rows.slice(0, limit);
|
|
329
|
-
|
|
330
|
-
if (wantJson) {
|
|
331
|
-
console.log(JSON.stringify(rows, null, 2));
|
|
332
|
-
return;
|
|
333
|
-
}
|
|
334
|
-
|
|
335
|
-
if (!rows.length) {
|
|
336
|
-
console.log('No citation attempts found.');
|
|
337
|
-
return;
|
|
338
|
-
}
|
|
339
|
-
|
|
340
|
-
const counts = rows.reduce((acc, r) => { acc[r.status] = (acc[r.status] || 0) + 1; return acc; }, {});
|
|
341
|
-
console.log(`AEO citation log (${rows.length} attempt${rows.length === 1 ? '' : 's'})`);
|
|
342
|
-
console.log(` ${Object.entries(counts).map(([k, v]) => `${k}=${v}`).join(' ')}`);
|
|
343
|
-
console.log('');
|
|
344
|
-
console.log(` ${pad('ts', 22)}${pad('engine', 12)}${pad('prompt_id', 26)}${pad('status', 10)}`);
|
|
345
|
-
console.log(` ${'-'.repeat(70)}`);
|
|
346
|
-
for (const r of rows) {
|
|
347
|
-
console.log(` ${pad(r.attempted_at.slice(0, 19), 22)}${pad(r.engine, 12)}${pad(r.prompt_id, 26)}${pad(r.status, 10)}`);
|
|
348
|
-
}
|
|
349
|
-
}
|
|
350
|
-
|
|
351
|
-
async function aeoStatus(args) {
|
|
352
|
-
assertNoExtras('status', args, ['--json']);
|
|
353
|
-
const wantJson = args.includes('--json');
|
|
354
|
-
const root = requireBackendRoot();
|
|
355
|
-
const proofRoot = path.join(root, 'atris', 'features', 'aeo', 'proof');
|
|
356
|
-
|
|
357
|
-
const attempts = loadCitationAttempts(root);
|
|
358
|
-
const enginesSeen = new Set(attempts.map((a) => a.engine).filter(Boolean));
|
|
359
|
-
const verified = attempts.filter((a) => a.status === 'verified').length;
|
|
360
|
-
const pending = attempts.filter((a) => a.status === 'pending').length;
|
|
361
|
-
const failed = attempts.filter((a) => a.status === 'failed').length;
|
|
362
|
-
|
|
363
|
-
const packets = [];
|
|
364
|
-
if (fs.existsSync(proofRoot)) {
|
|
365
|
-
for (const entry of fs.readdirSync(proofRoot)) {
|
|
366
|
-
if (!entry.endsWith('-buyer-packet')) continue;
|
|
367
|
-
const p = path.join(proofRoot, entry, 'packet.json');
|
|
368
|
-
const data = readJsonSafe(p);
|
|
369
|
-
if (!data) continue;
|
|
370
|
-
packets.push({
|
|
371
|
-
slug: entry.replace(/-buyer-packet$/, ''),
|
|
372
|
-
surface: data.surface || entry,
|
|
373
|
-
target_url: data.target_url || '',
|
|
374
|
-
baseline: data?.agent_usability?.baseline_score ?? null,
|
|
375
|
-
proposed: data?.agent_usability?.proposed_score ?? null,
|
|
376
|
-
claim_status: data.claim_status || '',
|
|
377
|
-
});
|
|
378
|
-
}
|
|
379
|
-
}
|
|
380
|
-
|
|
381
|
-
const operator = readJsonSafe(path.join(proofRoot, 'live-citation-operator', 'live-citation-operator.json'));
|
|
382
|
-
|
|
383
|
-
const proofDirs = fs.existsSync(proofRoot)
|
|
384
|
-
? fs.readdirSync(proofRoot).filter((e) => fs.statSync(path.join(proofRoot, e)).isDirectory()).length
|
|
385
|
-
: 0;
|
|
386
|
-
|
|
387
|
-
if (wantJson) {
|
|
388
|
-
console.log(JSON.stringify({
|
|
389
|
-
backend_root: root,
|
|
390
|
-
proof_dirs: proofDirs,
|
|
391
|
-
citation: { total: attempts.length, verified, pending, failed, engines: [...enginesSeen] },
|
|
392
|
-
packets,
|
|
393
|
-
operator_status: operator?.status || null,
|
|
394
|
-
operator_blocker: operator?.current_blocker || null,
|
|
395
|
-
}, null, 2));
|
|
396
|
-
return;
|
|
397
|
-
}
|
|
398
|
-
|
|
399
|
-
console.log('Atris AEO status');
|
|
400
|
-
console.log(` backend root: ${root}`);
|
|
401
|
-
console.log(` proof receipts: ${proofDirs} categories`);
|
|
402
|
-
console.log('');
|
|
403
|
-
console.log('Live citations');
|
|
404
|
-
console.log(` attempts: ${attempts.length}`);
|
|
405
|
-
console.log(` verified: ${verified}`);
|
|
406
|
-
console.log(` pending: ${pending}`);
|
|
407
|
-
console.log(` failed: ${failed}`);
|
|
408
|
-
console.log(` engines observed: ${[...enginesSeen].join(', ') || '(none)'}`);
|
|
409
|
-
if (operator) {
|
|
410
|
-
console.log(` operator state: ${operator.status || '?'} (blocker: ${operator.current_blocker || 'none'})`);
|
|
411
|
-
}
|
|
412
|
-
console.log('');
|
|
413
|
-
console.log(`Buyer packets (${packets.length})`);
|
|
414
|
-
for (const p of packets) {
|
|
415
|
-
const delta = p.baseline != null && p.proposed != null ? `${p.baseline} → ${p.proposed}` : '?';
|
|
416
|
-
console.log(` ${pad(p.slug, 16)} ${pad(p.target_url || p.surface, 36)} ${pad(delta, 12)} ${p.claim_status}`);
|
|
417
|
-
}
|
|
418
|
-
}
|
|
419
|
-
|
|
420
|
-
async function aeoPacket(args) {
|
|
421
|
-
const known = new Set(['--json']);
|
|
422
|
-
const positional = [];
|
|
423
|
-
for (const a of args) {
|
|
424
|
-
if (a.startsWith('--')) {
|
|
425
|
-
if (!known.has(a)) {
|
|
426
|
-
console.error(`Unknown flag: ${a}. Supported: --json`);
|
|
427
|
-
process.exit(1);
|
|
428
|
-
}
|
|
429
|
-
} else {
|
|
430
|
-
positional.push(a);
|
|
431
|
-
}
|
|
432
|
-
}
|
|
433
|
-
if (positional.length === 0) {
|
|
434
|
-
console.error('Missing slug. Usage: atris aeo packet <slug>');
|
|
435
|
-
process.exit(1);
|
|
436
|
-
}
|
|
437
|
-
if (positional.length > 1) {
|
|
438
|
-
console.error(`Too many arguments: ${positional.join(' ')}. Expected one slug.`);
|
|
439
|
-
process.exit(1);
|
|
440
|
-
}
|
|
441
|
-
const slug = positional[0];
|
|
442
|
-
const wantJson = args.includes('--json');
|
|
443
|
-
const root = requireBackendRoot();
|
|
444
|
-
const file = path.join(root, 'atris', 'features', 'aeo', 'proof', `${slug}-buyer-packet`, 'packet.json');
|
|
445
|
-
const data = readJsonSafe(file);
|
|
446
|
-
if (!data) {
|
|
447
|
-
console.error(`Packet not found: ${file}`);
|
|
448
|
-
process.exit(1);
|
|
449
|
-
}
|
|
450
|
-
|
|
451
|
-
if (wantJson) {
|
|
452
|
-
console.log(JSON.stringify(data, null, 2));
|
|
453
|
-
return;
|
|
454
|
-
}
|
|
455
|
-
|
|
456
|
-
const u = (data && typeof data.agent_usability === 'object' && data.agent_usability) || {};
|
|
457
|
-
const onlyObjects = (v) => (Array.isArray(v) ? v.filter((x) => x && typeof x === 'object') : []);
|
|
458
|
-
const friction = onlyObjects(u.baseline_friction_points);
|
|
459
|
-
const fixes = onlyObjects(u.fix_backlog);
|
|
460
|
-
console.log(`AEO buyer packet — ${slug}`);
|
|
461
|
-
console.log(` surface: ${data.surface || ''}`);
|
|
462
|
-
console.log(` target url: ${data.target_url || ''}`);
|
|
463
|
-
console.log(` claim status: ${data.claim_status || ''}`);
|
|
464
|
-
console.log(` positioning: ${u.positioning || ''}`);
|
|
465
|
-
console.log('');
|
|
466
|
-
console.log('Agent usability scores');
|
|
467
|
-
console.log(` baseline: ${u.baseline_score ?? '?'}`);
|
|
468
|
-
console.log(` proposed: ${u.proposed_score ?? '?'}`);
|
|
469
|
-
console.log(` delta: ${u.score_delta ?? '?'}`);
|
|
470
|
-
console.log(` verified: ${u.movement_verified ? 'yes' : 'no'}`);
|
|
471
|
-
console.log('');
|
|
472
|
-
console.log(`Baseline friction (${friction.length})`);
|
|
473
|
-
for (const f of friction) {
|
|
474
|
-
console.log(` [${f.severity || '?'}] ${f.stage || '?'}: ${f.missing_artifact || f.id || ''}`);
|
|
475
|
-
}
|
|
476
|
-
console.log('');
|
|
477
|
-
console.log(`Fix backlog (${fixes.length})`);
|
|
478
|
-
for (const f of fixes) {
|
|
479
|
-
console.log(` #${f.priority ?? '?'} ${f.stage || '?'}: ${f.action || ''}`);
|
|
480
|
-
}
|
|
481
|
-
}
|
|
482
|
-
|
|
483
|
-
async function aeoProofs(args) {
|
|
484
|
-
const filter = readArg(args, '--filter', '-f');
|
|
485
|
-
assertNoExtras('proofs', args, []);
|
|
486
|
-
const root = requireBackendRoot();
|
|
487
|
-
const proofRoot = path.join(root, 'atris', 'features', 'aeo', 'proof');
|
|
488
|
-
if (!fs.existsSync(proofRoot)) {
|
|
489
|
-
console.error(`Proof root not found: ${proofRoot}`);
|
|
490
|
-
process.exit(1);
|
|
491
|
-
}
|
|
492
|
-
const needle = filter ? filter.toLowerCase() : null;
|
|
493
|
-
const entries = fs.readdirSync(proofRoot)
|
|
494
|
-
.filter((e) => fs.statSync(path.join(proofRoot, e)).isDirectory())
|
|
495
|
-
.filter((e) => !needle || e.toLowerCase().includes(needle))
|
|
496
|
-
.sort();
|
|
497
|
-
console.log(`AEO proof receipts at ${path.relative(process.cwd(), proofRoot)}`);
|
|
498
|
-
console.log('');
|
|
499
|
-
for (const e of entries) {
|
|
500
|
-
const files = fs.readdirSync(path.join(proofRoot, e)).filter((f) => f.endsWith('.json'));
|
|
501
|
-
console.log(` ${pad(e, 44)} ${files.length} file${files.length === 1 ? '' : 's'}`);
|
|
502
|
-
}
|
|
503
|
-
}
|
|
504
|
-
|
|
505
|
-
// ---------- SCRIPT WRAPPERS ----------
|
|
506
|
-
|
|
507
|
-
function runBackendScript(scriptName, args) {
|
|
508
|
-
const root = requireBackendRoot();
|
|
509
|
-
const script = path.join(root, 'scripts', scriptName);
|
|
510
|
-
if (!fs.existsSync(script)) {
|
|
511
|
-
console.error(`Script not found: ${script}`);
|
|
512
|
-
process.exit(1);
|
|
513
|
-
}
|
|
514
|
-
const py = process.env.ATRIS_PYTHON || 'python3';
|
|
515
|
-
const result = spawnSync(py, [script, ...args], { cwd: root, stdio: 'inherit' });
|
|
516
|
-
if (result.error) {
|
|
517
|
-
console.error(`Failed to spawn ${py}: ${result.error.message}`);
|
|
518
|
-
process.exit(1);
|
|
519
|
-
}
|
|
520
|
-
if (result.signal) {
|
|
521
|
-
const signum = os.constants?.signals?.[result.signal] ?? 0;
|
|
522
|
-
console.error(`${scriptName} terminated by signal ${result.signal}`);
|
|
523
|
-
process.exit(128 + signum);
|
|
524
|
-
}
|
|
525
|
-
process.exit(result.status ?? 1);
|
|
526
|
-
}
|
|
527
|
-
|
|
528
|
-
async function aeoDiscover(args) {
|
|
529
|
-
if (!args.length || args[0] === '--help' || args[0] === '-h') {
|
|
530
|
-
console.log('Usage: atris aeo discover <source> [--question Q]... [--canonical-url URL] [--out-dir DIR] [--json]');
|
|
531
|
-
return;
|
|
532
|
-
}
|
|
533
|
-
runBackendScript('aeo_discovery_audit.py', args);
|
|
534
|
-
}
|
|
535
|
-
|
|
536
|
-
async function aeoAudit(args) {
|
|
537
|
-
if (!args.length || args[0] === '--help' || args[0] === '-h') {
|
|
538
|
-
console.log('Usage: atris aeo audit <source> [--baseline B] [--canonical-url URL] [--out-dir DIR] [--task T]');
|
|
539
|
-
return;
|
|
540
|
-
}
|
|
541
|
-
runBackendScript('aeo_agent_usability_audit.py', args);
|
|
542
|
-
}
|
|
543
|
-
|
|
544
186
|
async function run(args = []) {
|
|
545
187
|
const sub = args[0];
|
|
546
188
|
if (!sub || sub === 'help' || sub === '--help' || sub === '-h') return printHelp();
|
|
547
189
|
const rest = args.slice(1);
|
|
548
190
|
if (sub === 'init') return aeoInit(rest);
|
|
549
191
|
if (sub === 'draft') return aeoDraft(rest);
|
|
550
|
-
if (sub === 'log') return aeoLog(rest);
|
|
551
|
-
if (sub === 'status') return aeoStatus(rest);
|
|
552
|
-
if (sub === 'packet') return aeoPacket(rest);
|
|
553
|
-
if (sub === 'proofs') return aeoProofs(rest);
|
|
554
|
-
if (sub === 'discover') return aeoDiscover(rest);
|
|
555
|
-
if (sub === 'audit') return aeoAudit(rest);
|
|
556
192
|
console.error(`Unknown aeo subcommand: ${sub}`);
|
|
557
193
|
printHelp();
|
|
558
194
|
process.exit(1);
|
package/commands/computer.js
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
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)
|
|
8
9
|
* atris computer card — Show the local computer card
|
|
@@ -19,7 +20,7 @@ const path = require('path');
|
|
|
19
20
|
const readline = require('readline');
|
|
20
21
|
const { spawnSync } = require('child_process');
|
|
21
22
|
const { loadCredentials, decodeJwtClaims } = require('../utils/auth');
|
|
22
|
-
const { apiRequestJson, getApiBaseUrl } = require('../utils/api');
|
|
23
|
+
const { apiRequestJson, getApiBaseUrl, getAppBaseUrl } = require('../utils/api');
|
|
23
24
|
const { loadBusinesses, saveBusinesses } = require('./business');
|
|
24
25
|
const { consoleCommand, gatherAtrisContext, buildSystemPrompt } = require('./console');
|
|
25
26
|
const { streamSession } = require('./serve');
|
|
@@ -544,6 +545,36 @@ function parseComputerOptions(argv) {
|
|
|
544
545
|
};
|
|
545
546
|
}
|
|
546
547
|
|
|
548
|
+
function parseComputerCreateArgs(argv = []) {
|
|
549
|
+
const nameParts = [];
|
|
550
|
+
let businessSlug = null;
|
|
551
|
+
let help = false;
|
|
552
|
+
|
|
553
|
+
for (let i = 0; i < argv.length; i++) {
|
|
554
|
+
const arg = argv[i];
|
|
555
|
+
if (arg === '--help' || arg === '-h' || arg === 'help') {
|
|
556
|
+
help = true;
|
|
557
|
+
continue;
|
|
558
|
+
}
|
|
559
|
+
if ((arg === '--business' || arg === '-b') && argv[i + 1]) {
|
|
560
|
+
businessSlug = argv[i + 1];
|
|
561
|
+
i++;
|
|
562
|
+
continue;
|
|
563
|
+
}
|
|
564
|
+
if (arg.startsWith('--business=')) {
|
|
565
|
+
businessSlug = arg.split('=', 2)[1] || null;
|
|
566
|
+
continue;
|
|
567
|
+
}
|
|
568
|
+
nameParts.push(arg);
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
return {
|
|
572
|
+
name: nameParts.join(' ').trim(),
|
|
573
|
+
businessSlug: businessSlug ? String(businessSlug).trim() : null,
|
|
574
|
+
help,
|
|
575
|
+
};
|
|
576
|
+
}
|
|
577
|
+
|
|
547
578
|
function formatCloudSelection(options = {}) {
|
|
548
579
|
const worker = activeWorker(options.worker);
|
|
549
580
|
const parts = [`worker=${worker}`];
|
|
@@ -953,6 +984,55 @@ async function resolveBusinessContextBySlug(token, slug) {
|
|
|
953
984
|
return null;
|
|
954
985
|
}
|
|
955
986
|
|
|
987
|
+
async function resolveBusinessOwnerForCreate(token, businessSlug = null) {
|
|
988
|
+
const wantedSlug = businessSlug ? String(businessSlug).trim() : null;
|
|
989
|
+
if (wantedSlug) {
|
|
990
|
+
const fromApi = await resolveBusinessContextBySlug(token, wantedSlug);
|
|
991
|
+
if (fromApi) return fromApi;
|
|
992
|
+
|
|
993
|
+
const cached = loadBusinesses()[wantedSlug];
|
|
994
|
+
if (cached?.business_id) {
|
|
995
|
+
return {
|
|
996
|
+
slug: cached.slug || wantedSlug,
|
|
997
|
+
businessId: cached.business_id,
|
|
998
|
+
workspaceId: cached.workspace_id || null,
|
|
999
|
+
businessName: cached.name || cached.slug || wantedSlug,
|
|
1000
|
+
};
|
|
1001
|
+
}
|
|
1002
|
+
return null;
|
|
1003
|
+
}
|
|
1004
|
+
|
|
1005
|
+
const binding = readBusinessBinding();
|
|
1006
|
+
if (binding?.business_id) {
|
|
1007
|
+
return {
|
|
1008
|
+
slug: binding.slug || binding.canonical_slug || null,
|
|
1009
|
+
businessId: binding.business_id,
|
|
1010
|
+
workspaceId: binding.workspace_id || null,
|
|
1011
|
+
businessName: binding.name || binding.slug || 'business',
|
|
1012
|
+
};
|
|
1013
|
+
}
|
|
1014
|
+
|
|
1015
|
+
return resolveBusinessContext(token);
|
|
1016
|
+
}
|
|
1017
|
+
|
|
1018
|
+
function rememberCreatedComputer(ctx, workspace, endpoint = null) {
|
|
1019
|
+
const slug = ctx.slug || (ctx.businessName || '').toLowerCase().replace(/\s+/g, '-');
|
|
1020
|
+
if (!slug) return;
|
|
1021
|
+
const businesses = loadBusinesses();
|
|
1022
|
+
businesses[slug] = {
|
|
1023
|
+
...(businesses[slug] || {}),
|
|
1024
|
+
business_id: ctx.businessId,
|
|
1025
|
+
workspace_id: workspace.id,
|
|
1026
|
+
name: ctx.businessName,
|
|
1027
|
+
slug,
|
|
1028
|
+
computer_name: workspace.name,
|
|
1029
|
+
endpoint: endpoint || undefined,
|
|
1030
|
+
added_at: businesses[slug]?.added_at || new Date().toISOString(),
|
|
1031
|
+
updated_at: new Date().toISOString(),
|
|
1032
|
+
};
|
|
1033
|
+
saveBusinesses(businesses);
|
|
1034
|
+
}
|
|
1035
|
+
|
|
956
1036
|
function shellQuote(value) {
|
|
957
1037
|
return `'${String(value).replace(/'/g, `'\\''`)}'`;
|
|
958
1038
|
}
|
|
@@ -1224,6 +1304,90 @@ async function computerWake(token, ctx = null) {
|
|
|
1224
1304
|
console.log(` Endpoint: ${result.data.endpoint}`);
|
|
1225
1305
|
}
|
|
1226
1306
|
|
|
1307
|
+
async function computerCreate(token, args = []) {
|
|
1308
|
+
const options = parseComputerCreateArgs(args);
|
|
1309
|
+
if (options.help || !options.name) {
|
|
1310
|
+
console.log('Usage: atris computer create <name> --business <slug>');
|
|
1311
|
+
console.log('');
|
|
1312
|
+
console.log('Create a business computer, activate it, and wake it in one command.');
|
|
1313
|
+
console.log('');
|
|
1314
|
+
console.log('Examples:');
|
|
1315
|
+
console.log(' atris computer create "My Business Computer" --business atris-labs');
|
|
1316
|
+
console.log(' atris computer create "Recruiting Computer"');
|
|
1317
|
+
if (!options.name && !options.help) process.exitCode = 1;
|
|
1318
|
+
return;
|
|
1319
|
+
}
|
|
1320
|
+
|
|
1321
|
+
const ctx = await resolveBusinessOwnerForCreate(token, options.businessSlug);
|
|
1322
|
+
if (!ctx?.businessId) {
|
|
1323
|
+
console.error('No business found.');
|
|
1324
|
+
console.error('Run inside a bound business workspace or pass: --business <slug>');
|
|
1325
|
+
process.exitCode = 1;
|
|
1326
|
+
return;
|
|
1327
|
+
}
|
|
1328
|
+
|
|
1329
|
+
console.log(`Creating computer "${options.name}" for ${ctx.businessName}...`);
|
|
1330
|
+
const created = await apiRequestJson(`/business/${ctx.businessId}/workspaces`, {
|
|
1331
|
+
method: 'POST',
|
|
1332
|
+
token,
|
|
1333
|
+
body: { name: options.name, type: 'general' },
|
|
1334
|
+
});
|
|
1335
|
+
if (!created.ok) {
|
|
1336
|
+
console.error(`Failed to create workspace: ${created.errorMessage || created.error || created.status}`);
|
|
1337
|
+
process.exitCode = 1;
|
|
1338
|
+
return;
|
|
1339
|
+
}
|
|
1340
|
+
|
|
1341
|
+
const workspace = created.data || {};
|
|
1342
|
+
const workspaceId = workspace.id || workspace.workspace_id;
|
|
1343
|
+
if (!workspaceId) {
|
|
1344
|
+
console.error('Failed to create workspace: response did not include workspace id');
|
|
1345
|
+
process.exitCode = 1;
|
|
1346
|
+
return;
|
|
1347
|
+
}
|
|
1348
|
+
|
|
1349
|
+
const activate = await apiRequestJson(`/business/${ctx.businessId}/workspaces/${workspaceId}/activate`, {
|
|
1350
|
+
method: 'POST',
|
|
1351
|
+
token,
|
|
1352
|
+
body: {},
|
|
1353
|
+
});
|
|
1354
|
+
if (!activate.ok && activate.status !== 409) {
|
|
1355
|
+
console.error(`Failed to activate computer: ${activate.errorMessage || activate.error || activate.status}`);
|
|
1356
|
+
process.exitCode = 1;
|
|
1357
|
+
return;
|
|
1358
|
+
}
|
|
1359
|
+
|
|
1360
|
+
const wake = await apiRequestJson(`/business/${ctx.businessId}/ai-computer/wake`, {
|
|
1361
|
+
method: 'POST',
|
|
1362
|
+
token,
|
|
1363
|
+
body: {},
|
|
1364
|
+
});
|
|
1365
|
+
if (!wake.ok && !activate.ok) {
|
|
1366
|
+
console.error(`Failed to wake computer: ${wake.errorMessage || wake.error || wake.status}`);
|
|
1367
|
+
process.exitCode = 1;
|
|
1368
|
+
return;
|
|
1369
|
+
}
|
|
1370
|
+
|
|
1371
|
+
const endpoint = activate.data?.endpoint || wake.data?.endpoint || null;
|
|
1372
|
+
const status = endpoint
|
|
1373
|
+
? 'running'
|
|
1374
|
+
: (wake.data?.status || (activate.ok ? 'activated' : 'warming_up'));
|
|
1375
|
+
rememberCreatedComputer(ctx, { ...workspace, id: workspaceId, name: workspace.name || options.name }, endpoint);
|
|
1376
|
+
|
|
1377
|
+
const appBase = getAppBaseUrl();
|
|
1378
|
+
console.log('');
|
|
1379
|
+
console.log(`Computer created: ${workspaceId}`);
|
|
1380
|
+
console.log(` Name: ${workspace.name || options.name}`);
|
|
1381
|
+
console.log(` Business: ${ctx.businessName}`);
|
|
1382
|
+
console.log(` Status: ${status}`);
|
|
1383
|
+
if (endpoint) console.log(` Endpoint: ${endpoint}`);
|
|
1384
|
+
console.log(` Dashboard: ${appBase}/dashboard/gm/${ctx.businessId}`);
|
|
1385
|
+
console.log('');
|
|
1386
|
+
console.log('Next:');
|
|
1387
|
+
console.log(` atris pull ${ctx.slug || ctx.businessId}`);
|
|
1388
|
+
console.log(' atris computer');
|
|
1389
|
+
}
|
|
1390
|
+
|
|
1227
1391
|
async function computerSleep(token, ctx = null) {
|
|
1228
1392
|
if (ctx) {
|
|
1229
1393
|
console.log(`Sleeping computer for ${ctx.businessName}...`);
|
|
@@ -2562,6 +2726,14 @@ async function runComputer() {
|
|
|
2562
2726
|
return;
|
|
2563
2727
|
}
|
|
2564
2728
|
|
|
2729
|
+
if (sub === 'create') {
|
|
2730
|
+
const createOptions = parseComputerCreateArgs(args.slice(1));
|
|
2731
|
+
if (createOptions.help || !createOptions.name) {
|
|
2732
|
+
await computerCreate(null, args.slice(1));
|
|
2733
|
+
return;
|
|
2734
|
+
}
|
|
2735
|
+
}
|
|
2736
|
+
|
|
2565
2737
|
if (sub === '--help') {
|
|
2566
2738
|
console.log('Usage: atris computer [mode|command]');
|
|
2567
2739
|
console.log('');
|
|
@@ -2593,6 +2765,7 @@ async function runComputer() {
|
|
|
2593
2765
|
console.log(' claude|codex Legacy local console backends');
|
|
2594
2766
|
console.log('');
|
|
2595
2767
|
console.log('Cloud commands:');
|
|
2768
|
+
console.log(' create <name> Create and wake a business computer');
|
|
2596
2769
|
console.log(' chat Interactive cloud workspace chat');
|
|
2597
2770
|
console.log(' Ctrl-C during a cloud run interrupts it');
|
|
2598
2771
|
console.log(' /start shows the beginner flow');
|
|
@@ -2614,6 +2787,7 @@ async function runComputer() {
|
|
|
2614
2787
|
console.log('Examples:');
|
|
2615
2788
|
console.log(' atris computer');
|
|
2616
2789
|
console.log(' atris computer card --write');
|
|
2790
|
+
console.log(' atris computer create "My Business Computer" --business atris-labs');
|
|
2617
2791
|
console.log(' atris business init "My Lab" # shared owner + first/default computer');
|
|
2618
2792
|
console.log(' atris computer proof');
|
|
2619
2793
|
console.log(' atris computer local');
|
|
@@ -2695,6 +2869,7 @@ async function runComputer() {
|
|
|
2695
2869
|
switch (sub) {
|
|
2696
2870
|
case 'chat': return computerChat(token, ctx, cloudOptions);
|
|
2697
2871
|
case 'card': return computerCard(args.slice(1));
|
|
2872
|
+
case 'create': return computerCreate(token, args.slice(1));
|
|
2698
2873
|
case 'proof': return computerProof(token, ctx, cloudOptions);
|
|
2699
2874
|
case 'status': return computerStatus(token, ctx);
|
|
2700
2875
|
case 'wake': return computerWake(token, ctx);
|