atris 3.14.0 → 3.15.11

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 CHANGED
@@ -90,9 +90,21 @@ if (!skipUpdateCheck && (!process.argv[2] || (process.argv[2] && !['version', 'u
90
90
  }
91
91
 
92
92
  const command = process.argv[2];
93
+ const commandArgs = process.argv.slice(3);
94
+ const firstCommandArg = process.argv[3];
95
+ const isBusinessSyncSafetyCommand = command === 'sync'
96
+ && (
97
+ commandArgs.includes('--status')
98
+ || commandArgs.includes('--review')
99
+ || commandArgs.includes('--resolve')
100
+ || firstCommandArg === 'status'
101
+ || firstCommandArg === 'doctor'
102
+ || firstCommandArg === 'review'
103
+ || firstCommandArg === 'resolve'
104
+ );
93
105
 
94
106
  // Auto-sync skills only for commands that modify workspace state
95
- if (['init', 'update', 'sync', 'upgrade'].includes(command)) {
107
+ if (['init', 'update', 'upgrade'].includes(command) || (command === 'sync' && !isBusinessSyncSafetyCommand)) {
96
108
  try {
97
109
  const { syncSkills } = require('../commands/sync');
98
110
  const skillsUpdated = syncSkills({ silent: true });
@@ -238,6 +250,7 @@ function showHelp() {
238
250
  console.log('');
239
251
  console.log('Context & tracking:');
240
252
  console.log(' log - Add ideas to inbox');
253
+ console.log(' now - Show atris/now.md, the current operating truth');
241
254
  console.log(' activate - Load Atris context');
242
255
  console.log(' status - See local work and completions (`atris status <business>` for remote)');
243
256
  console.log(' analytics - Show recent productivity from journals');
@@ -247,6 +260,7 @@ function showHelp() {
247
260
  console.log(' task - Local agent task plane (atomic claims, TODO import)');
248
261
  console.log(' release - Tag release, bump version, create GitHub release, draft /launch');
249
262
  console.log(' learn - Project learnings (patterns, pitfalls, preferences)');
263
+ console.log(' brain - Compile MAP/TODO/wiki/state into a loadable agent brain');
250
264
  console.log(' ingest - Local-first wiki ingest into atris/wiki/');
251
265
  console.log(' query - Local-first wiki query against atris/wiki/');
252
266
  console.log(' lint - Local-first wiki lint for atris/wiki/');
@@ -270,6 +284,7 @@ function showHelp() {
270
284
  console.log('Sync:');
271
285
  console.log(' pull - Pull journals + member data from cloud');
272
286
  console.log(' push - Push workspace files to cloud');
287
+ console.log(' live - Keep a business brain fresh (doctor, pull, watch, push)');
273
288
  console.log(' clean-workspace <slug> - Analyze & remove junk files from a workspace (alias: cw)');
274
289
  console.log('');
275
290
  console.log('GitHub for Context:');
@@ -318,6 +333,7 @@ function showHelp() {
318
333
  console.log(' calendar - Calendar commands (today, week)');
319
334
  console.log(' twitter - Twitter commands (post)');
320
335
  console.log(' slack - Slack commands (channels)');
336
+ console.log(' imessage - Local Mac iMessage commands (doctor, recent)');
321
337
  console.log(' integrations - Show integration status');
322
338
  console.log('');
323
339
  console.log('Skills:');
@@ -442,11 +458,11 @@ const { planAtris: planCmd, doAtris: doCmd, reviewAtris: reviewCmd } = require('
442
458
  // All other commands are lazy-loaded inline (require() only when invoked)
443
459
 
444
460
  // Check if this is a known command or natural language input
445
- const knownCommands = ['init', 'log', 'status', 'analytics', 'visualize', 'brainstorm', 'autopilot', 'run', 'plan', 'do', 'review', 'release',
461
+ const knownCommands = ['init', 'log', 'now', 'status', 'analytics', 'visualize', 'brain', 'brainstorm', 'autopilot', 'run', 'plan', 'do', 'review', 'release',
446
462
  'activate', '_activate', 'agent', 'chat', 'console', 'login', 'logout', 'whoami', 'switch', 'use', 'accounts', '_resolve', '_profile-email', '_switch-session', 'shell-init', 'update', 'upgrade', 'version', 'help', 'next', 'atris',
447
- 'clean', 'verify', 'search', 'skill', 'member', 'app', 'learn', 'plugin', 'experiments', 'receipt', 'proof', 'openclaw', 'pull', 'push', 'align', 'terminal', 'computer', 'diff', 'business', 'sync',
448
- 'ingest', 'query', 'lint', 'loop', 'task',
449
- 'gmail', 'calendar', 'twitter', 'slack', 'integrations', 'setup', 'clean-workspace', 'cw',
463
+ 'clean', 'verify', 'search', 'skill', 'member', 'app', 'learn', 'plugin', 'experiments', 'receipt', 'proof', 'openclaw', 'pull', 'push', 'live', 'align', 'terminal', 'computer', 'diff', 'business', 'sync',
464
+ 'ingest', 'query', 'lint', 'loop', 'task', 'aeo',
465
+ 'gmail', 'calendar', 'twitter', 'slack', 'imessage', 'integrations', 'setup', 'clean-workspace', 'cw',
450
466
  'fork', 'browse', 'publish', 'sleep', 'wake', 'feedback', 'errors', 'wiki', 'code-review', 'cr', 'soul', 'fleet'];
451
467
 
452
468
  // Check if command is an atris.md spec file - triggers welcome visualization
@@ -796,6 +812,15 @@ if (command === 'init') {
796
812
  Promise.resolve(require('../commands/task').run(process.argv.slice(3)))
797
813
  .then(() => process.exit(0))
798
814
  .catch((err) => { console.error(`\n✗ Error: ${err.message || err}`); process.exit(1); });
815
+ } else if (command === 'aeo') {
816
+ // AEO: AI Engine Optimization — credit-metered citation drafting against the customer workspace.
817
+ Promise.resolve(require('../commands/aeo').run(process.argv.slice(3)))
818
+ .then(() => process.exit(0))
819
+ .catch((err) => { console.error(`\n✗ Error: ${err.message || err}`); process.exit(1); });
820
+ } else if (command === 'brain') {
821
+ Promise.resolve(require('../commands/brain').brainCommand(process.argv.slice(3)))
822
+ .then(() => process.exit(0))
823
+ .catch((err) => { console.error(`\n✗ Error: ${err.message || err}`); process.exit(1); });
799
824
  } else if (command === 'agent') {
800
825
  agentAtris().then(() => process.exit(0)).catch((err) => { console.error(`\n✗ Error: ${err.message || err}`); process.exit(1); });
801
826
  } else if (command === 'log') {
@@ -815,10 +840,24 @@ if (command === 'init') {
815
840
  } else {
816
841
  logCmd();
817
842
  }
843
+ } else if (command === 'now') {
844
+ require('../commands/now').nowAtris(process.argv.slice(3));
818
845
  } else if (command === 'activate') {
819
846
  activateCmd();
820
847
  } else if (command === 'update' || command === 'sync') {
821
- if (process.argv.includes('--all')) {
848
+ const firstSyncArg = process.argv[3];
849
+ const isBusinessSync = command === 'sync'
850
+ && (
851
+ fs.existsSync(path.join(process.cwd(), '.atris', 'business.json'))
852
+ || isBusinessSyncSafetyCommand
853
+ || (firstSyncArg && !firstSyncArg.startsWith('-'))
854
+ )
855
+ && firstSyncArg !== 'all';
856
+ if (isBusinessSync) {
857
+ Promise.resolve(require('../commands/business-sync').businessSync(process.argv.slice(3)))
858
+ .then(() => process.exit(0))
859
+ .catch((err) => { console.error(`\n✗ Error: ${err.message || err}`); process.exit(1); });
860
+ } else if (process.argv.includes('--all')) {
822
861
  const dryRun = process.argv.includes('--dry-run');
823
862
  const force = process.argv.includes('--force') || process.argv.includes('--yes') || process.argv.includes('-y');
824
863
  Promise.resolve(syncAllCmd({ dryRun, force }))
@@ -1075,6 +1114,13 @@ if (command === 'init') {
1075
1114
  slackCommand(subcommand, ...args)
1076
1115
  .then(() => process.exit(0))
1077
1116
  .catch((err) => { console.error(`✗ Error: ${err.message || err}`); process.exit(1); });
1117
+ } else if (command === 'imessage') {
1118
+ const { imessageCommand } = require('../commands/integrations');
1119
+ const subcommand = process.argv[3];
1120
+ const args = process.argv.slice(4);
1121
+ imessageCommand(subcommand, ...args)
1122
+ .then(() => process.exit(0))
1123
+ .catch((err) => { console.error(`✗ Error: ${err.message || err}`); process.exit(1); });
1078
1124
  } else if (command === 'integrations') {
1079
1125
  const { integrationsStatus } = require('../commands/integrations');
1080
1126
  integrationsStatus()
@@ -1104,6 +1150,10 @@ if (command === 'init') {
1104
1150
  require('../commands/push').pushAtris()
1105
1151
  .then(() => process.exit(0))
1106
1152
  .catch((err) => { console.error(`\n✗ Error: ${err.message || err}`); process.exit(1); });
1153
+ } else if (command === 'live') {
1154
+ require('../commands/live').liveCommand()
1155
+ .then(() => process.exit(0))
1156
+ .catch((err) => { console.error(`\n✗ Error: ${err.message || err}`); process.exit(1); });
1107
1157
  } else if (command === 'align') {
1108
1158
  require('../commands/align').alignAtris()
1109
1159
  .then(() => process.exit(0))
@@ -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 };
package/commands/align.js CHANGED
@@ -473,7 +473,7 @@ async function alignHardLocalToCloud(token, biz, localDir) {
473
473
  console.log(` Manifest NOT updated. Re-run \`atris align ${resolvedSlug} --fix --hard\` to retry.`);
474
474
  process.exit(1);
475
475
  }
476
- saveManifest(resolvedSlug, buildManifest(localFiles, null));
476
+ saveManifest(resolvedSlug, buildManifest(localFiles, null, { workspaceRoot: localDir }));
477
477
  console.log(` Force-push complete: ${written}/${fileObjs.length} pushed, ${toDelete.length} cloud-only entries deleted.`);
478
478
  console.log(` ${businessName} is now mirrored from local.`);
479
479
  }