atris 3.15.23 → 3.15.30
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/README.md +53 -0
- package/ax +1083 -0
- package/commands/aeo.js +377 -13
- package/commands/gm.js +25 -9
- package/commands/play.js +41 -9
- package/commands/sync.js +9 -4
- package/commands/xp.js +224 -1
- package/package.json +5 -2
package/commands/aeo.js
CHANGED
|
@@ -1,24 +1,98 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* atris aeo — AI Engine Optimization commands
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
* atris aeo
|
|
4
|
+
* Backend-routed (require EC2):
|
|
5
|
+
* atris aeo init # create entity-graph skeleton
|
|
6
|
+
* atris aeo draft "<topic>" [opts] # generate citation-optimized article
|
|
6
7
|
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
8
|
+
* Local-read against ~/arena/atrisos-backend/atris/features/aeo/proof/:
|
|
9
|
+
* atris aeo log [--engine X] [--limit N] # citation attempt log
|
|
10
|
+
* atris aeo status # engine + proof + buyer summary
|
|
11
|
+
* atris aeo packet <slug> # buyer packet for a surface
|
|
12
|
+
* atris aeo proofs [--filter X] # list proof receipt categories
|
|
10
13
|
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
+
* Shell out to atrisos-backend/scripts/aeo_*.py:
|
|
15
|
+
* atris aeo discover <source> [...] # discovery audit
|
|
16
|
+
* atris aeo audit <source> [...] # agent-usability audit
|
|
17
|
+
*
|
|
18
|
+
* Backend root resolution: $ATRIS_BACKEND_ROOT or ~/arena/atrisos-backend.
|
|
14
19
|
*/
|
|
15
20
|
|
|
16
21
|
const fs = require('fs');
|
|
22
|
+
const os = require('os');
|
|
17
23
|
const path = require('path');
|
|
24
|
+
const { spawnSync } = require('child_process');
|
|
18
25
|
const { loadCredentials } = require('../utils/auth');
|
|
19
26
|
const { apiRequestJson } = require('../utils/api');
|
|
20
27
|
const { loadBusinesses, saveBusinesses } = require('./business');
|
|
21
28
|
|
|
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
|
+
|
|
22
96
|
function sleep(ms) { return new Promise((r) => setTimeout(r, ms)); }
|
|
23
97
|
|
|
24
98
|
async function ensureAwake(token, businessId, maxWaitSec = 90) {
|
|
@@ -83,13 +157,25 @@ function pickSlug(args) {
|
|
|
83
157
|
|
|
84
158
|
function printHelp() {
|
|
85
159
|
console.log('Usage:');
|
|
86
|
-
console.log('
|
|
87
|
-
console.log('
|
|
160
|
+
console.log(' Backend / EC2:');
|
|
161
|
+
console.log(' atris aeo init [--workspace <slug>]');
|
|
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]');
|
|
88
173
|
console.log('');
|
|
89
174
|
console.log('Examples:');
|
|
90
|
-
console.log(' atris aeo
|
|
91
|
-
console.log(' atris aeo
|
|
92
|
-
console.log(' atris aeo
|
|
175
|
+
console.log(' atris aeo log --engine perplexity --limit 5');
|
|
176
|
+
console.log(' atris aeo packet pallet');
|
|
177
|
+
console.log(' atris aeo status');
|
|
178
|
+
console.log(' atris aeo discover https://atris.ai/aeo --canonical-url https://atris.ai/aeo');
|
|
93
179
|
}
|
|
94
180
|
|
|
95
181
|
async function aeoInit(args) {
|
|
@@ -183,12 +269,290 @@ async function aeoDraft(args) {
|
|
|
183
269
|
if (data.hint) console.log(` hint: ${data.hint}`);
|
|
184
270
|
}
|
|
185
271
|
|
|
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
|
+
|
|
186
544
|
async function run(args = []) {
|
|
187
545
|
const sub = args[0];
|
|
188
546
|
if (!sub || sub === 'help' || sub === '--help' || sub === '-h') return printHelp();
|
|
189
547
|
const rest = args.slice(1);
|
|
190
548
|
if (sub === 'init') return aeoInit(rest);
|
|
191
549
|
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);
|
|
192
556
|
console.error(`Unknown aeo subcommand: ${sub}`);
|
|
193
557
|
printHelp();
|
|
194
558
|
process.exit(1);
|
package/commands/gm.js
CHANGED
|
@@ -4,6 +4,8 @@ const fs = require('fs');
|
|
|
4
4
|
const os = require('os');
|
|
5
5
|
const path = require('path');
|
|
6
6
|
|
|
7
|
+
const AGENTXP_LEADERBOARD_URL = 'https://api.atris.ai/api/agentxp/leaderboard';
|
|
8
|
+
|
|
7
9
|
const ROLE_PLAYERS_TO_IGNORE = new Set([
|
|
8
10
|
'game-manager',
|
|
9
11
|
'navigator',
|
|
@@ -26,7 +28,7 @@ function showHelp() {
|
|
|
26
28
|
console.log('');
|
|
27
29
|
console.log('Options:');
|
|
28
30
|
console.log(' --manager <id> Manager id. Defaults to game-manager when present.');
|
|
29
|
-
console.log(' --as <id> Alias for --
|
|
31
|
+
console.log(' --as <id> Alias for --player.');
|
|
30
32
|
console.log(' --player <id> Preferred player when seeding a first local mission.');
|
|
31
33
|
console.log(' --workspace <p> Read missions from another Atris workspace.');
|
|
32
34
|
console.log(' --no-seed Do not create a starter player mission.');
|
|
@@ -102,7 +104,7 @@ function teamMembers(workspaceRoot) {
|
|
|
102
104
|
}
|
|
103
105
|
|
|
104
106
|
function inferManager(workspaceRoot, args = []) {
|
|
105
|
-
const explicit = flag(args, '--manager') ||
|
|
107
|
+
const explicit = flag(args, '--manager') || positional(args)[0];
|
|
106
108
|
if (explicit) return { manager: slugify(explicit), source: 'flag' };
|
|
107
109
|
|
|
108
110
|
for (const value of [process.env.ATRIS_GM, process.env.ATRIS_MANAGER, process.env.ATRIS_AGENT_ID]) {
|
|
@@ -160,7 +162,7 @@ function starterMissionPrompt(player) {
|
|
|
160
162
|
}
|
|
161
163
|
|
|
162
164
|
function pickSeedPlayer(workspaceRoot, tasks, args = []) {
|
|
163
|
-
const explicit = flag(args, '--player') || flag(args, '--user');
|
|
165
|
+
const explicit = flag(args, '--player') || flag(args, '--user') || flag(args, '--as');
|
|
164
166
|
if (explicit) return slugify(explicit);
|
|
165
167
|
|
|
166
168
|
const fromTasks = playersFromTasks(tasks);
|
|
@@ -261,19 +263,29 @@ function compactTask(task) {
|
|
|
261
263
|
};
|
|
262
264
|
}
|
|
263
265
|
|
|
264
|
-
function
|
|
266
|
+
function globalSyncCommands(player) {
|
|
267
|
+
return [
|
|
268
|
+
'atris login',
|
|
269
|
+
`atris xp sync --local --as ${player}`,
|
|
270
|
+
];
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
function nextCommands({ seeded, reviewQueue, missions, players, manager }) {
|
|
265
274
|
if (reviewQueue.length) {
|
|
266
|
-
const
|
|
275
|
+
const task = reviewQueue[0];
|
|
276
|
+
const ref = task.ref;
|
|
277
|
+
const player = task.assigned_to || task.claimed_by || players[0]?.player || 'player';
|
|
267
278
|
return [
|
|
268
279
|
`atris task show ${ref}`,
|
|
269
|
-
`atris task accept ${ref} --proof "<human review>"`,
|
|
270
|
-
`atris task revise ${ref} --note "<what must change>"`,
|
|
280
|
+
`atris task accept ${ref} --as ${player} --proof "<human review>"`,
|
|
281
|
+
`atris task revise ${ref} --as ${player} --note "<what must change>"`,
|
|
282
|
+
...globalSyncCommands(player),
|
|
271
283
|
];
|
|
272
284
|
}
|
|
273
285
|
if (missions.length) {
|
|
274
286
|
const mission = missions[0];
|
|
275
287
|
const player = mission.assigned_to || mission.claimed_by || players[0]?.player || 'player';
|
|
276
|
-
if (mission.status === 'open') return [`atris task claim ${mission.ref} --as ${
|
|
288
|
+
if (mission.status === 'open') return [`atris task claim ${mission.ref} --as ${manager || 'game-manager'}`];
|
|
277
289
|
return [`atris play --as ${player}`];
|
|
278
290
|
}
|
|
279
291
|
if (seeded) return [`atris play --as ${seeded.assigned_to || 'player'}`];
|
|
@@ -297,7 +309,7 @@ function gmState(args = []) {
|
|
|
297
309
|
const reviewQueue = missions.filter(task => task.status === 'review');
|
|
298
310
|
const players = groupPlayers(tasks, workspaceRoot);
|
|
299
311
|
const seeded = compactTask(starter.seeded);
|
|
300
|
-
const commands = nextCommands({ seeded, reviewQueue, missions, players });
|
|
312
|
+
const commands = nextCommands({ seeded, reviewQueue, missions, players, manager: detected.manager });
|
|
301
313
|
|
|
302
314
|
return {
|
|
303
315
|
schema: 'atris.agentxp_gm_mode.v1',
|
|
@@ -318,6 +330,8 @@ function gmState(args = []) {
|
|
|
318
330
|
review_queue: reviewQueue,
|
|
319
331
|
next_commands: commands,
|
|
320
332
|
xp_rule: 'GM can route missions and review proof, but AgentXP still lands only after human accept.',
|
|
333
|
+
global_sync_rule: 'Run atris login once before syncing to the hosted AgentXP leaderboard.',
|
|
334
|
+
leaderboard_url: AGENTXP_LEADERBOARD_URL,
|
|
321
335
|
};
|
|
322
336
|
}
|
|
323
337
|
|
|
@@ -351,6 +365,8 @@ function render(state) {
|
|
|
351
365
|
|
|
352
366
|
console.log('');
|
|
353
367
|
console.log('XP rule: no proof, no AgentXP; accept/revise stays human-gated.');
|
|
368
|
+
console.log('Global sync: run atris login once before hosted leaderboard sync.');
|
|
369
|
+
console.log(`Leaderboard: ${state.leaderboard_url}`);
|
|
354
370
|
console.log('');
|
|
355
371
|
console.log('Next commands:');
|
|
356
372
|
for (const command of state.next_commands) console.log(`- ${command}`);
|
package/commands/play.js
CHANGED
|
@@ -6,6 +6,8 @@ const fs = require('fs');
|
|
|
6
6
|
const { spawnSync } = require('child_process');
|
|
7
7
|
const { getSessionProfile, loadCredentials } = require('../utils/auth');
|
|
8
8
|
|
|
9
|
+
const AGENTXP_LEADERBOARD_URL = 'https://api.atris.ai/api/agentxp/leaderboard';
|
|
10
|
+
|
|
9
11
|
function showHelp() {
|
|
10
12
|
console.log('');
|
|
11
13
|
console.log('Usage: atris play [--as <player>] [--workspace <path>] [--json]');
|
|
@@ -252,10 +254,17 @@ function starterMissionPrompt(player) {
|
|
|
252
254
|
].join(' ');
|
|
253
255
|
}
|
|
254
256
|
|
|
257
|
+
function globalSyncCommands(player) {
|
|
258
|
+
return [
|
|
259
|
+
'atris login',
|
|
260
|
+
`atris xp sync --local --as ${player}`,
|
|
261
|
+
];
|
|
262
|
+
}
|
|
263
|
+
|
|
255
264
|
function ensureStarterMission(taskDb, db, workspaceRoot, player, tasks, args = []) {
|
|
256
265
|
if (hasFlag(args, '--no-seed')) return { tasks, seeded: null };
|
|
257
266
|
if (selectMission(tasks, player)) return { tasks, seeded: null };
|
|
258
|
-
|
|
267
|
+
fs.mkdirSync(path.join(workspaceRoot, 'atris'), { recursive: true });
|
|
259
268
|
|
|
260
269
|
const result = taskDb.addTask(db, {
|
|
261
270
|
title: starterMissionTitle(),
|
|
@@ -282,7 +291,21 @@ function ensureStarterMission(taskDb, db, workspaceRoot, player, tasks, args = [
|
|
|
282
291
|
return { tasks: refreshed, seeded };
|
|
283
292
|
}
|
|
284
293
|
|
|
294
|
+
function playWorkspaceRoot(taskDb, workspaceArg) {
|
|
295
|
+
let requested = path.resolve(workspaceArg || process.cwd());
|
|
296
|
+
try { requested = fs.realpathSync(requested); } catch {}
|
|
297
|
+
if (
|
|
298
|
+
fs.existsSync(path.join(requested, '.git'))
|
|
299
|
+
|| fs.existsSync(path.join(requested, 'atris'))
|
|
300
|
+
|| fs.existsSync(path.join(requested, '.atris'))
|
|
301
|
+
) {
|
|
302
|
+
return taskDb.workspaceRoot(requested);
|
|
303
|
+
}
|
|
304
|
+
return requested;
|
|
305
|
+
}
|
|
306
|
+
|
|
285
307
|
function nextCommands(task, player) {
|
|
308
|
+
const helper = 'game-manager';
|
|
286
309
|
if (!task) {
|
|
287
310
|
return [
|
|
288
311
|
`atris task delegate "AgentXP first rep: one proof-backed mission" --to ${player} --tag agent-xp`,
|
|
@@ -293,40 +316,45 @@ function nextCommands(task, player) {
|
|
|
293
316
|
const ref = taskRef(task);
|
|
294
317
|
if (task.status === 'open') {
|
|
295
318
|
return [
|
|
296
|
-
`atris task claim ${ref} --as ${
|
|
297
|
-
`atris task ready ${ref} --proof "<artifact path + verifier result>"`,
|
|
298
|
-
`atris task accept ${ref} --proof "<human review>"`,
|
|
319
|
+
`atris task claim ${ref} --as ${helper}`,
|
|
320
|
+
`atris task ready ${ref} --as ${helper} --proof "<artifact path + verifier result>"`,
|
|
321
|
+
`atris task accept ${ref} --as ${player} --proof "<human review>"`,
|
|
299
322
|
'atris xp card --local',
|
|
323
|
+
...globalSyncCommands(player),
|
|
300
324
|
];
|
|
301
325
|
}
|
|
302
326
|
|
|
303
327
|
if (task.status === 'claimed') {
|
|
328
|
+
const actor = task.claimed_by || helper;
|
|
304
329
|
return [
|
|
305
|
-
`atris task ready ${ref} --proof "<artifact path + verifier result>"`,
|
|
306
|
-
`atris task accept ${ref} --proof "<human review>"`,
|
|
330
|
+
`atris task ready ${ref} --as ${actor} --proof "<artifact path + verifier result>"`,
|
|
331
|
+
`atris task accept ${ref} --as ${player} --proof "<human review>"`,
|
|
307
332
|
'atris xp card --local',
|
|
333
|
+
...globalSyncCommands(player),
|
|
308
334
|
];
|
|
309
335
|
}
|
|
310
336
|
|
|
311
337
|
if (task.status === 'review') {
|
|
312
338
|
return [
|
|
313
339
|
`atris task show ${ref}`,
|
|
314
|
-
`atris task accept ${ref} --proof "<human review>"`,
|
|
315
|
-
`atris task revise ${ref} --note "<what must change>"`,
|
|
340
|
+
`atris task accept ${ref} --as ${player} --proof "<human review>"`,
|
|
341
|
+
`atris task revise ${ref} --as ${player} --note "<what must change>"`,
|
|
316
342
|
'atris xp card --local',
|
|
343
|
+
...globalSyncCommands(player),
|
|
317
344
|
];
|
|
318
345
|
}
|
|
319
346
|
|
|
320
347
|
return [
|
|
321
348
|
`atris task show ${ref}`,
|
|
322
349
|
'atris xp card --local',
|
|
350
|
+
...globalSyncCommands(player),
|
|
323
351
|
];
|
|
324
352
|
}
|
|
325
353
|
|
|
326
354
|
function modeState(args = []) {
|
|
327
355
|
const taskDb = require('../lib/task-db');
|
|
328
356
|
const workspaceArg = flag(args, '--workspace') || flag(args, '--root') || process.cwd();
|
|
329
|
-
const workspaceRoot = taskDb
|
|
357
|
+
const workspaceRoot = playWorkspaceRoot(taskDb, workspaceArg);
|
|
330
358
|
const db = taskDb.open();
|
|
331
359
|
const rows = taskDb.listTasks(db, {
|
|
332
360
|
workspaceRoot,
|
|
@@ -367,6 +395,8 @@ function modeState(args = []) {
|
|
|
367
395
|
prompt: latestMessage(events),
|
|
368
396
|
} : null,
|
|
369
397
|
xp_rule: 'AgentXP lands only after proof is ready and a human accepts the task.',
|
|
398
|
+
global_sync_rule: 'Run atris login once before syncing to the hosted AgentXP leaderboard.',
|
|
399
|
+
leaderboard_url: AGENTXP_LEADERBOARD_URL,
|
|
370
400
|
next_commands: commandList,
|
|
371
401
|
};
|
|
372
402
|
}
|
|
@@ -399,6 +429,8 @@ function render(state) {
|
|
|
399
429
|
console.log('');
|
|
400
430
|
console.log('Win condition: real artifact + verifier + human accept.');
|
|
401
431
|
console.log('XP rule: no proof, no AgentXP; accept/revise stays human-gated.');
|
|
432
|
+
console.log('Global sync: run atris login once before hosted leaderboard sync.');
|
|
433
|
+
console.log(`Leaderboard: ${state.leaderboard_url}`);
|
|
402
434
|
console.log('');
|
|
403
435
|
console.log('Next commands:');
|
|
404
436
|
for (const command of state.next_commands) console.log(`- ${command}`);
|
package/commands/sync.js
CHANGED
|
@@ -48,6 +48,10 @@ function _substituteParams(content, params) {
|
|
|
48
48
|
.replace(/\{\{workspace_template\}\}/g, params.workspace_template || 'business');
|
|
49
49
|
}
|
|
50
50
|
|
|
51
|
+
function _templateTargetRelPath(relPath) {
|
|
52
|
+
return relPath === 'persona.md' ? 'PERSONA.md' : relPath;
|
|
53
|
+
}
|
|
54
|
+
|
|
51
55
|
/**
|
|
52
56
|
* Sync the canonical skill set from atris-cli/atris/skills/* into a
|
|
53
57
|
* workspace's atris/skills/* (plus ensure .claude/skills/ symlinks).
|
|
@@ -212,14 +216,15 @@ function syncWorkspaceTemplate(targetRoot, bizMeta, options = {}) {
|
|
|
212
216
|
const addedList = [], updatedList = [], preservedList = [];
|
|
213
217
|
|
|
214
218
|
for (const relPath of templateFiles) {
|
|
219
|
+
const targetRelPath = _templateTargetRelPath(relPath);
|
|
215
220
|
const templatePath = path.join(template.dir, relPath);
|
|
216
|
-
const targetPath = path.join(targetAtrisDir,
|
|
221
|
+
const targetPath = path.join(targetAtrisDir, targetRelPath);
|
|
217
222
|
let templateContent;
|
|
218
223
|
try { templateContent = fs.readFileSync(templatePath, 'utf-8'); } catch { continue; }
|
|
219
224
|
const finalContent = _substituteParams(templateContent, params);
|
|
220
225
|
|
|
221
226
|
if (!fs.existsSync(targetPath)) {
|
|
222
|
-
addedList.push(
|
|
227
|
+
addedList.push(targetRelPath); added++;
|
|
223
228
|
if (!dryRun) {
|
|
224
229
|
fs.mkdirSync(path.dirname(targetPath), { recursive: true });
|
|
225
230
|
fs.writeFileSync(targetPath, finalContent);
|
|
@@ -229,10 +234,10 @@ function syncWorkspaceTemplate(targetRoot, bizMeta, options = {}) {
|
|
|
229
234
|
if (existing === finalContent) {
|
|
230
235
|
skipped++;
|
|
231
236
|
} else if (force) {
|
|
232
|
-
updatedList.push(
|
|
237
|
+
updatedList.push(targetRelPath); updated++;
|
|
233
238
|
if (!dryRun) fs.writeFileSync(targetPath, finalContent);
|
|
234
239
|
} else {
|
|
235
|
-
preservedList.push(
|
|
240
|
+
preservedList.push(targetRelPath); preserved++;
|
|
236
241
|
}
|
|
237
242
|
}
|
|
238
243
|
}
|