atris 3.12.0 → 3.13.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 CHANGED
@@ -27,7 +27,7 @@ Then read the workspace's `atris/atris.md` and follow it exactly. `atris.md` is
27
27
 
28
28
  | File | Purpose |
29
29
  |------|---------|
30
- | `atris/atris.md` | God file. Protocol and source of truth |
30
+ | `atris/atris.md` | Main instructions for agents working in this repo |
31
31
  | `atris/MAP.md` | Navigation index with file:line refs |
32
32
  | `atris/TODO.md` | Shared task queue |
33
33
  | `atris/logs/YYYY/YYYY-MM-DD.md` | Daily log, inbox, notes, completions |
@@ -67,7 +67,7 @@ atris
67
67
 
68
68
  `atris init` scaffolds the workspace, including `atris/wiki/`. `atris` loads context and hands the workflow off to `atris/atris.md`.
69
69
 
70
- If you're still shaping the idea, use `atris brainstorm`. If you want Atris to keep cycling, use `atris run` or `atris autopilot`. If you want the repo brain kept honest, use `atris loop`. `atris activate` now surfaces wiki state from `atris/wiki/STATUS.md` when it exists.
70
+ If you're still shaping the idea, use `atris brainstorm`. If you want Atris to keep cycling, use `atris run` or `atris autopilot`. If you want project memory checked for stale pages and missing context, use `atris loop`. `atris activate` surfaces wiki state from `atris/wiki/STATUS.md` when it exists.
71
71
 
72
72
  Core loop: `plan` -> `do` -> `review`
73
73
 
@@ -84,9 +84,9 @@ atris business onboard --website https://blondish.world --contact "Joel Zimmerma
84
84
  atris align --fix
