atris 2.6.2 → 3.0.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 +124 -34
- package/atris/CLAUDE.md +5 -1
- package/atris/atris.md +4 -0
- package/atris/features/README.md +24 -0
- package/atris/skills/autopilot/SKILL.md +74 -75
- package/atris/skills/endgame/SKILL.md +179 -0
- package/atris/skills/flow/SKILL.md +121 -0
- package/atris/skills/improve/SKILL.md +84 -0
- package/atris/skills/loop/SKILL.md +72 -0
- package/atris/skills/wiki/SKILL.md +61 -0
- package/atris/team/executor/MEMBER.md +10 -4
- package/atris/team/navigator/MEMBER.md +2 -0
- package/atris/team/validator/MEMBER.md +8 -5
- package/atris.md +33 -0
- package/bin/atris.js +210 -41
- package/commands/activate.js +28 -2
- package/commands/align.js +720 -0
- package/commands/auth.js +75 -2
- package/commands/autopilot.js +1213 -270
- package/commands/browse.js +100 -0
- package/commands/business.js +785 -12
- package/commands/clean.js +107 -2
- package/commands/computer.js +429 -0
- package/commands/context-sync.js +78 -8
- package/commands/experiments.js +351 -0
- package/commands/feedback.js +150 -0
- package/commands/fleet.js +395 -0
- package/commands/fork.js +127 -0
- package/commands/init.js +50 -1
- package/commands/learn.js +407 -0
- package/commands/lifecycle.js +94 -0
- package/commands/loop.js +114 -0
- package/commands/publish.js +129 -0
- package/commands/pull.js +434 -48
- package/commands/push.js +312 -164
- package/commands/review.js +149 -0
- package/commands/run.js +76 -43
- package/commands/serve.js +360 -0
- package/commands/setup.js +1 -1
- package/commands/soul.js +381 -0
- package/commands/status.js +119 -1
- package/commands/sync.js +147 -1
- package/commands/terminal.js +201 -0
- package/commands/wiki.js +376 -0
- package/commands/workflow.js +191 -74
- package/commands/workspace-clean.js +3 -3
- package/lib/endstate.js +259 -0
- package/lib/learnings.js +235 -0
- package/lib/manifest.js +1 -0
- package/lib/todo.js +9 -5
- package/lib/wiki.js +578 -0
- package/package.json +2 -2
- package/utils/api.js +48 -36
- package/utils/auth.js +1 -0
package/commands/business.js
CHANGED
|
@@ -20,6 +20,18 @@ function saveBusinesses(data) {
|
|
|
20
20
|
fs.writeFileSync(getBusinessConfigPath(), JSON.stringify(data, null, 2));
|
|
21
21
|
}
|
|
22
22
|
|
|
23
|
+
function detectBusinessSlug(explicitSlug) {
|
|
24
|
+
if (explicitSlug) return explicitSlug;
|
|
25
|
+
const bizFile = path.join(process.cwd(), '.atris', 'business.json');
|
|
26
|
+
if (!fs.existsSync(bizFile)) return null;
|
|
27
|
+
try {
|
|
28
|
+
const biz = JSON.parse(fs.readFileSync(bizFile, 'utf8'));
|
|
29
|
+
return biz.slug || biz.name || null;
|
|
30
|
+
} catch {
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
23
35
|
async function addBusiness(slug) {
|
|
24
36
|
if (!slug) {
|
|
25
37
|
console.error('Usage: atris business add <slug>');
|
|
@@ -33,14 +45,14 @@ async function addBusiness(slug) {
|
|
|
33
45
|
}
|
|
34
46
|
|
|
35
47
|
// Resolve slug to business
|
|
36
|
-
const result = await apiRequestJson(`/
|
|
48
|
+
const result = await apiRequestJson(`/business/by-slug/${slug}`, {
|
|
37
49
|
method: 'GET',
|
|
38
50
|
token: creds.token,
|
|
39
51
|
});
|
|
40
52
|
|
|
41
53
|
if (!result.ok) {
|
|
42
54
|
// Try listing all and matching
|
|
43
|
-
const listResult = await apiRequestJson('/
|
|
55
|
+
const listResult = await apiRequestJson('/business/', { method: 'GET', token: creds.token });
|
|
44
56
|
if (listResult.ok && Array.isArray(listResult.data)) {
|
|
45
57
|
const match = listResult.data.find(b => b.slug === slug || b.name.toLowerCase() === slug.toLowerCase());
|
|
46
58
|
if (match) {
|
|
@@ -74,7 +86,14 @@ async function addBusiness(slug) {
|
|
|
74
86
|
console.log(`\nAdded "${biz.name}" (${biz.slug})`);
|
|
75
87
|
}
|
|
76
88
|
|
|
77
|
-
async function listBusinesses() {
|
|
89
|
+
async function listBusinesses(opts = {}) {
|
|
90
|
+
// --local mode: walk ~/arena/atris-business/ and show fleet status table
|
|
91
|
+
// (no API calls, rate-limit safe). Different from API-mode below which lists
|
|
92
|
+
// businesses cached from the API.
|
|
93
|
+
if (opts.local) {
|
|
94
|
+
return listBusinessesLocal(opts);
|
|
95
|
+
}
|
|
96
|
+
|
|
78
97
|
const businesses = loadBusinesses();
|
|
79
98
|
const slugs = Object.keys(businesses);
|
|
80
99
|
|
|
@@ -93,6 +112,159 @@ async function listBusinesses() {
|
|
|
93
112
|
}
|
|
94
113
|
}
|
|
95
114
|
|
|
115
|
+
/**
|
|
116
|
+
* Walk ~/arena/atris-business/ and print a fleet status table for every
|
|
117
|
+
* customer workspace. Pure local — no API calls, no rate-limit risk.
|
|
118
|
+
*
|
|
119
|
+
* Classifies each dir as: canonical, flat, unbound, nested, bare, or superseded.
|
|
120
|
+
*
|
|
121
|
+
* Discovered the need for this during overnight loop tick #3 when we hand-wrote
|
|
122
|
+
* /tmp/customer_fleet.md. Now any team member can run `atris business list --local`
|
|
123
|
+
* (or `atris business fleet`) to see fleet state in one shot.
|
|
124
|
+
*/
|
|
125
|
+
function listBusinessesLocal(opts = {}) {
|
|
126
|
+
const os = require('os');
|
|
127
|
+
const SKIP_DIRS = new Set(['deals', 'archive', 'archives', '_archive', 'templates', 'node_modules', '.git']);
|
|
128
|
+
const SKIP_FILES = new Set(['.DS_Store', 'Thumbs.db']);
|
|
129
|
+
|
|
130
|
+
const rootDir = opts.root || path.join(os.homedir(), 'arena', 'atris-business');
|
|
131
|
+
const jsonMode = opts.json === true;
|
|
132
|
+
|
|
133
|
+
if (!fs.existsSync(rootDir)) {
|
|
134
|
+
console.error(`Fleet root not found: ${rootDir}`);
|
|
135
|
+
process.exit(1);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function countFiles(dir) {
|
|
139
|
+
let total = 0;
|
|
140
|
+
let md = 0;
|
|
141
|
+
function walk(d) {
|
|
142
|
+
let entries;
|
|
143
|
+
try { entries = fs.readdirSync(d, { withFileTypes: true }); } catch { return; }
|
|
144
|
+
for (const e of entries) {
|
|
145
|
+
if (e.name.startsWith('.git')) continue;
|
|
146
|
+
if (e.name === 'node_modules') continue;
|
|
147
|
+
const full = path.join(d, e.name);
|
|
148
|
+
if (e.isDirectory()) {
|
|
149
|
+
walk(full);
|
|
150
|
+
} else if (e.isFile()) {
|
|
151
|
+
if (SKIP_FILES.has(e.name)) continue;
|
|
152
|
+
total++;
|
|
153
|
+
if (e.name.endsWith('.md')) md++;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
walk(dir);
|
|
158
|
+
return { total, md };
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
function classifyCustomer(name) {
|
|
162
|
+
const customerDir = path.join(rootDir, name);
|
|
163
|
+
const businessJson = path.join(customerDir, '.atris', 'business.json');
|
|
164
|
+
const atrisDir = path.join(customerDir, 'atris');
|
|
165
|
+
const nestedDir = path.join(customerDir, name);
|
|
166
|
+
|
|
167
|
+
const hasBizJson = fs.existsSync(businessJson);
|
|
168
|
+
const hasAtris = fs.existsSync(atrisDir) && fs.statSync(atrisDir).isDirectory();
|
|
169
|
+
const hasNested = fs.existsSync(nestedDir) && fs.statSync(nestedDir).isDirectory();
|
|
170
|
+
const { total, md } = countFiles(customerDir);
|
|
171
|
+
|
|
172
|
+
let state, action, icon;
|
|
173
|
+
if (hasBizJson && hasAtris) {
|
|
174
|
+
state = 'canonical'; action = 'none'; icon = '🟢';
|
|
175
|
+
} else if (hasBizJson && !hasAtris) {
|
|
176
|
+
state = 'flat'; action = 'migrate to atris/ wrapper'; icon = '🟡';
|
|
177
|
+
} else if (!hasBizJson && hasAtris) {
|
|
178
|
+
state = 'unbound'; action = 'create .atris/business.json'; icon = '🟡';
|
|
179
|
+
} else if (hasNested) {
|
|
180
|
+
state = 'nested'; action = 'legacy nesting bug'; icon = '🔴';
|
|
181
|
+
} else if (total < 5) {
|
|
182
|
+
state = 'bare'; action = 'not yet onboarded'; icon = '⚪';
|
|
183
|
+
} else {
|
|
184
|
+
state = 'flat-unbound'; action = 'needs canonical init'; icon = '🟡';
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
let bizName = name;
|
|
188
|
+
if (hasBizJson) {
|
|
189
|
+
try {
|
|
190
|
+
const meta = JSON.parse(fs.readFileSync(businessJson, 'utf8'));
|
|
191
|
+
bizName = meta.name || name;
|
|
192
|
+
} catch {}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
return { name, bizName, state, icon, files: total, md, hasBizJson, hasAtris, hasNested, action };
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
const entries = fs.readdirSync(rootDir, { withFileTypes: true })
|
|
199
|
+
.filter((e) => e.isDirectory())
|
|
200
|
+
.filter((e) => !e.name.startsWith('.'))
|
|
201
|
+
.filter((e) => !SKIP_DIRS.has(e.name))
|
|
202
|
+
.map((e) => e.name)
|
|
203
|
+
.sort();
|
|
204
|
+
|
|
205
|
+
const customers = entries.map(classifyCustomer);
|
|
206
|
+
|
|
207
|
+
// Mark superseded: any customer with a -canonical sibling is superseded
|
|
208
|
+
const canonicalNames = new Set(
|
|
209
|
+
customers.filter((c) => c.name.endsWith('-canonical')).map((c) => c.name.replace(/-canonical$/, ''))
|
|
210
|
+
);
|
|
211
|
+
for (const c of customers) {
|
|
212
|
+
if (canonicalNames.has(c.name)) {
|
|
213
|
+
c.state = 'superseded';
|
|
214
|
+
c.icon = '🔴';
|
|
215
|
+
c.action = `superseded by ${c.name}-canonical`;
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
if (jsonMode) {
|
|
220
|
+
console.log(JSON.stringify({ root: rootDir, customers }, null, 2));
|
|
221
|
+
return;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
console.log('');
|
|
225
|
+
console.log(`Atris Fleet — ${rootDir}`);
|
|
226
|
+
console.log('═'.repeat(86));
|
|
227
|
+
console.log(' CUSTOMER STATE FILES BIZ.JSON ATRIS/ ACTION');
|
|
228
|
+
console.log(' ' + '─'.repeat(83));
|
|
229
|
+
|
|
230
|
+
const order = ['canonical', 'flat', 'unbound', 'flat-unbound', 'bare', 'nested', 'superseded'];
|
|
231
|
+
const grouped = {};
|
|
232
|
+
for (const c of customers) {
|
|
233
|
+
if (!grouped[c.state]) grouped[c.state] = [];
|
|
234
|
+
grouped[c.state].push(c);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
for (const state of order) {
|
|
238
|
+
if (!grouped[state]) continue;
|
|
239
|
+
for (const c of grouped[state]) {
|
|
240
|
+
const name = c.name.padEnd(20).slice(0, 20);
|
|
241
|
+
const stateLabel = (c.icon + ' ' + state).padEnd(13).slice(0, 13);
|
|
242
|
+
const filesStr = String(c.files).padStart(5);
|
|
243
|
+
const bizStr = c.hasBizJson ? ' ✓ ' : ' ✗ ';
|
|
244
|
+
const atrisStr = c.hasAtris ? ' ✓ ' : ' ✗ ';
|
|
245
|
+
const action = c.action.length > 28 ? c.action.slice(0, 25) + '...' : c.action;
|
|
246
|
+
console.log(` ${name} ${stateLabel} ${filesStr} ${bizStr} ${atrisStr} ${action}`);
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
console.log(' ' + '─'.repeat(83));
|
|
251
|
+
|
|
252
|
+
const counts = {};
|
|
253
|
+
for (const c of customers) counts[c.state] = (counts[c.state] || 0) + 1;
|
|
254
|
+
const summary = order.filter((s) => counts[s]).map((s) => `${counts[s]} ${s}`).join(', ');
|
|
255
|
+
console.log(` ${customers.length} customers — ${summary}`);
|
|
256
|
+
console.log('');
|
|
257
|
+
|
|
258
|
+
const needsWork = customers.filter((c) => ['flat', 'unbound', 'flat-unbound', 'nested'].includes(c.state));
|
|
259
|
+
if (needsWork.length > 0) {
|
|
260
|
+
console.log(' Next actions:');
|
|
261
|
+
needsWork.slice(0, 5).forEach((c) => {
|
|
262
|
+
console.log(` ${c.icon} ${c.name}: ${c.action}`);
|
|
263
|
+
});
|
|
264
|
+
console.log('');
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
|
|
96
268
|
async function removeBusiness(slug) {
|
|
97
269
|
if (!slug) {
|
|
98
270
|
console.error('Usage: atris business remove <slug>');
|
|
@@ -122,7 +294,7 @@ async function resolveSlug(slug, creds) {
|
|
|
122
294
|
}
|
|
123
295
|
|
|
124
296
|
// Try by-slug endpoint
|
|
125
|
-
const result = await apiRequestJson(`/
|
|
297
|
+
const result = await apiRequestJson(`/business/by-slug/${slug}/`, {
|
|
126
298
|
method: 'GET',
|
|
127
299
|
token: creds.token,
|
|
128
300
|
});
|
|
@@ -131,7 +303,7 @@ async function resolveSlug(slug, creds) {
|
|
|
131
303
|
}
|
|
132
304
|
|
|
133
305
|
// Fallback: list all and match
|
|
134
|
-
const listResult = await apiRequestJson('/
|
|
306
|
+
const listResult = await apiRequestJson('/business/', { method: 'GET', token: creds.token });
|
|
135
307
|
if (listResult.ok && Array.isArray(listResult.data)) {
|
|
136
308
|
const match = listResult.data.find(b => b.slug === slug || b.name.toLowerCase() === slug.toLowerCase());
|
|
137
309
|
if (match) {
|
|
@@ -189,9 +361,9 @@ async function businessHealth(slug) {
|
|
|
189
361
|
// Fetch dashboard and workspace snapshot in parallel
|
|
190
362
|
const fetchOpts = { method: 'GET', token: creds.token, timeoutMs: 120000 };
|
|
191
363
|
const [dashResult, wsResult] = await Promise.all([
|
|
192
|
-
apiRequestJson(`/
|
|
364
|
+
apiRequestJson(`/business/${bizId}/dashboard/`, fetchOpts),
|
|
193
365
|
wsId
|
|
194
|
-
? apiRequestJson(`/
|
|
366
|
+
? apiRequestJson(`/business/${bizId}/workspaces/${wsId}/snapshot?include_content=false`, fetchOpts)
|
|
195
367
|
: Promise.resolve({ ok: false }),
|
|
196
368
|
]);
|
|
197
369
|
|
|
@@ -303,7 +475,7 @@ async function businessAudit() {
|
|
|
303
475
|
process.exit(1);
|
|
304
476
|
}
|
|
305
477
|
|
|
306
|
-
const listResult = await apiRequestJson('/
|
|
478
|
+
const listResult = await apiRequestJson('/business/', { method: 'GET', token: creds.token });
|
|
307
479
|
if (!listResult.ok || !Array.isArray(listResult.data)) {
|
|
308
480
|
console.error(`Failed to fetch businesses: ${listResult.error || 'unknown error'}`);
|
|
309
481
|
process.exit(1);
|
|
@@ -347,15 +519,578 @@ async function businessAudit() {
|
|
|
347
519
|
console.log('');
|
|
348
520
|
}
|
|
349
521
|
|
|
522
|
+
async function createBusiness(name, ...flags) {
|
|
523
|
+
if (!name) {
|
|
524
|
+
console.error('Usage: atris business create <name> [--description "..."]');
|
|
525
|
+
process.exit(1);
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
const creds = loadCredentials();
|
|
529
|
+
if (!creds || !creds.token) {
|
|
530
|
+
console.error('Not logged in. Run: atris login');
|
|
531
|
+
process.exit(1);
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
// Parse flags
|
|
535
|
+
let description = '';
|
|
536
|
+
for (let i = 0; i < flags.length; i++) {
|
|
537
|
+
if ((flags[i] === '--description' || flags[i] === '-d') && flags[i + 1]) {
|
|
538
|
+
description = flags[i + 1];
|
|
539
|
+
i++;
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
console.log(`Creating business: ${name}...`);
|
|
544
|
+
|
|
545
|
+
const result = await apiRequestJson('/business/', {
|
|
546
|
+
method: 'POST',
|
|
547
|
+
token: creds.token,
|
|
548
|
+
body: { name, description: description || undefined },
|
|
549
|
+
});
|
|
550
|
+
|
|
551
|
+
if (!result.ok) {
|
|
552
|
+
console.error(`Failed: ${result.errorMessage || result.error || result.status}`);
|
|
553
|
+
process.exit(1);
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
const biz = result.data;
|
|
557
|
+
|
|
558
|
+
// Register locally
|
|
559
|
+
const businesses = loadBusinesses();
|
|
560
|
+
businesses[biz.slug] = {
|
|
561
|
+
business_id: biz.id,
|
|
562
|
+
workspace_id: biz.workspace_id,
|
|
563
|
+
name: biz.name,
|
|
564
|
+
slug: biz.slug,
|
|
565
|
+
agent_id: biz.agent_id,
|
|
566
|
+
added_at: new Date().toISOString(),
|
|
567
|
+
};
|
|
568
|
+
saveBusinesses(businesses);
|
|
569
|
+
|
|
570
|
+
// Scaffold local directory if in an atris project
|
|
571
|
+
const atrisDir = findAtrisDir();
|
|
572
|
+
if (atrisDir) {
|
|
573
|
+
const bizDir = path.join(atrisDir, 'business', biz.slug);
|
|
574
|
+
if (!fs.existsSync(bizDir)) {
|
|
575
|
+
fs.mkdirSync(path.join(bizDir, 'context'), { recursive: true });
|
|
576
|
+
fs.mkdirSync(path.join(bizDir, 'team'), { recursive: true });
|
|
577
|
+
fs.mkdirSync(path.join(bizDir, 'workspace'), { recursive: true });
|
|
578
|
+
fs.writeFileSync(path.join(bizDir, 'BUSINESS.md'), [
|
|
579
|
+
`# ${biz.name}`,
|
|
580
|
+
description ? `\n> ${description}\n` : '',
|
|
581
|
+
'\n## The Business\n\n[What problem does this solve?]\n',
|
|
582
|
+
'## Revenue Model\n\n[How does this make money?]\n',
|
|
583
|
+
`---\n*Created: ${new Date().toISOString().split('T')[0]}*\n`,
|
|
584
|
+
].join(''));
|
|
585
|
+
console.log(` Local scaffold: ${bizDir}/`);
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
// Apply template if specified
|
|
590
|
+
let template = null;
|
|
591
|
+
for (let i = 0; i < flags.length; i++) {
|
|
592
|
+
if ((flags[i] === '--template' || flags[i] === '-t') && flags[i + 1]) {
|
|
593
|
+
template = flags[i + 1];
|
|
594
|
+
i++;
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
if (template) {
|
|
599
|
+
const templates = {
|
|
600
|
+
'saas': { agents: ['growth-hacker', 'product-analyst', 'support-agent'], desc: 'SaaS Startup' },
|
|
601
|
+
'agency': { agents: ['project-manager', 'researcher', 'outreach-agent'], desc: 'Agency / Consulting' },
|
|
602
|
+
'ecommerce': { agents: ['inventory-analyst', 'marketing-agent', 'support-agent'], desc: 'E-Commerce' },
|
|
603
|
+
'content': { agents: ['writer', 'researcher', 'social-media-agent'], desc: 'Content Creator' },
|
|
604
|
+
'restaurant': { agents: ['review-responder', 'social-media-agent', 'booking-agent'], desc: 'Restaurant / Local' },
|
|
605
|
+
};
|
|
606
|
+
const tpl = templates[template.toLowerCase()];
|
|
607
|
+
if (tpl) {
|
|
608
|
+
console.log(` Template: ${tpl.desc} (${tpl.agents.length} agents)`);
|
|
609
|
+
for (const agentName of tpl.agents) {
|
|
610
|
+
console.log(` + ${agentName}`);
|
|
611
|
+
}
|
|
612
|
+
} else {
|
|
613
|
+
console.log(` Unknown template: ${template}`);
|
|
614
|
+
console.log(` Available: ${Object.keys(templates).join(', ')}`);
|
|
615
|
+
}
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
console.log(`\n Business created!`);
|
|
619
|
+
console.log(` ID: ${biz.id}`);
|
|
620
|
+
console.log(` Slug: ${biz.slug}`);
|
|
621
|
+
console.log(` Agent: ${biz.agent_id || '(none)'}`);
|
|
622
|
+
console.log(` Dashboard: https://atris.ai/dashboard/gm/${biz.id}`);
|
|
623
|
+
console.log('');
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
|
|
627
|
+
async function businessStatus(slug) {
|
|
628
|
+
const creds = loadCredentials();
|
|
629
|
+
if (!creds || !creds.token) {
|
|
630
|
+
console.error('Not logged in. Run: atris login');
|
|
631
|
+
process.exit(1);
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
const resolved = await resolveSlug(slug, creds);
|
|
635
|
+
if (!resolved) {
|
|
636
|
+
console.error('No business specified. Usage: atris business status <slug>');
|
|
637
|
+
process.exit(1);
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
const result = await apiRequestJson(`/business/${resolved.business_id}`, {
|
|
641
|
+
method: 'GET',
|
|
642
|
+
token: creds.token,
|
|
643
|
+
});
|
|
644
|
+
|
|
645
|
+
if (!result.ok) {
|
|
646
|
+
console.error(`Failed to fetch business: ${result.errorMessage || result.status}`);
|
|
647
|
+
return;
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
const biz = result.data;
|
|
651
|
+
const agents = biz.member_count || 0;
|
|
652
|
+
const apps = biz.app_count || 0;
|
|
653
|
+
|
|
654
|
+
// Quick status line
|
|
655
|
+
console.log(`\n ${biz.name} (${biz.slug})`);
|
|
656
|
+
console.log(` ${'─'.repeat(40)}`);
|
|
657
|
+
console.log(` Agents: ${agents}`);
|
|
658
|
+
console.log(` Apps: ${apps}`);
|
|
659
|
+
if (biz.workspace_id) console.log(` Workspace: ${biz.workspace_id.slice(0, 12)}...`);
|
|
660
|
+
console.log(` Created: ${biz.created_at ? biz.created_at.split('T')[0] : '?'}`);
|
|
661
|
+
console.log('');
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
function describeAccess(member) {
|
|
665
|
+
const role = (member.role || '').toLowerCase();
|
|
666
|
+
if (role === 'owner') return 'full control';
|
|
667
|
+
if (role === 'admin') return 'admin access';
|
|
668
|
+
if (role === 'member') return 'standard access';
|
|
669
|
+
if (role === 'agent') return 'agent';
|
|
670
|
+
return role || 'unknown';
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
async function businessTeam(slug) {
|
|
674
|
+
const requestedSlug = detectBusinessSlug(slug);
|
|
675
|
+
if (!requestedSlug) {
|
|
676
|
+
console.error('No business specified. Usage: atris business team <slug>');
|
|
677
|
+
process.exit(1);
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
const creds = loadCredentials();
|
|
681
|
+
if (!creds || !creds.token) {
|
|
682
|
+
console.error('Not logged in. Run: atris login');
|
|
683
|
+
process.exit(1);
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
const resolved = await resolveSlug(requestedSlug, creds);
|
|
687
|
+
if (!resolved) {
|
|
688
|
+
console.error(`Business "${requestedSlug}" not found.`);
|
|
689
|
+
process.exit(1);
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
const result = await apiRequestJson(`/business/${resolved.business_id}`, {
|
|
693
|
+
method: 'GET',
|
|
694
|
+
token: creds.token,
|
|
695
|
+
});
|
|
696
|
+
|
|
697
|
+
if (!result.ok) {
|
|
698
|
+
console.error(`Failed to fetch business team: ${result.errorMessage || result.status}`);
|
|
699
|
+
process.exit(1);
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
const biz = result.data || {};
|
|
703
|
+
const members = Array.isArray(biz.members) ? [...biz.members] : [];
|
|
704
|
+
const roleOrder = { owner: 0, admin: 1, member: 2, agent: 3 };
|
|
705
|
+
members.sort((a, b) => {
|
|
706
|
+
const roleDelta = (roleOrder[a.role] ?? 99) - (roleOrder[b.role] ?? 99);
|
|
707
|
+
if (roleDelta !== 0) return roleDelta;
|
|
708
|
+
const aName = (a.display_name || a.name || a.email || '').toLowerCase();
|
|
709
|
+
const bName = (b.display_name || b.name || b.email || '').toLowerCase();
|
|
710
|
+
return aName.localeCompare(bName);
|
|
711
|
+
});
|
|
712
|
+
|
|
713
|
+
const admins = members.filter(m => ['owner', 'admin'].includes((m.role || '').toLowerCase()));
|
|
714
|
+
const nonAdmins = members.filter(m => !['owner', 'admin'].includes((m.role || '').toLowerCase()));
|
|
715
|
+
const roleCounts = members.reduce((acc, member) => {
|
|
716
|
+
const role = member.role || 'unknown';
|
|
717
|
+
acc[role] = (acc[role] || 0) + 1;
|
|
718
|
+
return acc;
|
|
719
|
+
}, {});
|
|
720
|
+
const roleSummary = Object.entries(roleCounts)
|
|
721
|
+
.sort((a, b) => (roleOrder[a[0]] ?? 99) - (roleOrder[b[0]] ?? 99))
|
|
722
|
+
.map(([role, count]) => `${count} ${role}${count === 1 ? '' : 's'}`)
|
|
723
|
+
.join(', ');
|
|
724
|
+
|
|
725
|
+
console.log('');
|
|
726
|
+
console.log(`Business Team: ${biz.name || resolved.name || requestedSlug} (${biz.slug || resolved.slug || requestedSlug})`);
|
|
727
|
+
console.log('━'.repeat(32 + (biz.name || resolved.name || requestedSlug).length));
|
|
728
|
+
console.log('');
|
|
729
|
+
console.log(` Members: ${members.length}`);
|
|
730
|
+
console.log(` Roles: ${roleSummary || 'none'}`);
|
|
731
|
+
console.log(` Admins: ${admins.length}`);
|
|
732
|
+
|
|
733
|
+
if (admins.length > 0) {
|
|
734
|
+
console.log('');
|
|
735
|
+
console.log(' Admin Access:');
|
|
736
|
+
for (const member of admins) {
|
|
737
|
+
const name = member.display_name || member.name || member.email || 'Unknown';
|
|
738
|
+
const email = member.email || '(no email)';
|
|
739
|
+
const role = member.role || 'unknown';
|
|
740
|
+
console.log(` ${name.padEnd(24)} ${role.padEnd(8)} ${describeAccess(member).padEnd(14)} ${email}`);
|
|
741
|
+
}
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
if (nonAdmins.length > 0) {
|
|
745
|
+
console.log('');
|
|
746
|
+
console.log(' Standard Access:');
|
|
747
|
+
for (const member of nonAdmins) {
|
|
748
|
+
const name = member.display_name || member.name || member.email || 'Unknown';
|
|
749
|
+
const email = member.email || '(no email)';
|
|
750
|
+
const role = member.role || 'unknown';
|
|
751
|
+
console.log(` ${name.padEnd(24)} ${role.padEnd(8)} ${describeAccess(member).padEnd(14)} ${email}`);
|
|
752
|
+
}
|
|
753
|
+
}
|
|
754
|
+
|
|
755
|
+
console.log('');
|
|
756
|
+
}
|
|
757
|
+
|
|
758
|
+
|
|
759
|
+
async function connectService(connector, ...flags) {
|
|
760
|
+
if (!connector) {
|
|
761
|
+
console.log('Usage: atris business connect <service> [--business <slug>]');
|
|
762
|
+
console.log('');
|
|
763
|
+
console.log('Available connectors:');
|
|
764
|
+
// List skills that look like integrations
|
|
765
|
+
const skillDirs = [
|
|
766
|
+
path.join(__dirname, '..', '..', '.claude', 'skills'),
|
|
767
|
+
path.join(require('os').homedir(), '.claude', 'skills'),
|
|
768
|
+
];
|
|
769
|
+
const seen = new Set();
|
|
770
|
+
for (const dir of skillDirs) {
|
|
771
|
+
if (!fs.existsSync(dir)) continue;
|
|
772
|
+
for (const name of fs.readdirSync(dir)) {
|
|
773
|
+
const skillFile = path.join(dir, name, 'SKILL.md');
|
|
774
|
+
if (fs.existsSync(skillFile) && !seen.has(name)) {
|
|
775
|
+
seen.add(name);
|
|
776
|
+
}
|
|
777
|
+
}
|
|
778
|
+
}
|
|
779
|
+
const integrations = [...seen].filter(s =>
|
|
780
|
+
['slack', 'hubspot', 'linear', 'notion', 'google-drive', 'github',
|
|
781
|
+
'calendar', 'email-agent', 'x-search', 'youtube', 'ramp'].includes(s)
|
|
782
|
+
).sort();
|
|
783
|
+
for (const s of integrations) {
|
|
784
|
+
console.log(` ${s}`);
|
|
785
|
+
}
|
|
786
|
+
if (integrations.length === 0) console.log(' (none found — install skills first)');
|
|
787
|
+
return;
|
|
788
|
+
}
|
|
789
|
+
|
|
790
|
+
// Parse --business flag
|
|
791
|
+
let bizSlug = null;
|
|
792
|
+
for (let i = 0; i < flags.length; i++) {
|
|
793
|
+
if ((flags[i] === '--business' || flags[i] === '-b') && flags[i + 1]) {
|
|
794
|
+
bizSlug = flags[i + 1];
|
|
795
|
+
i++;
|
|
796
|
+
}
|
|
797
|
+
}
|
|
798
|
+
|
|
799
|
+
// Find the skill
|
|
800
|
+
const skillDirs = [
|
|
801
|
+
path.join(__dirname, '..', '..', '.claude', 'skills', connector),
|
|
802
|
+
path.join(require('os').homedir(), '.claude', 'skills', connector),
|
|
803
|
+
];
|
|
804
|
+
let skillPath = null;
|
|
805
|
+
for (const dir of skillDirs) {
|
|
806
|
+
const p = path.join(dir, 'SKILL.md');
|
|
807
|
+
if (fs.existsSync(p)) { skillPath = p; break; }
|
|
808
|
+
}
|
|
809
|
+
|
|
810
|
+
if (!skillPath) {
|
|
811
|
+
console.error(`Skill "${connector}" not found.`);
|
|
812
|
+
console.error('Check: .claude/skills/ or ~/.claude/skills/');
|
|
813
|
+
process.exit(1);
|
|
814
|
+
}
|
|
815
|
+
|
|
816
|
+
console.log(`\n Connecting: ${connector}`);
|
|
817
|
+
console.log(` Skill: ${skillPath}`);
|
|
818
|
+
if (bizSlug) console.log(` Business: ${bizSlug}`);
|
|
819
|
+
|
|
820
|
+
// Read skill to check for required secrets
|
|
821
|
+
const skillContent = fs.readFileSync(skillPath, 'utf8');
|
|
822
|
+
const secretMatches = skillContent.match(/[A-Z][A-Z0-9_]*_(?:KEY|TOKEN|SECRET|PASSWORD|API_KEY)/g) || [];
|
|
823
|
+
const uniqueSecrets = [...new Set(secretMatches)];
|
|
824
|
+
|
|
825
|
+
if (uniqueSecrets.length > 0) {
|
|
826
|
+
console.log(`\n Required secrets:`);
|
|
827
|
+
for (const secret of uniqueSecrets) {
|
|
828
|
+
console.log(` ${secret}`);
|
|
829
|
+
}
|
|
830
|
+
console.log(`\n Store secrets with: atris computer run "echo $${uniqueSecrets[0]}"`);
|
|
831
|
+
console.log(` Or set in: ~/.atris/secrets/${connector}/`);
|
|
832
|
+
}
|
|
833
|
+
|
|
834
|
+
// Create local secrets directory
|
|
835
|
+
const secretsDir = path.join(require('os').homedir(), '.atris', 'secrets', connector);
|
|
836
|
+
if (!fs.existsSync(secretsDir)) {
|
|
837
|
+
fs.mkdirSync(secretsDir, { recursive: true });
|
|
838
|
+
console.log(`\n Created secrets dir: ${secretsDir}/`);
|
|
839
|
+
}
|
|
840
|
+
|
|
841
|
+
console.log(`\n Connected "${connector}" skill.`);
|
|
842
|
+
console.log(` Agent can now use ${connector} capabilities.`);
|
|
843
|
+
console.log('');
|
|
844
|
+
}
|
|
845
|
+
|
|
846
|
+
|
|
847
|
+
async function setNotificationMode(mode, ...flags) {
|
|
848
|
+
const validModes = ['digest', 'silent', 'push'];
|
|
849
|
+
if (!mode || !validModes.includes(mode)) {
|
|
850
|
+
console.log('Usage: atris business notify <digest|silent|push> [--business <slug>]');
|
|
851
|
+
console.log('');
|
|
852
|
+
console.log(' digest Batch all reports into morning briefing (1 email/day)');
|
|
853
|
+
console.log(' silent Log only, never notify (check with `atris business status`)');
|
|
854
|
+
console.log(' push Interrupt immediately on every action (default, noisy)');
|
|
855
|
+
return;
|
|
856
|
+
}
|
|
857
|
+
|
|
858
|
+
const creds = loadCredentials();
|
|
859
|
+
if (!creds || !creds.token) {
|
|
860
|
+
console.error('Not logged in. Run: atris login');
|
|
861
|
+
process.exit(1);
|
|
862
|
+
}
|
|
863
|
+
|
|
864
|
+
// Parse --business flag
|
|
865
|
+
let bizSlug = null;
|
|
866
|
+
for (let i = 0; i < flags.length; i++) {
|
|
867
|
+
if ((flags[i] === '--business' || flags[i] === '-b') && flags[i + 1]) {
|
|
868
|
+
bizSlug = flags[i + 1];
|
|
869
|
+
i++;
|
|
870
|
+
}
|
|
871
|
+
}
|
|
872
|
+
|
|
873
|
+
const resolved = await resolveSlug(bizSlug, creds);
|
|
874
|
+
if (!resolved) {
|
|
875
|
+
console.error('No business specified. Usage: atris business notify digest --business <slug>');
|
|
876
|
+
process.exit(1);
|
|
877
|
+
}
|
|
878
|
+
|
|
879
|
+
// Update business config with notification mode
|
|
880
|
+
const result = await apiRequestJson(`/business/${resolved.business_id}`, {
|
|
881
|
+
method: 'PUT',
|
|
882
|
+
token: creds.token,
|
|
883
|
+
body: {
|
|
884
|
+
config: { notification_mode: mode },
|
|
885
|
+
},
|
|
886
|
+
});
|
|
887
|
+
|
|
888
|
+
if (!result.ok) {
|
|
889
|
+
console.error(`Failed: ${result.errorMessage || result.status}`);
|
|
890
|
+
process.exit(1);
|
|
891
|
+
}
|
|
892
|
+
|
|
893
|
+
const icons = { digest: '📬', silent: '🔇', push: '🔔' };
|
|
894
|
+
const descriptions = {
|
|
895
|
+
digest: 'Agents report in morning briefing only (1 email/day)',
|
|
896
|
+
silent: 'Everything logged, nothing notified',
|
|
897
|
+
push: 'Every action sends a notification',
|
|
898
|
+
};
|
|
899
|
+
|
|
900
|
+
console.log(`\n ${icons[mode]} Notification mode: ${mode}`);
|
|
901
|
+
console.log(` ${descriptions[mode]}`);
|
|
902
|
+
console.log(` Business: ${resolved.name || resolved.slug}`);
|
|
903
|
+
console.log('');
|
|
904
|
+
}
|
|
905
|
+
|
|
906
|
+
|
|
907
|
+
async function deployBusiness(slug) {
|
|
908
|
+
if (!slug) {
|
|
909
|
+
console.error('Usage: atris business deploy <slug>');
|
|
910
|
+
console.error(' Pushes local atris/business/<slug>/ to the cloud business.');
|
|
911
|
+
process.exit(1);
|
|
912
|
+
}
|
|
913
|
+
|
|
914
|
+
const creds = loadCredentials();
|
|
915
|
+
if (!creds || !creds.token) {
|
|
916
|
+
console.error('Not logged in. Run: atris login');
|
|
917
|
+
process.exit(1);
|
|
918
|
+
}
|
|
919
|
+
|
|
920
|
+
// Find local business directory
|
|
921
|
+
const atrisDir = findAtrisDir();
|
|
922
|
+
if (!atrisDir) {
|
|
923
|
+
console.error('Not in an atris project. Run from a directory with atris/ folder.');
|
|
924
|
+
process.exit(1);
|
|
925
|
+
}
|
|
926
|
+
|
|
927
|
+
const bizDir = path.join(atrisDir, 'business', slug);
|
|
928
|
+
if (!fs.existsSync(bizDir)) {
|
|
929
|
+
console.error(`Local business not found: ${bizDir}`);
|
|
930
|
+
console.error(`Create with: atris business create "${slug}"`);
|
|
931
|
+
process.exit(1);
|
|
932
|
+
}
|
|
933
|
+
|
|
934
|
+
// Check if business exists in cloud
|
|
935
|
+
const businesses = loadBusinesses();
|
|
936
|
+
let bizConfig = businesses[slug];
|
|
937
|
+
|
|
938
|
+
if (!bizConfig) {
|
|
939
|
+
// Try to find by slug in cloud
|
|
940
|
+
const listResult = await apiRequestJson('/business/', { method: 'GET', token: creds.token });
|
|
941
|
+
if (listResult.ok && Array.isArray(listResult.data)) {
|
|
942
|
+
const match = listResult.data.find(b => b.slug === slug);
|
|
943
|
+
if (match) {
|
|
944
|
+
bizConfig = { business_id: match.id, workspace_id: match.workspace_id, name: match.name, slug: match.slug };
|
|
945
|
+
businesses[slug] = { ...bizConfig, added_at: new Date().toISOString() };
|
|
946
|
+
saveBusinesses(businesses);
|
|
947
|
+
}
|
|
948
|
+
}
|
|
949
|
+
}
|
|
950
|
+
|
|
951
|
+
if (!bizConfig || !bizConfig.business_id) {
|
|
952
|
+
console.log(` Business "${slug}" not in cloud. Creating...`);
|
|
953
|
+
const bizMd = path.join(bizDir, 'BUSINESS.md');
|
|
954
|
+
const name = slug.split('-').map(w => w.charAt(0).toUpperCase() + w.slice(1)).join(' ');
|
|
955
|
+
const createResult = await apiRequestJson('/business/', {
|
|
956
|
+
method: 'POST', token: creds.token,
|
|
957
|
+
body: { name },
|
|
958
|
+
});
|
|
959
|
+
if (!createResult.ok) {
|
|
960
|
+
console.error(`Failed to create: ${createResult.errorMessage || createResult.status}`);
|
|
961
|
+
process.exit(1);
|
|
962
|
+
}
|
|
963
|
+
bizConfig = {
|
|
964
|
+
business_id: createResult.data.id,
|
|
965
|
+
workspace_id: createResult.data.workspace_id,
|
|
966
|
+
name: createResult.data.name,
|
|
967
|
+
slug: createResult.data.slug,
|
|
968
|
+
};
|
|
969
|
+
businesses[slug] = { ...bizConfig, added_at: new Date().toISOString() };
|
|
970
|
+
saveBusinesses(businesses);
|
|
971
|
+
console.log(` Created: ${bizConfig.name} (${bizConfig.business_id.slice(0, 12)}...)`);
|
|
972
|
+
}
|
|
973
|
+
|
|
974
|
+
// Upload workspace files
|
|
975
|
+
const workspaceDir = path.join(bizDir, 'workspace');
|
|
976
|
+
let uploadCount = 0;
|
|
977
|
+
if (fs.existsSync(workspaceDir)) {
|
|
978
|
+
const files = walkDir(workspaceDir);
|
|
979
|
+
for (const filePath of files) {
|
|
980
|
+
const relativePath = path.relative(workspaceDir, filePath);
|
|
981
|
+
if (relativePath.startsWith('.')) continue;
|
|
982
|
+
try {
|
|
983
|
+
const content = fs.readFileSync(filePath, 'utf8');
|
|
984
|
+
const uploadResult = await apiRequestJson(
|
|
985
|
+
`/business/${bizConfig.business_id}/workspaces/${bizConfig.workspace_id}/file`,
|
|
986
|
+
{ method: 'PUT', token: creds.token, body: { path: '/' + relativePath, content } }
|
|
987
|
+
);
|
|
988
|
+
if (uploadResult.ok) {
|
|
989
|
+
uploadCount++;
|
|
990
|
+
process.stdout.write(` Uploaded: ${relativePath}\n`);
|
|
991
|
+
}
|
|
992
|
+
} catch (e) {
|
|
993
|
+
// Skip binary files or errors
|
|
994
|
+
}
|
|
995
|
+
}
|
|
996
|
+
}
|
|
997
|
+
|
|
998
|
+
// Upload BUSINESS.md as context
|
|
999
|
+
const bizMd = path.join(bizDir, 'BUSINESS.md');
|
|
1000
|
+
if (fs.existsSync(bizMd)) {
|
|
1001
|
+
try {
|
|
1002
|
+
const content = fs.readFileSync(bizMd, 'utf8');
|
|
1003
|
+
await apiRequestJson(
|
|
1004
|
+
`/business/${bizConfig.business_id}/workspaces/${bizConfig.workspace_id}/file`,
|
|
1005
|
+
{ method: 'PUT', token: creds.token, body: { path: '/BUSINESS.md', content } }
|
|
1006
|
+
);
|
|
1007
|
+
uploadCount++;
|
|
1008
|
+
console.log(' Uploaded: BUSINESS.md');
|
|
1009
|
+
} catch {}
|
|
1010
|
+
}
|
|
1011
|
+
|
|
1012
|
+
console.log(`\n Deployed ${uploadCount} files to ${bizConfig.name}`);
|
|
1013
|
+
console.log(` Dashboard: https://atris.ai/dashboard/gm/${bizConfig.business_id}`);
|
|
1014
|
+
console.log('');
|
|
1015
|
+
}
|
|
1016
|
+
|
|
1017
|
+
|
|
1018
|
+
function walkDir(dir) {
|
|
1019
|
+
let results = [];
|
|
1020
|
+
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
1021
|
+
const full = path.join(dir, entry.name);
|
|
1022
|
+
if (entry.isDirectory()) {
|
|
1023
|
+
results = results.concat(walkDir(full));
|
|
1024
|
+
} else {
|
|
1025
|
+
results.push(full);
|
|
1026
|
+
}
|
|
1027
|
+
}
|
|
1028
|
+
return results;
|
|
1029
|
+
}
|
|
1030
|
+
|
|
1031
|
+
|
|
1032
|
+
function findAtrisDir() {
|
|
1033
|
+
let dir = process.cwd();
|
|
1034
|
+
while (dir !== path.dirname(dir)) {
|
|
1035
|
+
if (fs.existsSync(path.join(dir, 'atris'))) return path.join(dir, 'atris');
|
|
1036
|
+
dir = path.dirname(dir);
|
|
1037
|
+
}
|
|
1038
|
+
return null;
|
|
1039
|
+
}
|
|
1040
|
+
|
|
1041
|
+
|
|
1042
|
+
async function quickstart() {
|
|
1043
|
+
console.log(`
|
|
1044
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
1045
|
+
Start a Business in 3 Commands
|
|
1046
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
1047
|
+
|
|
1048
|
+
1. Create:
|
|
1049
|
+
atris business create "My Company" --template saas
|
|
1050
|
+
|
|
1051
|
+
2. Connect integrations:
|
|
1052
|
+
atris business connect slack --business my-company
|
|
1053
|
+
atris business connect github --business my-company
|
|
1054
|
+
|
|
1055
|
+
3. Deploy:
|
|
1056
|
+
atris business deploy my-company
|
|
1057
|
+
|
|
1058
|
+
That's it. Your agents are live.
|
|
1059
|
+
|
|
1060
|
+
Optional:
|
|
1061
|
+
atris business notify digest --business my-company
|
|
1062
|
+
(get 1 email/day instead of every notification)
|
|
1063
|
+
|
|
1064
|
+
Templates: saas, agency, ecommerce, content, restaurant
|
|
1065
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
1066
|
+
`);
|
|
1067
|
+
}
|
|
1068
|
+
|
|
1069
|
+
|
|
350
1070
|
async function businessCommand(subcommand, ...args) {
|
|
351
1071
|
switch (subcommand) {
|
|
352
1072
|
case 'add':
|
|
353
1073
|
await addBusiness(args[0]);
|
|
354
1074
|
break;
|
|
1075
|
+
case 'create':
|
|
1076
|
+
case 'new':
|
|
1077
|
+
await createBusiness(args[0], ...args.slice(1));
|
|
1078
|
+
break;
|
|
355
1079
|
case 'list':
|
|
356
|
-
case 'ls':
|
|
357
|
-
|
|
1080
|
+
case 'ls': {
|
|
1081
|
+
const opts = {};
|
|
1082
|
+
if (args.includes('--local')) opts.local = true;
|
|
1083
|
+
if (args.includes('--json')) opts.json = true;
|
|
1084
|
+
await listBusinesses(opts);
|
|
358
1085
|
break;
|
|
1086
|
+
}
|
|
1087
|
+
case 'fleet': {
|
|
1088
|
+
// Shorthand for `business list --local`
|
|
1089
|
+
const opts = { local: true };
|
|
1090
|
+
if (args.includes('--json')) opts.json = true;
|
|
1091
|
+
await listBusinesses(opts);
|
|
1092
|
+
break;
|
|
1093
|
+
}
|
|
359
1094
|
case 'remove':
|
|
360
1095
|
case 'rm':
|
|
361
1096
|
await removeBusiness(args[0]);
|
|
@@ -363,12 +1098,50 @@ async function businessCommand(subcommand, ...args) {
|
|
|
363
1098
|
case 'health':
|
|
364
1099
|
await businessHealth(args[0]);
|
|
365
1100
|
break;
|
|
1101
|
+
case 'team':
|
|
1102
|
+
case 'members':
|
|
1103
|
+
case 'roster':
|
|
1104
|
+
await businessTeam(args[0]);
|
|
1105
|
+
break;
|
|
1106
|
+
case 'status':
|
|
1107
|
+
await businessStatus(args[0]);
|
|
1108
|
+
break;
|
|
366
1109
|
case 'audit':
|
|
367
1110
|
await businessAudit();
|
|
368
1111
|
break;
|
|
1112
|
+
case 'connect':
|
|
1113
|
+
await connectService(args[0], ...args.slice(1));
|
|
1114
|
+
break;
|
|
1115
|
+
case 'notify':
|
|
1116
|
+
case 'notification':
|
|
1117
|
+
await setNotificationMode(args[0], ...args.slice(1));
|
|
1118
|
+
break;
|
|
1119
|
+
case 'deploy':
|
|
1120
|
+
case 'push':
|
|
1121
|
+
await deployBusiness(args[0]);
|
|
1122
|
+
break;
|
|
1123
|
+
case 'quickstart':
|
|
1124
|
+
case 'start':
|
|
1125
|
+
case 'guide':
|
|
1126
|
+
await quickstart();
|
|
1127
|
+
break;
|
|
369
1128
|
default:
|
|
370
|
-
console.log('Usage: atris business <
|
|
1129
|
+
console.log('Usage: atris business <command> [args]');
|
|
1130
|
+
console.log('');
|
|
1131
|
+
console.log(' quickstart ← Start here! 3-command guide');
|
|
1132
|
+
console.log('');
|
|
1133
|
+
console.log(' create <name> Create a new business (cloud + local)');
|
|
1134
|
+
console.log(' add <slug> Register an existing cloud business');
|
|
1135
|
+
console.log(' list Show registered businesses');
|
|
1136
|
+
console.log(' team [slug] Show members, roles, and admin access');
|
|
1137
|
+
console.log(' status <slug> Quick status check');
|
|
1138
|
+
console.log(' health [slug] Full health dashboard');
|
|
1139
|
+
console.log(' audit Audit all businesses');
|
|
1140
|
+
console.log(' connect <service> Connect a skill/integration');
|
|
1141
|
+
console.log(' notify <mode> Set notification mode (digest/silent/push)');
|
|
1142
|
+
console.log(' deploy <slug> Push local business to cloud');
|
|
1143
|
+
console.log(' remove <slug> Unregister locally');
|
|
371
1144
|
}
|
|
372
1145
|
}
|
|
373
1146
|
|
|
374
|
-
module.exports = { businessCommand, businessHealth, businessAudit, loadBusinesses, saveBusinesses, getBusinessConfigPath };
|
|
1147
|
+
module.exports = { businessCommand, businessHealth, businessAudit, businessTeam, loadBusinesses, saveBusinesses, getBusinessConfigPath };
|