atris 3.13.0 → 3.15.0
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 +28 -3
- package/bin/atris.js +34 -8
- package/commands/aeo.js +197 -0
- package/commands/business.js +393 -6
- package/commands/computer.js +181 -3
- package/commands/live.js +289 -0
- package/commands/pull.js +8 -4
- package/commands/push.js +2 -2
- package/commands/task.js +217 -0
- package/lib/task-db.js +288 -0
- package/lib/todo-fallback.js +142 -0
- package/lib/todo.js +99 -184
- package/package.json +2 -2
- package/cli/__pycache__/atris_code.cpython-314.pyc +0 -0
- package/cli/__pycache__/runtime_guard.cpython-312.pyc +0 -0
- package/cli/__pycache__/runtime_guard.cpython-314.pyc +0 -0
package/README.md
CHANGED
|
@@ -4,6 +4,16 @@
|
|
|
4
4
|
|
|
5
5
|
It turns any repo into an AI workspace with shared context, a `plan -> do -> review` loop, daily logs, feature packs, and reusable skills.
|
|
6
6
|
|
|
7
|
+
Atris gives every owner persistent AI computers.
|
|
8
|
+
|
|
9
|
+
```text
|
|
10
|
+
Owner = User | Business
|
|
11
|
+
Owner has many Computers
|
|
12
|
+
Computer = workspace + files + tools + secrets + memory + agents + validation loop
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
A computer can be personal or shared by a business. It can run a job like code, research, CRM, reporting, recruiting, event ops, support, or business ops.
|
|
16
|
+
|
|
7
17
|
## For Coding Agents
|
|
8
18
|
|
|
9
19
|
If you're an agent, run:
|
|
@@ -19,6 +29,8 @@ Then read the workspace's `atris/atris.md` and follow it exactly. `atris.md` is
|
|
|
19
29
|
## What Atris Gives You
|
|
20
30
|
|
|
21
31
|
- An AI workspace on top of any repo
|
|
32
|
+
- Persistent AI computers for scoped jobs
|
|
33
|
+
- A local computer card that makes each workspace inspectable
|
|
22
34
|
- A strict `plan -> do -> review` loop
|
|
23
35
|
- Daily logs, task tracking, feature packs, and project memory
|
|
24
36
|
- Skills, team members, integrations, and cloud sync when you need them
|
|
@@ -54,6 +66,7 @@ atris --version
|
|
|
54
66
|
```
|
|
55
67
|
|
|
56
68
|
Requires Node.js 18+.
|
|
69
|
+
`atris task` uses built-in SQLite and requires Node.js 22+.
|
|
57
70
|
|
|
58
71
|
If you want Atris cloud workspaces, businesses, or integrations, run `atris setup` after install.
|
|
59
72
|
|
|
@@ -73,9 +86,9 @@ Core loop: `plan` -> `do` -> `review`
|
|
|
73
86
|
|
|
74
87
|
Integrates with any agent.
|
|
75
88
|
|
|
76
|
-
## Business
|
|
89
|
+
## Business Owners
|
|
77
90
|
|
|
78
|
-
If you want a
|
|
91
|
+
If you want a shared owner for a company, lab, collective, community, artist, team, or project, use the business command instead of raw `atris init`.
|
|
79
92
|
|
|
80
93
|
```bash
|
|
81
94
|
atris business init "BLOND:ISH" --owner-email joel@blondish.world
|
|
@@ -84,10 +97,18 @@ atris business onboard --website https://blondish.world --contact "Joel Zimmerma
|
|
|
84
97
|
atris align --fix
|
|
85
98
|
```
|
|
86
99
|
|
|
87
|
-
That creates the
|
|
100
|
+
That creates the shared owner, creates its first/default computer, writes `.atris/business.json`, initializes `.atris/state/` for events and run history, and scaffolds the local `atris/` workspace under `~/arena/atris-business/<slug>/` with starter roles, a default recap template, and an initial task queue in `atris/TODO.md`.
|
|
88
101
|
|
|
89
102
|
If you do not have a neat source pack yet, `atris business onboard` is the easiest intake step: give it a website, a named human, a few notes, or run it in a folder with loose files. Atris turns that into raw intake, a starter brief, a first workflow, a safe next action, and a short operator brief.
|
|
90
103
|
|
|
104
|
+
Use the owner's language when you talk about it:
|
|
105
|
+
|
|
106
|
+
```text
|
|
107
|
+
Your business runs on Atris.
|
|
108
|
+
Your lab runs on Atris.
|
|
109
|
+
Your collective runs on Atris.
|
|
110
|
+
```
|
|
111
|
+
|
|
91
112
|
You can also use bare input:
|
|
92
113
|
|
|
93
114
|
```bash
|
|
@@ -116,12 +137,14 @@ atris business record atris/reports/2026-04-12-operator-recap.md --outcome mixed
|
|
|
116
137
|
| `atris autopilot` | Guided loop with approvals |
|
|
117
138
|
| `atris log` | Add inbox items to today's journal |
|
|
118
139
|
| `atris status` | Show active work and completions |
|
|
140
|
+
| `atris task` | Local agent task plane with atomic claims and TODO import |
|
|
119
141
|
| `atris learn` | Manage structured learnings |
|
|
120
142
|
| `atris ingest` | Stage raw evidence into `atris/context/` and compile into `atris/wiki/` |
|
|
121
143
|
| `atris loop` | Refresh wiki health, stale/orphan signals, and next ingest candidates |
|
|
122
144
|
| `atris wiki` | Full wiki namespace: ingest, query, lint, search, log, and loop |
|
|
123
145
|
| `atris receipt` | Save evidence from an agent run |
|
|
124
146
|
| `atris experiments` | Run small experiments and compare results |
|
|
147
|
+
| `atris computer card` | Show or write the local owner/computer card |
|
|
125
148
|
|
|
126
149
|
## Built-In Systems
|
|
127
150
|
|
|
@@ -131,8 +154,10 @@ atris business record atris/reports/2026-04-12-operator-recap.md --outcome mixed
|
|
|
131
154
|
- `atris wiki --private` stores local-only sensitive notes under `.atris/presidio/`
|
|
132
155
|
- `atris loop` refreshes `atris/wiki/STATUS.md` and `atris/wiki/log.md`, flags stale/orphan pages, and suggests the next ingest
|
|
133
156
|
- `atris activate` loads the current wiki status so the next session starts with project memory, not just tasks
|
|
157
|
+
- `atris task` keeps a local SQLite task plane for agents while `atris/TODO.md` remains the readable project board
|
|
134
158
|
- `atris experiments` runs small test packs in `atris/experiments/`
|
|
135
159
|
- `atris pull` and `atris push` sync cloud workspaces and journals
|
|
160
|
+
- `atris live` keeps a business brain fresh by checking/fixing the workspace, pushing local state, pulling cloud state, and pushing again after local changes go quiet
|
|
136
161
|
|
|
137
162
|
## Verifiable Feedback Loop
|
|
138
163
|
|
package/bin/atris.js
CHANGED
|
@@ -204,19 +204,26 @@ function showHelp() {
|
|
|
204
204
|
console.log('');
|
|
205
205
|
console.log('Quick Start:');
|
|
206
206
|
console.log('');
|
|
207
|
-
console.log(' 1. atris
|
|
208
|
-
console.log(' 2. Describe what you want
|
|
209
|
-
console.log(' 3.
|
|
207
|
+
console.log(' 1. atris Open a persistent AI computer for this workspace');
|
|
208
|
+
console.log(' 2. Describe what you want run, built, researched, or validated');
|
|
209
|
+
console.log(' 3. Atris acts with context, memory, tools, and a review loop');
|
|
210
210
|
console.log('');
|
|
211
211
|
console.log('Common invocations:');
|
|
212
212
|
console.log(' atris init');
|
|
213
|
+
console.log(' atris computer');
|
|
214
|
+
console.log(' atris business init "My Company"');
|
|
213
215
|
console.log(' atris run');
|
|
214
216
|
console.log(' atris status');
|
|
215
217
|
console.log(' atris soul');
|
|
216
|
-
console.log(' atris fleet');
|
|
218
|
+
console.log(' atris fleet status');
|
|
217
219
|
console.log('');
|
|
218
220
|
console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
|
|
219
221
|
console.log('');
|
|
222
|
+
console.log('Atris Computers:');
|
|
223
|
+
console.log(' Owner = User | Business');
|
|
224
|
+
console.log(' Owners have Computers: workspace + files + tools + secrets + memory + agents + validation');
|
|
225
|
+
console.log(' Types: code, research, CRM, reporting, recruiting, events, support, business ops');
|
|
226
|
+
console.log('');
|
|
220
227
|
console.log('Setup:');
|
|
221
228
|
console.log(' setup - Guided first-time setup (login, pick business, pull)');
|
|
222
229
|
console.log(' init - Initialize Atris in current project');
|
|
@@ -237,6 +244,7 @@ function showHelp() {
|
|
|
237
244
|
console.log(' search - Search journal history (atris search <keyword>)');
|
|
238
245
|
console.log(' clean - Housekeeping (stale tasks, archive journals, broken refs)');
|
|
239
246
|
console.log(' verify - Validate work is done (tests, MAP.md, changes)');
|
|
247
|
+
console.log(' task - Local agent task plane (atomic claims, TODO import)');
|
|
240
248
|
console.log(' release - Tag release, bump version, create GitHub release, draft /launch');
|
|
241
249
|
console.log(' learn - Project learnings (patterns, pitfalls, preferences)');
|
|
242
250
|
console.log(' ingest - Local-first wiki ingest into atris/wiki/');
|
|
@@ -262,6 +270,7 @@ function showHelp() {
|
|
|
262
270
|
console.log('Sync:');
|
|
263
271
|
console.log(' pull - Pull journals + member data from cloud');
|
|
264
272
|
console.log(' push - Push workspace files to cloud');
|
|
273
|
+
console.log(' live - Keep a business brain fresh (doctor, pull, watch, push)');
|
|
265
274
|
console.log(' clean-workspace <slug> - Analyze & remove junk files from a workspace (alias: cw)');
|
|
266
275
|
console.log('');
|
|
267
276
|
console.log('GitHub for Context:');
|
|
@@ -272,7 +281,7 @@ function showHelp() {
|
|
|
272
281
|
console.log(' wake [business] - Resume workspace (agents restart)');
|
|
273
282
|
console.log('');
|
|
274
283
|
console.log('Business:');
|
|
275
|
-
console.log(' business init <name> -
|
|
284
|
+
console.log(' business init <name> - Create shared owner + first/default computer');
|
|
276
285
|
console.log(' business onboard - Onboard from sparse input (--name, --website, --contact)');
|
|
277
286
|
console.log(' business add <slug> - Connect a business');
|
|
278
287
|
console.log(' business list - Show connected businesses');
|
|
@@ -280,6 +289,7 @@ function showHelp() {
|
|
|
280
289
|
console.log(' business team [slug] - Show members, roles, and admin access');
|
|
281
290
|
console.log(' business health <slug> - Health report (members, workspace, issues)');
|
|
282
291
|
console.log(' business audit - One-line health summary of all businesses');
|
|
292
|
+
console.log(' business doctor - Catch stale cache, alias, and folder bindings');
|
|
283
293
|
console.log(' business create <name> - Cloud-only business record; add --workspace to also scaffold local');
|
|
284
294
|
console.log(' business connect <svc> - Wire a skill/integration');
|
|
285
295
|
console.log(' business notify <mode> - Set notification mode (digest/silent/push)');
|
|
@@ -290,9 +300,11 @@ function showHelp() {
|
|
|
290
300
|
console.log(' cr --all - Audit all backend services');
|
|
291
301
|
console.log('');
|
|
292
302
|
console.log('Cloud & agents:');
|
|
293
|
-
console.log(' computer -
|
|
303
|
+
console.log(' computer - Open a scoped AI computer (cloud/local, personal/business)');
|
|
294
304
|
console.log(' receipt - Save evidence from an agent run');
|
|
295
305
|
console.log(' console - Start/attach always-on coding console (tmux daemon)');
|
|
306
|
+
console.log(' soul - Show, snapshot, or fork workspace identity');
|
|
307
|
+
console.log(' fleet - Inspect local fleet status');
|
|
296
308
|
console.log(' agent - Select which Atris agent to use');
|
|
297
309
|
console.log(' chat - Chat with the selected Atris agent');
|
|
298
310
|
console.log(' login - Sign in or add another account');
|
|
@@ -433,8 +445,8 @@ const { planAtris: planCmd, doAtris: doCmd, reviewAtris: reviewCmd } = require('
|
|
|
433
445
|
// Check if this is a known command or natural language input
|
|
434
446
|
const knownCommands = ['init', 'log', 'status', 'analytics', 'visualize', 'brainstorm', 'autopilot', 'run', 'plan', 'do', 'review', 'release',
|
|
435
447
|
'activate', '_activate', 'agent', 'chat', 'console', 'login', 'logout', 'whoami', 'switch', 'use', 'accounts', '_resolve', '_profile-email', '_switch-session', 'shell-init', 'update', 'upgrade', 'version', 'help', 'next', 'atris',
|
|
436
|
-
'clean', 'verify', 'search', 'skill', 'member', 'app', 'learn', 'plugin', 'experiments', 'receipt', 'proof', 'openclaw', 'pull', 'push', 'align', 'terminal', 'computer', 'diff', 'business', 'sync',
|
|
437
|
-
'ingest', 'query', 'lint', 'loop',
|
|
448
|
+
'clean', 'verify', 'search', 'skill', 'member', 'app', 'learn', 'plugin', 'experiments', 'receipt', 'proof', 'openclaw', 'pull', 'push', 'live', 'align', 'terminal', 'computer', 'diff', 'business', 'sync',
|
|
449
|
+
'ingest', 'query', 'lint', 'loop', 'task', 'aeo',
|
|
438
450
|
'gmail', 'calendar', 'twitter', 'slack', 'integrations', 'setup', 'clean-workspace', 'cw',
|
|
439
451
|
'fork', 'browse', 'publish', 'sleep', 'wake', 'feedback', 'errors', 'wiki', 'code-review', 'cr', 'soul', 'fleet'];
|
|
440
452
|
|
|
@@ -780,6 +792,16 @@ if (command === 'init') {
|
|
|
780
792
|
console.error(`✗ Error: ${error.message || error}`);
|
|
781
793
|
process.exit(1);
|
|
782
794
|
});
|
|
795
|
+
} else if (command === 'task') {
|
|
796
|
+
// SQLite-backed task plane. ~/.atris/tasks.db, gitignored, per-workspace.
|
|
797
|
+
Promise.resolve(require('../commands/task').run(process.argv.slice(3)))
|
|
798
|
+
.then(() => process.exit(0))
|
|
799
|
+
.catch((err) => { console.error(`\n✗ Error: ${err.message || err}`); process.exit(1); });
|
|
800
|
+
} else if (command === 'aeo') {
|
|
801
|
+
// AEO: AI Engine Optimization — credit-metered citation drafting against the customer workspace.
|
|
802
|
+
Promise.resolve(require('../commands/aeo').run(process.argv.slice(3)))
|
|
803
|
+
.then(() => process.exit(0))
|
|
804
|
+
.catch((err) => { console.error(`\n✗ Error: ${err.message || err}`); process.exit(1); });
|
|
783
805
|
} else if (command === 'agent') {
|
|
784
806
|
agentAtris().then(() => process.exit(0)).catch((err) => { console.error(`\n✗ Error: ${err.message || err}`); process.exit(1); });
|
|
785
807
|
} else if (command === 'log') {
|
|
@@ -1088,6 +1110,10 @@ if (command === 'init') {
|
|
|
1088
1110
|
require('../commands/push').pushAtris()
|
|
1089
1111
|
.then(() => process.exit(0))
|
|
1090
1112
|
.catch((err) => { console.error(`\n✗ Error: ${err.message || err}`); process.exit(1); });
|
|
1113
|
+
} else if (command === 'live') {
|
|
1114
|
+
require('../commands/live').liveCommand()
|
|
1115
|
+
.then(() => process.exit(0))
|
|
1116
|
+
.catch((err) => { console.error(`\n✗ Error: ${err.message || err}`); process.exit(1); });
|
|
1091
1117
|
} else if (command === 'align') {
|
|
1092
1118
|
require('../commands/align').alignAtris()
|
|
1093
1119
|
.then(() => process.exit(0))
|
package/commands/aeo.js
ADDED
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* atris aeo — AI Engine Optimization commands
|
|
3
|
+
*
|
|
4
|
+
* atris aeo init # create entity-graph skeleton in workspace
|
|
5
|
+
* atris aeo draft "<topic>" [opts] # generate citation-optimized article (credit-metered)
|
|
6
|
+
*
|
|
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
|
|
10
|
+
*
|
|
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/.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
const fs = require('fs');
|
|
17
|
+
const path = require('path');
|
|
18
|
+
const { loadCredentials } = require('../utils/auth');
|
|
19
|
+
const { apiRequestJson } = require('../utils/api');
|
|
20
|
+
const { loadBusinesses, saveBusinesses } = require('./business');
|
|
21
|
+
|
|
22
|
+
function sleep(ms) { return new Promise((r) => setTimeout(r, ms)); }
|
|
23
|
+
|
|
24
|
+
async function ensureAwake(token, businessId, maxWaitSec = 90) {
|
|
25
|
+
const status = await apiRequestJson(`/business/${businessId}/ai-computer/status`, { method: 'GET', token });
|
|
26
|
+
if (status.ok && status.data && status.data.status === 'running' && status.data.endpoint) return true;
|
|
27
|
+
process.stdout.write(' Waking EC2 computer... ');
|
|
28
|
+
await apiRequestJson(`/business/${businessId}/ai-computer/wake`, { method: 'POST', token });
|
|
29
|
+
const start = Date.now();
|
|
30
|
+
while (Date.now() - start < maxWaitSec * 1000) {
|
|
31
|
+
await sleep(3000);
|
|
32
|
+
const s = await apiRequestJson(`/business/${businessId}/ai-computer/status`, { method: 'GET', token });
|
|
33
|
+
if (s.ok && s.data && s.data.status === 'running' && s.data.endpoint) {
|
|
34
|
+
console.log(`awake (${Math.floor((Date.now() - start) / 1000)}s)`);
|
|
35
|
+
return true;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
console.log('timeout');
|
|
39
|
+
return false;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
async function resolveBusiness(token, slug) {
|
|
43
|
+
const businesses = loadBusinesses();
|
|
44
|
+
const list = await apiRequestJson('/business/', { method: 'GET', token });
|
|
45
|
+
if (list.ok) {
|
|
46
|
+
const match = (list.data || []).find(
|
|
47
|
+
(b) => b.slug === slug || b.name.toLowerCase() === slug.toLowerCase()
|
|
48
|
+
);
|
|
49
|
+
if (!match) return null;
|
|
50
|
+
businesses[slug] = {
|
|
51
|
+
business_id: match.id,
|
|
52
|
+
workspace_id: match.workspace_id,
|
|
53
|
+
name: match.name,
|
|
54
|
+
slug: match.slug,
|
|
55
|
+
added_at: new Date().toISOString(),
|
|
56
|
+
};
|
|
57
|
+
saveBusinesses(businesses);
|
|
58
|
+
return { businessId: match.id, workspaceId: match.workspace_id, businessName: match.name };
|
|
59
|
+
}
|
|
60
|
+
if (businesses[slug]) {
|
|
61
|
+
return {
|
|
62
|
+
businessId: businesses[slug].business_id,
|
|
63
|
+
workspaceId: businesses[slug].workspace_id,
|
|
64
|
+
businessName: businesses[slug].name || slug,
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
return null;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function pickSlug(args) {
|
|
71
|
+
const wsIdx = args.findIndex((a) => a === '--workspace' || a === '-w');
|
|
72
|
+
if (wsIdx !== -1 && args[wsIdx + 1]) {
|
|
73
|
+
const slug = args[wsIdx + 1];
|
|
74
|
+
args.splice(wsIdx, 2);
|
|
75
|
+
return slug;
|
|
76
|
+
}
|
|
77
|
+
const bizFile = path.join(process.cwd(), '.atris', 'business.json');
|
|
78
|
+
if (fs.existsSync(bizFile)) {
|
|
79
|
+
try { return JSON.parse(fs.readFileSync(bizFile, 'utf8')).slug; } catch { /* ignore */ }
|
|
80
|
+
}
|
|
81
|
+
return null;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function printHelp() {
|
|
85
|
+
console.log('Usage:');
|
|
86
|
+
console.log(' atris aeo init [--workspace <slug>]');
|
|
87
|
+
console.log(' atris aeo draft "<topic>" [--workspace <slug>] [--queries q1,q2] [--slug X] [--url URL]');
|
|
88
|
+
console.log('');
|
|
89
|
+
console.log('Examples:');
|
|
90
|
+
console.log(' atris aeo init');
|
|
91
|
+
console.log(' atris aeo draft "what is pallet" --queries "what is pallet,best freight platform"');
|
|
92
|
+
console.log(' atris aeo draft "how does atris work" --workspace doordash --slug atris-overview');
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
async function aeoInit(args) {
|
|
96
|
+
const slug = pickSlug(args);
|
|
97
|
+
if (!slug) {
|
|
98
|
+
console.error('Cannot determine business. Pass --workspace <slug> or run from a workspace.');
|
|
99
|
+
process.exit(1);
|
|
100
|
+
}
|
|
101
|
+
const creds = loadCredentials();
|
|
102
|
+
if (!creds || !creds.token) { console.error('Not logged in. Run: atris login'); process.exit(1); }
|
|
103
|
+
|
|
104
|
+
const biz = await resolveBusiness(creds.token, slug);
|
|
105
|
+
if (!biz) { console.error(`Business "${slug}" not found.`); process.exit(1); }
|
|
106
|
+
if (!biz.workspaceId) { console.error(`Business "${slug}" has no workspace.`); process.exit(1); }
|
|
107
|
+
|
|
108
|
+
const awake = await ensureAwake(creds.token, biz.businessId);
|
|
109
|
+
if (!awake) { console.error(' EC2 computer did not become ready in time.'); process.exit(1); }
|
|
110
|
+
|
|
111
|
+
const result = await apiRequestJson(
|
|
112
|
+
`/business/${biz.businessId}/workspaces/${biz.workspaceId}/aeo/init`,
|
|
113
|
+
{ method: 'POST', token: creds.token, body: {}, timeoutMs: 60000 }
|
|
114
|
+
);
|
|
115
|
+
if (!result.ok) {
|
|
116
|
+
console.error(`✗ aeo init failed: ${result.errorMessage || result.error || result.status}`);
|
|
117
|
+
process.exit(1);
|
|
118
|
+
}
|
|
119
|
+
const data = result.data || {};
|
|
120
|
+
const created = data.created || [];
|
|
121
|
+
const skipped = data.skipped || [];
|
|
122
|
+
console.log(`✓ AEO entity graph @ ${data.dir}`);
|
|
123
|
+
if (created.length) console.log(` created: ${created.map((p) => p.split('/').pop()).join(', ')}`);
|
|
124
|
+
if (skipped.length) console.log(` existed: ${skipped.map((p) => p.split('/').pop()).join(', ')}`);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
async function aeoDraft(args) {
|
|
128
|
+
// Pull --slug, --url, --queries, --workspace; remainder is the topic.
|
|
129
|
+
const opts = {};
|
|
130
|
+
for (const k of ['slug', 'url', 'queries']) {
|
|
131
|
+
const i = args.findIndex((a) => a === `--${k}`);
|
|
132
|
+
if (i !== -1 && args[i + 1]) {
|
|
133
|
+
opts[k] = args[i + 1];
|
|
134
|
+
args.splice(i, 2);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
const slug = pickSlug(args);
|
|
138
|
+
const topic = args.join(' ').trim();
|
|
139
|
+
if (!topic) {
|
|
140
|
+
console.error('Missing topic. Usage: atris aeo draft "<topic>"');
|
|
141
|
+
process.exit(1);
|
|
142
|
+
}
|
|
143
|
+
if (!slug) {
|
|
144
|
+
console.error('Cannot determine business. Pass --workspace <slug> or run from a workspace.');
|
|
145
|
+
process.exit(1);
|
|
146
|
+
}
|
|
147
|
+
const creds = loadCredentials();
|
|
148
|
+
if (!creds || !creds.token) { console.error('Not logged in. Run: atris login'); process.exit(1); }
|
|
149
|
+
|
|
150
|
+
const biz = await resolveBusiness(creds.token, slug);
|
|
151
|
+
if (!biz) { console.error(`Business "${slug}" not found.`); process.exit(1); }
|
|
152
|
+
if (!biz.workspaceId) { console.error(`Business "${slug}" has no workspace.`); process.exit(1); }
|
|
153
|
+
|
|
154
|
+
const awake = await ensureAwake(creds.token, biz.businessId);
|
|
155
|
+
if (!awake) { console.error(' EC2 computer did not become ready in time.'); process.exit(1); }
|
|
156
|
+
|
|
157
|
+
const body = { topic };
|
|
158
|
+
if (opts.slug) body.slug = opts.slug;
|
|
159
|
+
if (opts.url) body.target_url = opts.url;
|
|
160
|
+
if (opts.queries) body.target_queries = opts.queries.split(',').map((s) => s.trim()).filter(Boolean);
|
|
161
|
+
|
|
162
|
+
process.stdout.write(`Drafting "${topic}" for ${biz.businessName}... `);
|
|
163
|
+
const t0 = Date.now();
|
|
164
|
+
const result = await apiRequestJson(
|
|
165
|
+
`/business/${biz.businessId}/workspaces/${biz.workspaceId}/aeo/draft`,
|
|
166
|
+
{ method: 'POST', token: creds.token, body, timeoutMs: 180000 }
|
|
167
|
+
);
|
|
168
|
+
const elapsed = ((Date.now() - t0) / 1000).toFixed(1);
|
|
169
|
+
if (!result.ok) {
|
|
170
|
+
console.log('failed');
|
|
171
|
+
console.error(`✗ aeo draft failed (${result.status}): ${result.errorMessage || result.error}`);
|
|
172
|
+
process.exit(1);
|
|
173
|
+
}
|
|
174
|
+
const data = result.data || {};
|
|
175
|
+
console.log(`done (${elapsed}s)`);
|
|
176
|
+
console.log('');
|
|
177
|
+
console.log(` path: ${data.path}`);
|
|
178
|
+
console.log(` self-score: ${data.self_score ?? '?'}/10`);
|
|
179
|
+
console.log(` credits: ${data.credits_charged ?? '?'}`);
|
|
180
|
+
console.log(` tokens: in=${data.tokens?.input ?? '?'} out=${data.tokens?.output ?? '?'}`);
|
|
181
|
+
console.log(` entity graph: entities=${data.entity_graph?.has_entities ? 'y' : 'n'} defs=${data.entity_graph?.has_definitions ? 'y' : 'n'} stats=${data.entity_graph?.has_stats ? 'y' : 'n'}`);
|
|
182
|
+
if (data.overlay_active) console.log(` overlay: active (${data.overlay_lines} lines)`);
|
|
183
|
+
if (data.hint) console.log(` hint: ${data.hint}`);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
async function run(args = []) {
|
|
187
|
+
const sub = args[0];
|
|
188
|
+
if (!sub || sub === 'help' || sub === '--help' || sub === '-h') return printHelp();
|
|
189
|
+
const rest = args.slice(1);
|
|
190
|
+
if (sub === 'init') return aeoInit(rest);
|
|
191
|
+
if (sub === 'draft') return aeoDraft(rest);
|
|
192
|
+
console.error(`Unknown aeo subcommand: ${sub}`);
|
|
193
|
+
printHelp();
|
|
194
|
+
process.exit(1);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
module.exports = { run };
|