85
85
  ```
86
86
 
87
- That creates the cloud business, writes `.atris/business.json`, initializes `.atris/state/` for events, episodes, and scorecards, and scaffolds the local `atris/` workspace under `~/arena/atris-business/<slug>/` with starter team lanes, a default recap artifact, and a first-loop starter queue in `atris/TODO.md`.
87
+ That creates the cloud business, 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
88
 
89
- If you do not have a neat source pack yet, `atris business onboard` is the low-friction intake step: give it a website, a named human, a few notes, or even just run it in a folder with loose files, and it seeds raw intake, a starter brief, a first loop, a safe next action, and an operator one-pager for you.
89
+ 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
90
 
91
91
  You can also use bare input:
92
92
 
@@ -120,26 +120,27 @@ atris business record atris/reports/2026-04-12-operator-recap.md --outcome mixed
120
120
  | `atris ingest` | Stage raw evidence into `atris/context/` and compile into `atris/wiki/` |
121
121
  | `atris loop` | Refresh wiki health, stale/orphan signals, and next ingest candidates |
122
122
  | `atris wiki` | Full wiki namespace: ingest, query, lint, search, log, and loop |
123
- | `atris experiments` | Run Karpathy-style keep/revert packs |
123
+ | `atris receipt` | Save evidence from an agent run |
124
+ | `atris experiments` | Run small experiments and compare results |
124
125
 
125
126
  ## Built-In Systems
126
127
 
127
128
  - `atris learn` stores structured project memory in `atris/learnings.jsonl`
128
129
  - `atris wiki` keeps repo memory in `atris/wiki/` by default, with `--cloud` when you want the remote workspace path
129
130
  - `atris ingest` now stages local source packs under `atris/context/_ingest/`, writes a manifest receipt, and refreshes `atris/wiki/STATUS.md` plus `log.md`
130
- - `atris wiki --private` uses `.atris/presidio/` for local-only sensitive notes and operating memory
131
+ - `atris wiki --private` stores local-only sensitive notes under `.atris/presidio/`
131
132
  - `atris loop` refreshes `atris/wiki/STATUS.md` and `atris/wiki/log.md`, flags stale/orphan pages, and suggests the next ingest
132
133
  - `atris activate` loads the current wiki status so the next session starts with project memory, not just tasks
133
- - `atris experiments` runs Karpathy-style keep/revert loops in `atris/experiments/`
134
+ - `atris experiments` runs small test packs in `atris/experiments/`
134
135
  - `atris pull` and `atris push` sync cloud workspaces and journals
135
136
 
136
137
  ## Verifiable Feedback Loop
137
138
 
138
139
  Under the hood, Atris can keep score on real repo work.
139
140
 
140
- - Endgame tasks can carry a `Verify:` command, so work can end on a deterministic check instead of pure prose.
141
- - `atris autopilot` can run that check after review, record a reward in the journal, and append a local scorecard when a horizon closes.
142
- - Future horizon picks can weight against recent scorecards, so the loop learns from repo-local history without claiming model retraining.
141
+ - Tasks can carry a `Verify:` command, so work can end on a deterministic check instead of pure prose.
142
+ - `atris autopilot` can run that check after review and record the result in the journal.
143
+ - Future task picks can use recent results, so Atris learns from repo-local history without claiming model retraining.
143
144
 
144
145
  ## Benchmark Harness
145
146
 
@@ -167,7 +168,7 @@ What to inspect:
167
168
  - receipts land in `atris/experiments/endstate-baseline/artifacts/` and
168
169
  `atris/experiments/endstate-stack/artifacts/`
169
170
  - scores append to each pack's `results.tsv`
170
- - `atris experiments compare endstate` prints the latest side-by-side scorecard
171
+ - `atris experiments compare endstate` prints the latest side-by-side comparison
171
172
  - `atris experiments replay endstate` runs the full public dry-run rehearsal
172
173
  - the benchmark contract lives at `atris/features/endstate/contract.md`
173
174
  - the verification log lives at `atris/features/endstate/validate.md`
@@ -198,7 +199,7 @@ For Codex, copy any skill folder into `~/.codex/skills/`.
198
199
  ## v3.2.0
199
200
 
200
201
  - **Staleness gate** — tasks tagged `[unverified]` are skipped at the moment of use, not pruned eagerly. Three-state model: actionable / unverified / deleted.
201
- - **Lesson gate** — `isLessonResolved` checks whether a lesson already shipped before proposing new horizons from it. Prevents the loop from re-solving solved problems.
202
+ - **Lesson gate** — `isLessonResolved` checks whether a lesson already shipped before proposing new work from it. Prevents the loop from re-solving solved problems.
202
203
  - **`atris release`** — new command: tags the version, bumps package.json, creates a GitHub release, and drafts a `/launch` post in one shot.
203
204
  - **Shell injection fix** — `checkStaleness` switched from `execSync` string interpolation to `execFileSync` with args arrays. Markdown-derived content (task titles, inbox items) no longer reaches a shell.
204
205
  - **Codex hardening** — `atris activate` and `atris` entry point detect Codex environments and write `AGENTS.md` so Codex sessions start with workspace context.
@@ -0,0 +1,108 @@
1
+ ---
2
+ name: atris-feedback
3
+ description: Submit, list, resolve, close, or delete Atris customer feedback. Use when user types /feedback or asks to triage the feedback queue.
4
+ version: 1.0.0
5
+ tags:
6
+ - feedback
7
+ - customer
8
+ - admin
9
+ ---
10
+
11
+ # Feedback
12
+
13
+ One skill for everything: submit feedback, view the queue, resolve/close/delete items.
14
+
15
+ ## Parse the input
16
+
17
+ - `/feedback` (no args) → show the queue
18
+ - `/feedback <message>` → submit new feedback
19
+ - `/feedback resolve <id> <resolution>` → mark as resolved, notify customer
20
+ - `/feedback close <id>` → close as wontfix/duplicate
21
+ - `/feedback delete <id>` → remove from queue
22
+
23
+ ## Preferred path: the Atris CLI
24
+
25
+ The `atris` CLI wraps every feedback operation against the production API and
26
+ handles auth from the user's login. Use it first — it's the canonical,
27
+ audited path and works without needing AWS credentials.
28
+
29
+ ```bash
30
+ atris feedback # list queue
31
+ atris feedback "the calendar hangs" # submit
32
+ atris feedback resolve abc123 "fixed" # mark resolved
33
+ atris feedback close abc123 # close as wontfix
34
+ atris feedback delete abc123 # delete
35
+ ```
36
+
37
+ IDs can be short (first 8 chars of the UUID) — the CLI resolves the prefix
38
+ against the live list before acting.
39
+
40
+ If `atris` is not on PATH, it lives at `~/arena/atris-cli/bin/atris.js`.
41
+
42
+ ## Fallback: direct API / DynamoDB
43
+
44
+ Only use this path if the CLI is unavailable (stale install, broken login).
45
+
46
+ ### Setup
47
+
48
+ ```bash
49
+ cd ~/arena/atrisos-backend && source venv/bin/activate
50
+ ```
51
+
52
+ ```python
53
+ from dotenv import load_dotenv; load_dotenv('backend/.env')
54
+ import boto3
55
+ table = boto3.resource('dynamodb', region_name='us-east-1').Table('atris_feedback')
56
+ ```
57
+
58
+ ### Submit
59
+
60
+ ```bash
61
+ curl -s -X POST "https://api.atris.ai/api/feedback" \
62
+ -H "Content-Type: application/json" \
63
+ -H "X-Feedback-Key: $FEEDBACK_API_KEY" \
64
+ -d '{"message": "THE_MESSAGE", "source": "cli", "context": {"user_email": "GIT_EMAIL"}}'
65
+ ```
66
+
67
+ Confirm: `Feedback submitted (id: abc123)`
68
+
69
+ ### Resolve
70
+
71
+ ```python
72
+ table.update_item(
73
+ Key={'id': FULL_ID},
74
+ UpdateExpression='SET #s = :s, resolution = :r, resolved_at = :t',
75
+ ExpressionAttributeNames={'#s': 'status'},
76
+ ExpressionAttributeValues={':s': 'resolved', ':r': RESOLUTION, ':t': NOW},
77
+ )
78
+ ```
79
+
80
+ Print: `Resolved abc123: <resolution>`
81
+
82
+ ### Close
83
+
84
+ Same as resolve but `status = 'closed'`, no resolution text needed.
85
+
86
+ ### Delete
87
+
88
+ ```python
89
+ table.delete_item(Key={'id': FULL_ID})
90
+ ```
91
+
92
+ Print: `Deleted abc123`
93
+
94
+ ### ID matching
95
+
96
+ Users type short IDs (first 8 chars). Scan the table and match by prefix
97
+ to find the full UUID.
98
+
99
+ ## Security
100
+
101
+ - NEVER include API keys, tokens, or secrets in feedback messages
102
+ - Server-side sanitization strips them anyway (double protection)
103
+ - Max 2000 chars per message
104
+
105
+ ## Output
106
+
107
+ Always print the result directly as text. Never leave it inside a tool
108
+ call expansion.
package/bin/atris.js CHANGED
@@ -247,7 +247,7 @@ function showHelp() {
247
247
  console.log('Optional helpers:');
248
248
  console.log(' brainstorm - Explore ideas conversationally before planning');
249
249
  console.log(' autopilot - Guided loop that can clarify TODOs and run plan → do → review');
250
- console.log(' visualize - Legacy visualization helper (prefer "atris plan")');
250
+ console.log(' visualize - Generate a Slack/deck-ready visual from a prompt');
251
251
  console.log('');
252
252
  console.log('Experiments:');
253
253
  console.log(' experiments init [slug] - Prepare atris/experiments/ or scaffold a pack');
@@ -272,7 +272,7 @@ function showHelp() {
272
272
  console.log(' wake [business] - Resume workspace (agents restart)');
273
273
  console.log('');
274
274
  console.log('Business:');
275
- console.log(' business init <name> - Create canonical business workspace (cloud + local)');
275
+ console.log(' business init <name> - RECOMMENDED: create business environment (cloud + local)');
276
276
  console.log(' business onboard - Onboard from sparse input (--name, --website, --contact)');
277
277
  console.log(' business add <slug> - Connect a business');
278
278
  console.log(' business list - Show connected businesses');
@@ -280,7 +280,7 @@ function showHelp() {
280
280
  console.log(' business team [slug] - Show members, roles, and admin access');
281
281
  console.log(' business health <slug> - Health report (members, workspace, issues)');
282
282
  console.log(' business audit - One-line health summary of all businesses');
283
- console.log(' business create <name> - Create new business; add --workspace for canonical local scaffold');
283
+ console.log(' business create <name> - Cloud-only business record; add --workspace to also scaffold local');
284
284
  console.log(' business connect <svc> - Wire a skill/integration');
285
285
  console.log(' business notify <mode> - Set notification mode (digest/silent/push)');
286
286
  console.log(' business deploy <slug> - Push local business to cloud');
@@ -291,6 +291,7 @@ function showHelp() {
291
291
  console.log('');
292
292
  console.log('Cloud & agents:');
293
293
  console.log(' computer - Talk directly to the AI computer (bash or agent exec)');
294
+ console.log(' receipt - Save evidence from an agent run');
294
295
  console.log(' console - Start/attach always-on coding console (tmux daemon)');
295
296
  console.log(' agent - Select which Atris agent to use');
296
297
  console.log(' chat - Chat with the selected Atris agent');
@@ -328,8 +329,11 @@ function showHelp() {
328
329
  console.log(' plugin info - Preview what will be included');
329
330
  console.log('');
330
331
  console.log('Feedback:');
331
- console.log(' feedback "msg" - Submit feedback');
332
- console.log(' feedback - List your feedback');
332
+ console.log(' feedback "msg" - Submit feedback');
333
+ console.log(' feedback - List feedback queue');
334
+ console.log(' feedback resolve <id> "<note>" - Mark resolved (admin)');
335
+ console.log(' feedback close <id> - Close as wontfix (admin)');
336
+ console.log(' feedback delete <id> - Delete feedback (admin)');
333
337
  console.log('');
334
338
  console.log('Other:');
335
339
  console.log(' version - Show Atris version');
@@ -429,10 +433,10 @@ const { planAtris: planCmd, doAtris: doCmd, reviewAtris: reviewCmd } = require('
429
433
  // Check if this is a known command or natural language input
430
434
  const knownCommands = ['init', 'log', 'status', 'analytics', 'visualize', 'brainstorm', 'autopilot', 'run', 'plan', 'do', 'review', 'release',
431
435
  'activate', '_activate', 'agent', 'chat', 'console', 'login', 'logout', 'whoami', 'switch', 'use', 'accounts', '_resolve', '_profile-email', '_switch-session', 'shell-init', 'update', 'upgrade', 'version', 'help', 'next', 'atris',
432
- 'clean', 'verify', 'search', 'skill', 'member', 'app', 'learn', 'plugin', 'experiments', 'pull', 'push', 'align', 'terminal', 'computer', 'diff', 'business', 'sync',
436
+ 'clean', 'verify', 'search', 'skill', 'member', 'app', 'learn', 'plugin', 'experiments', 'receipt', 'proof', 'openclaw', 'pull', 'push', 'align', 'terminal', 'computer', 'diff', 'business', 'sync',
433
437
  'ingest', 'query', 'lint', 'loop',
434
438
  'gmail', 'calendar', 'twitter', 'slack', 'integrations', 'setup', 'clean-workspace', 'cw',
435
- 'fork', 'browse', 'publish', 'sleep', 'wake', 'feedback', 'wiki', 'code-review', 'cr', 'soul', 'fleet'];
439
+ 'fork', 'browse', 'publish', 'sleep', 'wake', 'feedback', 'errors', 'wiki', 'code-review', 'cr', 'soul', 'fleet'];
436
440
 
437
441
  // Check if command is an atris.md spec file - triggers welcome visualization
438
442
  function isSpecFile(cmd) {
@@ -865,10 +869,9 @@ if (command === 'init') {
865
869
  } else if (command === 'shell-init') {
866
870
  require('../commands/auth').shellInit();
867
871
  } else if (command === 'visualize') {
868
- console.log('ℹ️ "atris visualize" is a legacy helper. Visualization is now built into "atris plan".');
869
- console.log(' Prefer: atris plan');
870
- console.log('');
871
- require('../commands/visualize').visualizeAtris();
872
+ require('../commands/visualize').visualizeAtris(process.argv.slice(3))
873
+ .then(() => process.exit(0))
874
+ .catch((err) => { console.error(`\n✗ Error: ${err.message || err}`); process.exit(1); });
872
875
  } else if (command === 'run') {
873
876
  const args = process.argv.slice(3);
874
877
  if (args.includes('--help') || args.includes('-h')) {
@@ -1157,6 +1160,10 @@ if (command === 'init') {
1157
1160
  const subcommand = process.argv[3];
1158
1161
  const args = process.argv.slice(4);
1159
1162
  require('../commands/experiments').experimentsCommand(subcommand, ...args);
1163
+ } else if (command === 'receipt' || command === 'proof' || command === 'openclaw') {
1164
+ const subcommand = process.argv[3];
1165
+ const args = process.argv.slice(4);
1166
+ require('../commands/proof').proofCommand(subcommand, ...args);
1160
1167
  } else if (command === 'setup') {
1161
1168
  require('../commands/setup').setupAtris()
1162
1169
  .then(() => process.exit(0))
@@ -1185,6 +1192,10 @@ if (command === 'init') {
1185
1192
  require('../commands/feedback').feedbackCommand()
1186
1193
  .then(() => process.exit(0))
1187
1194
  .catch((err) => { console.error(`\n✗ Error: ${err.message || err}`); process.exit(1); });
1195
+ } else if (command === 'errors') {
1196
+ require('../commands/errors').errorsCommand()
1197
+ .then(() => process.exit(0))
1198
+ .catch((err) => { console.error(`\n✗ Error: ${err.message || err}`); process.exit(1); });
1188
1199
  } else {
1189
1200
  console.log(`Unknown command: ${command}`);
1190
1201
  console.log('Run "atris help" to see available commands');
@@ -13,6 +13,20 @@ function getBusinessConfigPath() {
13
13
  return path.join(dir, 'businesses.json');
14
14
  }
15
15
 
16
+ function slugify(name) {
17
+ return String(name || '')
18
+ .toLowerCase()
19
+ .trim()
20
+ .replace(/[^a-z0-9\s-]/g, '')
21
+ .replace(/\s+/g, '-')
22
+ .replace(/-+/g, '-')
23
+ .replace(/^-+|-+$/g, '');
24
+ }
25
+
26
+ function isHelpToken(arg) {
27
+ return arg === '--help' || arg === '-h' || arg === 'help' || arg === '-?';
28
+ }
29
+
16
30
  function loadBusinesses() {
17
31
  const p = getBusinessConfigPath();
18
32
  if (!fs.existsSync(p)) return {};
@@ -774,7 +788,45 @@ function detectBusinessSlug(explicitSlug) {
774
788
  }
775
789
  }
776
790
 
791
+ async function findExistingBusinessBySlug(slug, token) {
792
+ if (!slug) return null;
793
+
794
+ // Local cache first — no network round-trip needed.
795
+ const local = loadBusinesses();
796
+ if (local[slug]) {
797
+ return { id: local[slug].business_id, name: local[slug].name, slug, source: 'local' };
798
+ }
799
+ for (const v of Object.values(local)) {
800
+ if (v && v.slug === slug) {
801
+ return { id: v.business_id, name: v.name, slug, source: 'local' };
802
+ }
803
+ }
804
+
805
+ if (!token) return null;
806
+
807
+ // Cloud lookup — covers businesses the user is a member of but hasn't added.
808
+ const direct = await apiRequestJson(`/business/by-slug/${encodeURIComponent(slug)}`, {
809
+ method: 'GET',
810
+ token,
811
+ });
812
+ if (direct.ok && direct.data && direct.data.id) {
813
+ return { id: direct.data.id, name: direct.data.name, slug: direct.data.slug || slug, source: 'cloud' };
814
+ }
815
+
816
+ const list = await apiRequestJson('/business/', { method: 'GET', token });
817
+ if (list.ok && Array.isArray(list.data)) {
818
+ const match = list.data.find(b => b && b.slug === slug);
819
+ if (match) return { id: match.id, name: match.name, slug: match.slug, source: 'cloud' };
820
+ }
821
+
822
+ return null;
823
+ }
824
+
777
825
  async function addBusiness(slug) {
826
+ if (!slug || isHelpToken(slug)) {
827
+ console.error('Usage: atris business add <slug>');
828
+ process.exit(1);
829
+ }
778
830
  if (!slug) {
779
831
  console.error('Usage: atris business add <slug>');
780
832
  process.exit(1);
@@ -1008,7 +1060,7 @@ function listBusinessesLocal(opts = {}) {
1008
1060
  }
1009
1061
 
1010
1062
  async function removeBusiness(slug) {
1011
- if (!slug) {
1063
+ if (!slug || isHelpToken(slug)) {
1012
1064
  console.error('Usage: atris business remove <slug>');
1013
1065
  process.exit(1);
1014
1066
  }
@@ -1262,8 +1314,11 @@ async function businessAudit() {
1262
1314
  }
1263
1315
 
1264
1316
  async function createBusinessInternal(name, flags = [], mode = 'auto') {
1265
- if (!name) {
1317
+ if (!name || isHelpToken(name) || String(name).startsWith('-')) {
1266
1318
  console.error('Usage: atris business create <name> [--description "..."] [--workspace] [--here|--root <dir>]');
1319
+ if (name && String(name).startsWith('-') && !isHelpToken(name)) {
1320
+ console.error(`\n Refusing to create a business named "${name}" — looks like a flag, not a name.`);
1321
+ }
1267
1322
  process.exit(1);
1268
1323
  }
1269
1324
 
@@ -1275,6 +1330,29 @@ async function createBusinessInternal(name, flags = [], mode = 'auto') {
1275
1330
 
1276
1331
  const options = parseCreateBusinessFlags(flags);
1277
1332
  const description = options.description;
1333
+ const force = flags.includes('--force') || flags.includes('--allow-duplicate');
1334
+
1335
+ // Pre-flight: refuse to create a duplicate by slug. The backend will silently
1336
+ // suffix `-1`, `-2`, etc., which produces ghost businesses when users actually
1337
+ // wanted to attach to an existing one. Guide them to `atris pull` instead.
1338
+ if (!force) {
1339
+ const desiredSlug = slugify(name);
1340
+ if (desiredSlug) {
1341
+ const existing = await findExistingBusinessBySlug(desiredSlug, creds.token);
1342
+ if (existing) {
1343
+ console.error(`\nA business with slug "${desiredSlug}" already exists.`);
1344
+ console.error(` Name: ${existing.name || desiredSlug}`);
1345
+ if (existing.id) console.error(` ID: ${existing.id}`);
1346
+ console.error('');
1347
+ console.error('To set up a local workspace for it, run:');
1348
+ console.error(` atris pull ${desiredSlug} # into ./${desiredSlug}`);
1349
+ console.error(` atris pull ${desiredSlug} --into <path> # into a custom path`);
1350
+ console.error('');
1351
+ console.error(`To create a NEW business anyway (will be slugged "${desiredSlug}-1"), pass --force.`);
1352
+ process.exit(1);
1353
+ }
1354
+ }
1355
+ }
1278
1356
 
1279
1357
  console.log(`Creating business: ${name}...`);
1280
1358
 
@@ -1832,12 +1910,52 @@ async function quickstart() {
1832
1910
  (get 1 email/day instead of every notification)
1833
1911
 
1834
1912
  Templates: saas, agency, ecommerce, content, restaurant
1913
+
1914
+ Rule of thumb:
1915
+ atris business init "<name>" = cloud + local business computer workspace
1916
+ atris business create "<name>" = cloud-only unless you pass --workspace
1835
1917
  ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
1836
1918
  `);
1837
1919
  }
1838
1920
 
1839
1921
 
1922
+ function printBusinessHelp() {
1923
+ console.log('Usage: atris business <command> [args]');
1924
+ console.log('');
1925
+ console.log(' quickstart ← Start here! 3-command guide');
1926
+ console.log('');
1927
+ console.log(' init <name> RECOMMENDED: create a business environment (cloud + local)');
1928
+ console.log(' workspace <name> Alias for init');
1929
+ console.log(' create <name> Cloud-only business record; add --workspace to also scaffold local');
1930
+ console.log(' add <slug> Register an existing cloud business');
1931
+ console.log(' list Show registered businesses');
1932
+ console.log(' team [slug] Show members, roles, and admin access');
1933
+ console.log(' status <slug> Quick status check');
1934
+ console.log(' health [slug] Full health dashboard');
1935
+ console.log(' audit Audit all businesses');
1936
+ console.log(' connect <service> Connect a skill/integration');
1937
+ console.log(' notify <mode> Set notification mode (digest/silent/push)');
1938
+ console.log(' deploy <slug> Push local business to cloud');
1939
+ console.log(' onboard Seed brief, person, first loop, safe next action, and one-pager from sparse input');
1940
+ console.log(' record <report> Append recap state into events, episodes, and scorecards');
1941
+ console.log(' remove <slug> Unregister locally');
1942
+ console.log('');
1943
+ console.log(' Already-attached business? Run `atris pull <slug>` to scaffold a local workspace.');
1944
+ }
1945
+
1840
1946
  async function businessCommand(subcommand, ...args) {
1947
+ // Help intercept — without this, `atris business init --help` would treat
1948
+ // `--help` as a business name and create one. Same for any subcommand that
1949
+ // takes a positional name/slug.
1950
+ if (!subcommand || isHelpToken(subcommand)) {
1951
+ printBusinessHelp();
1952
+ return;
1953
+ }
1954
+ if (args.length > 0 && isHelpToken(args[0])) {
1955
+ printBusinessHelp();
1956
+ return;
1957
+ }
1958
+
1841
1959
  switch (subcommand) {
1842
1960
  case 'add':
1843
1961
  await addBusiness(args[0]);
@@ -1907,25 +2025,10 @@ async function businessCommand(subcommand, ...args) {
1907
2025
  await quickstart();
1908
2026
  break;
1909
2027
  default:
1910
- console.log('Usage: atris business <command> [args]');
1911
- console.log('');
1912
- console.log(' quickstart ← Start here! 3-command guide');
1913
- console.log('');
1914
- console.log(' init <name> Create a business environment (cloud + local)');
1915
- console.log(' workspace <name> Alias for init');
1916
- console.log(' create <name> Create the cloud business; add --workspace for a local business environment');
1917
- console.log(' add <slug> Register an existing cloud business');
1918
- console.log(' list Show registered businesses');
1919
- console.log(' team [slug] Show members, roles, and admin access');
1920
- console.log(' status <slug> Quick status check');
1921
- console.log(' health [slug] Full health dashboard');
1922
- console.log(' audit Audit all businesses');
1923
- console.log(' connect <service> Connect a skill/integration');
1924
- console.log(' notify <mode> Set notification mode (digest/silent/push)');
1925
- console.log(' deploy <slug> Push local business to cloud');
1926
- console.log(' onboard Seed brief, person, first loop, safe next action, and one-pager from sparse input');
1927
- console.log(' record <report> Append recap state into events, episodes, and scorecards');
1928
- console.log(' remove <slug> Unregister locally');
2028
+ console.error(`Unknown subcommand: ${subcommand}`);
2029
+ console.error('');
2030
+ printBusinessHelp();
2031
+ process.exitCode = 1;
1929
2032
  }
1930
2033
  }
1931
2034