atris 3.55.0 → 3.56.1

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.
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: engines
3
3
  description: "Dispatch work to an installed terminal agent or named Atris engine profile. Supports Atris Fast, Claude, Codex, Cursor, Fable, Composer, Haiku, Devin, Grok, and Antigravity (agy). Triggers on: use codex, use cursor, use devin, use grok, use agy, use antigravity, use fable, use claude, use atris, engine, dispatch to, worker agent, second opinion build."
4
- version: 1.4.0
4
+ version: 1.4.1
5
5
  tags:
6
6
  - engines
7
7
  - claude
@@ -28,6 +28,13 @@ One contract, ten live profiles. The orchestrator writes a bounded task prompt,
28
28
  - build: `atris engine <name> <task-id>`
29
29
  - switch: `atris engine <name>`
30
30
 
31
+ ## Fable means Fable
32
+
33
+ Use `atris engine fable "<bounded question>"` for Fable's take. The canonical
34
+ profile pins `claude-fable-5`, gives deep Fable asks ten minutes by default,
35
+ and records the resolved model in the receipt. An explicit `--model` or
36
+ `--timeout` still wins.
37
+
31
38
  ## Raw binary fallback and debugging
32
39
 
33
40
  Raw spawns are not the default because they skip Atris receipts, watch, and coaching.
@@ -38,7 +45,7 @@ Raw spawns are not the default because they skip Atris receipts, watch, and coac
38
45
  | Claude | `claude -p "<prompt>"` | Uses the local Claude configuration. Add `--model opus` for maximum-depth review or `--model sonnet` for speed. |
39
46
  | Codex | `codex exec --dangerously-bypass-approvals-and-sandbox -o <result-file> "<prompt>"` (run from the target repo/worktree; read-only research: `--sandbox read-only`) | Headless, exits when done — run it as a tracked background Bash task like the other engines and completion auto-wakes the session. Final answer lands in the `-o` file. Verified live 2026-08-11. Outside a git repo add `--skip-git-repo-check` or it exits 1. ONE-SHOT SESSIONS (`claude -p` workers): run codex in the FOREGROUND and wait — a one-shot session never wakes again, so backgrounding strands the result (observed 2026-08-11). The old plugin path (`codex-companion.mjs task --background`) is deprecated for dispatch: its job store never notifies the session (a finished result sat unread overnight, 2026-08-10) |
40
47
  | Cursor | `cursor-agent --trust -p "<prompt>"` (run from the target repo) | Headless print mode; `--trust` required for non-interactive |
41
- | Fable | `claude -p "<prompt>" --model opus` | Maximum-depth Claude review. The `fable` Atris profile is the leader lane and uses the installed Claude CLI. |
48
+ | Fable | `claude -p "<prompt>" --model claude-fable-5` | Raw fallback only. Prefer `atris engine fable` so the model, longer timeout, live log, and receipt stay honest. |
42
49
  | Composer | `atris run "<objective>" --engine composer` | Fast navigator/executor profile routed through the installed `ax` binary. |
43
50
  | Haiku | `claude -p "<prompt>" --model claude-haiku-4-5` | Fast validation and bounded read-only checks. |
44
51
  | Devin | `devin -p --permission-mode dangerous -- "<prompt>"` (run from the target repo) | Default permission mode is read-only for writes — build work NEEDS `--permission-mode dangerous`, so only run it in an isolated worktree. Also `devin cloud` for sessions that outlive this machine. Supports `--model swe-1.7` |
@@ -65,7 +72,8 @@ Each engine CLI can pin a specific model. Current best picks:
65
72
 
66
73
  | Engine | Flag | Best models today |
67
74
  |--------|------|-------------------|
68
- | Claude / Fable | `--model opus` | `opus` currently resolves to Opus 5; use the explicit Opus 4.8 identifier only for reproducibility |
75
+ | Claude | `--model opus` | `opus` follows the local Claude alias; use an explicit model identifier for reproducibility |
76
+ | Fable | `--model claude-fable-5` | Canonical Fable 5 route; `atris engine fable` pins it automatically |
69
77
  | Devin | `--model swe-1.7` | `swe-1.7` (free right now: use it as the volume executor for parallel bounded slices), `swe-1.7-lightning` for speed |
70
78
  | Cursor | `--model cursor-grok-4.6-xhigh` | `cursor-grok-4.6-xhigh` for second-opinion builds, `cursor-grok-4.6-high-fast` for quick pinned asks (answered in ~12s live 2026-08-12), `composer-2.5` for fast edits; parameterized Claude via `'claude-opus-4-8[effort=high]'`; `--list-models` shows the full menu |
71
79
  | Composer | `--engine composer` | `composer 2.5` through the Atris profile |
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: x-search
3
3
  description: "X/Twitter search via xAI Grok API. Use when user wants to search tweets, monitor topics, find viral posts, or run social listening. Costs 5 credits per search. Triggers on x search, tweet search, twitter search, social listening, revenue intel, viral tweets."
4
- version: 2.0.0
4
+ version: 2.2.0
5
5
  tags:
6
6
  - x-search
7
7
  - social
@@ -12,6 +12,25 @@ tags:
12
12
 
13
13
  > Drop this in `~/.claude/skills/x-search/SKILL.md` and Claude Code becomes your X/Twitter intelligence tool.
14
14
 
15
+ ## Feature map
16
+
17
+ Hard path. Follow in order. Soft memory does not override this.
18
+
19
+ ```
20
+ login
21
+ |
22
+ x-search QUERY (5 credits)
23
+ |
24
+ write one Apply (claimable)
25
+ |
26
+ stop
27
+ ```
28
+
29
+ 1. Login: stored login, then `atris login --agent` if needed. Never paste tokens. Never `/auth/cli`.
30
+ 2. Search: `atris x-search "QUERY"`. Always billed (5 credits). Empty or failed search refunds.
31
+ 3. One Apply: name a product change plus a receipt that can fail. If missing, the CLI writes a fill-this stub and a next-line. Search still exits 0.
32
+ 4. Stop. Empty or failed search does not owe an Apply.
33
+
15
34
  ## Customer path (preferred)
16
35
 
17
36
  Logged-in customers should use the CLI. Same auth and billing as `atris youtube process` (5 credits).
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: youtube
3
- description: "YouTube discovery and learning. Use atris youtube search QUERY to get free youtu.be links (local ytsearch/yt-dlp, zero credits). Use atris youtube search --paid QUERY to buy watch permalinks from Atris (5 credits, login required). A YouTube link in any message routes here: run atris youtube notes URL FIRST (free, about 30s, quotes verified). Never summarize a video from model memory. Use atris youtube process only to store it as queryable knowledge (5 credits). Triggers on: youtube search, find videos, paid youtube search, any youtube.com or youtu.be link, youtube, video, watch this, notes on this."
4
- version: 2.7.0
3
+ description: "YouTube discovery and learning. Get watch permalinks with atris youtube search QUERY (free, local ytsearch/yt-dlp). On 429 the CLI already retries; use cached rows if printed, else STOP. Never run --paid after a 429. --paid only when the user explicitly asked to buy permalinks (5 credits). After a URL is picked, atris youtube notes URL (free, ephemeral unless --save). atris youtube process only to store knowledge (5 credits). Never paste tokens. Never /auth/cli. Mint with atris login --agent from a stored login. Never summarize a video from model memory. Triggers on: youtube search, find videos, paid youtube search, any youtube.com or youtu.be link, youtube, video, watch this, notes on this."
4
+ version: 2.11.0
5
5
  tags:
6
6
  - youtube
7
7
  - research
@@ -11,47 +11,56 @@ tags:
11
11
 
12
12
  # YouTube Skill
13
13
 
14
- Three rails. Pick before running anything:
14
+ ## Feature map
15
15
 
16
- - **Search / discovery (free)** → `atris youtube search "<query>"`. Local ytsearch or yt-dlp `ytsearchN:`. Returns title, channel, duration, views, upload_date, and a `youtu.be` link. Zero credits. Use this to *get* video links; do not call process until the user picks one.
17
- - **Paid search (5 credits)** → `atris youtube search --paid "<query>"`. Posts `/youtube/search` with the stored token or a youtube-scope agent mint. Prints watch permalinks, titles, and credits. Use this when the customer wants to buy permalinks the same way as `atris x-search`. Do not replace free search with this.
18
- - **Notes / learning (free)** → `atris youtube notes <url>`. Local captions + a fast engine, about 30 seconds, quotes verified against the transcript. Use when the goal is to learn from a video you already have.
19
- - **Process / product (5 credits)** → `atris youtube process <url>`. Credits-billed cloud knowledge store. Use when a customer/agent needs the video stored as Atris knowledge.
16
+ Hard path. Follow in order. Soft memory does not override this.
20
17
 
21
- If the user says "find videos", "search youtube", or "get youtube links" → search. If they say "learn from", "notes on", "alpha", or "rabbit hole" → notes. If they say "process", "store", "add to knowledge" → process.
18
+ ```
19
+ search QUERY (free)
20
+ |
21
+ 429 --> CLI already retried once
22
+ --> cached rows printed? use them
23
+ --> rate-limit sentence printed? STOP
24
+ do not run --paid
25
+ |
26
+ pick a URL --> notes URL (free, ephemeral unless --save)
27
+ | --save files the brief + apply stub
28
+ | or teach URL [--section N] (one chapter: claim numbers, named mechanisms, one check; free unless --save)
29
+ |
30
+ write one Apply (claimable) before process
31
+ |
32
+ store knowledge? --> process URL (5 credits)
33
+
34
+ --paid QUERY only if the user asked to buy permalinks
35
+ login: atris login --agent from a stored login
36
+ never paste tokens, never /auth/cli
37
+ ```
38
+
39
+ 1. Get watch permalinks: `atris youtube search QUERY` (free).
40
+ 2. If 429: wait/retry is already in the CLI. If it prints cached rows, use those. If it prints `youtube rate-limited local search. do not use --paid as a fallback; retry later.`, STOP. Do not run `--paid`.
41
+ 3. `--paid` only when the user explicitly asked to buy permalinks. The CLI hard-refuses `--paid` when the free cache still has a fresh same-query hit.
42
+ 4. `atris youtube notes URL` after a URL is picked (free). Notes is ephemeral unless `--save`.
43
+ 5. Write one Apply (change + receipt) before `atris youtube process`. Process still requires a filled Apply (so you `--save`, fill Apply, then process).
44
+ 6. Never paste tokens. Never `/auth/cli`. Mint with `atris login --agent` from a stored login.
45
+
46
+ If the user says "find videos", "search youtube", or "get youtube links" → search. If they say "learn from", "notes on", "alpha", or "rabbit hole" → notes. If they say "process", "store", "add to knowledge" → process. "Buy permalinks" or "paid search" is the only ask that unlocks `--paid`.
22
47
 
23
48
  Never summarize a video from model memory; that is fabrication.
24
49
 
25
- ## Bootstrap (ALWAYS Run First)
50
+ ## Bootstrap
51
+
52
+ Search and notes need no login. Paid search and process need a stored login, then `atris login --agent`. Never print credentials. Never `/auth/cli`.
26
53
 
27
54
  ```bash
28
55
  #!/bin/bash
29
56
  set -e
30
57
 
31
- # 1. Check atris CLI
32
58
  if ! command -v atris &> /dev/null; then
33
59
  echo "Installing atris CLI..."
34
60
  npm install -g atris
35
61
  fi
36
62
 
37
- # 2. Optional login (required only for process / credits)
38
- if [ ! -f ~/.atris/credentials.json ]; then
39
- echo "Not logged in. Search and notes still work. Process needs: atris login"
40
- fi
41
-
42
- # 3. Extract token when present
43
- if [ -f ~/.atris/credentials.json ]; then
44
- if command -v node &> /dev/null; then
45
- TOKEN=$(node -e "console.log(require('$HOME/.atris/credentials.json').token)")
46
- elif command -v python3 &> /dev/null; then
47
- TOKEN=$(python3 -c "import json,os; print(json.load(open(os.path.expanduser('~/.atris/credentials.json')))['token'])")
48
- elif command -v jq &> /dev/null; then
49
- TOKEN=$(jq -r '.token' ~/.atris/credentials.json)
50
- fi
51
- export ATRIS_TOKEN="$TOKEN"
52
- fi
53
-
54
- echo "Ready. YouTube skill active (search + notes free; process 5 credits)."
63
+ echo "Ready. Feature map: free search, 429 cache-or-stop, notes, then process."
55
64
  ```
56
65
 
57
66
  ---
@@ -66,14 +75,18 @@ atris youtube search "MCP agents" --json
66
75
 
67
76
  Uses `ytsearch` on PATH when present, else bundled `scripts/det/ytsearch`, else `yt-dlp --flat-playlist --print` with `ytsearchN:`. No credits. No `/agent/process_youtube` call.
68
77
 
69
- ## Paid search (5 credits)
78
+ On 429 the CLI retries once, then serves `~/.atris/youtube-search-cache.json` if the same query is younger than one hour. If it prints the rate-limit sentence, stop. Do not run `--paid`.
79
+
80
+ ## Paid search (5 credits, opt-in buy only)
81
+
82
+ Only when the user explicitly asked to buy permalinks. A 429 or a missing cache is not that ask.
70
83
 
71
84
  ```bash
72
85
  atris youtube search --paid "MCP agents 2026"
73
86
  atris youtube search --paid "MCP agents" --limit 10
74
87
  ```
75
88
 
76
- Requires login. Uses the stored token, or mints a youtube-scope agent token from disk the same way as `atris youtube process` and `atris x-search`. Never `/auth/cli`. Prints `title | watch permalink` plus credits. Empty or failed searches refund.
89
+ Requires a stored login, then `atris login --agent`. The CLI mints a youtube-scope agent token from disk the same way as `atris youtube process` and `atris x-search`. Never `/auth/cli`. Never paste tokens. Prints `title | watch permalink` plus credits. Empty or failed searches refund.
77
90
 
78
91
  Line contract:
79
92
 
@@ -90,10 +103,7 @@ title | channel | duration | views | upload_date | https://youtu.be/ID
90
103
  Base: `https://api.atris.ai/api`
91
104
  Auth: `-H "Authorization: Bearer $TOKEN"`
92
105
 
93
- ### Get Token
94
- ```bash
95
- TOKEN=$(node -e "console.log(require('$HOME/.atris/credentials.json').token)")
96
- ```
106
+ Do not extract or paste tokens. The CLI reads the stored login. Agents mint with `atris login --agent`.
97
107
 
98
108
  ### Process a Video
99
109
  ```bash
@@ -203,7 +213,7 @@ Two layers, never mixed. The reply the person reads is flowing prose: ideas, spe
203
213
 
204
214
  `atris youtube search` shells to local ytsearch/yt-dlp and never hits the Atris API.
205
215
 
206
- `atris youtube search --paid` posts `{query, limit}` to `/youtube/search` with bearer auth. Agent tokens need the youtube scope.
216
+ `atris youtube search --paid` posts `{query, limit}` to `/youtube/search` with bearer auth. Agent tokens need the youtube scope. This is an opt-in buy, not a 429 fallback.
207
217
 
208
218
  `atris youtube` process first tries local transcript extraction with `yt-dlp`. It sends timestamped `transcript_text` to `/agent/process_youtube` with `cache_transcript=false`. If local transcript processing fails with a retryable error, it falls back to cloud video processing. Use `--json` to inspect `metadata.processing_method` and `metadata.transcript_source`.
209
219
 
@@ -225,35 +235,33 @@ Two layers, never mixed. The reply the person reads is flowing prose: ideas, spe
225
235
 
226
236
  | Error | Meaning | Fix |
227
237
  |-------|---------|-----|
228
- | `401` | Token expired/invalid | `atris login --force` |
238
+ | `401` | Token expired/invalid | `atris login --agent` from a stored login |
229
239
  | `402` | Not enough credits | Check balance, purchase at atris.ai |
230
240
  | `400` | Invalid YouTube URL | Check URL format |
231
241
  | `502` | Transcript or cloud processing failed | Retry; credits auto-refunded when backend fails |
232
242
  | search exit 2 | ytsearch/yt-dlp missing or no results | Install yt-dlp, or put ytsearch on PATH |
233
- | search 429 | YouTube rate-limited local search | serve last same-query free rows if younger than one hour; otherwise retry later; do not use --paid |
243
+ | search 429 | YouTube rate-limited local search | CLI already retried; use cached rows if printed; if the rate-limit sentence prints, STOP; do not use --paid |
234
244
 
235
245
  ---
236
246
 
237
247
  ## Quick Reference
238
248
 
239
249
  ```bash
240
- # Setup (once)
250
+ # Setup: human stores a login, then agents mint. never /auth/cli
241
251
  npm install -g atris && atris login
252
+ atris login --agent
242
253
 
243
254
  # Free: discover videos
244
255
  atris youtube search "MCP agents 2026"
245
256
  atris youtube search "MCP agents" --limit 10
246
257
 
247
- # Paid: watch permalinks from Atris (5 credits)
258
+ # Paid: only if the user asked to buy permalinks (5 credits). never a 429 fallback
248
259
  atris youtube search --paid "MCP agents 2026"
249
260
 
250
261
  # Free: notes on a URL you already have
251
262
  atris youtube notes "https://youtu.be/VIDEO_ID"
252
263
 
253
- # Get token (process only)
254
- TOKEN=$(node -e "console.log(require('$HOME/.atris/credentials.json').token)")
255
-
256
- # Process a video (5 credits)
264
+ # Process a video (5 credits). different store from paid search
257
265
  atris youtube process "https://youtube.com/watch?v=..." --query "Create a outline (flowing, idea-first) and action brief"
258
266
 
259
267
  # Process + store to agent knowledge
package/bin/atris.js CHANGED
@@ -47,7 +47,7 @@ const {
47
47
  requireAccountBound,
48
48
  refuseAccountGlobal,
49
49
  } = require('../lib/account-bound');
50
- const { isHelpToken, argsWantHelp } = require('../lib/noninteractive');
50
+ const { isHelpToken, argsWantHelp, hasYesFlag } = require('../lib/noninteractive');
51
51
 
52
52
  // State detection for smart default
53
53
  const { detectWorkspaceState, loadContext } = require('../lib/state-detection');
@@ -56,12 +56,20 @@ const {
56
56
  createStarterTask,
57
57
  shouldGatherContext,
58
58
  isAtrisMetaQuestion,
59
+ isFlagLikeAnswer,
59
60
  renderPrompt: renderContextGathererPrompt,
60
61
  } = require('../lib/context-gatherer');
61
62
  const {
62
63
  buildFirstMinute,
64
+ firstTalkJson,
65
+ folderName,
63
66
  freshMinuteJson,
64
67
  isBareAtrisFlag,
68
+ isFirstTalkLine,
69
+ isLeftoverVerbLook,
70
+ listUserVisibleWork,
71
+ personName,
72
+ renderFirstTalk,
65
73
  shouldAutoInitFresh,
66
74
  } = require('../lib/first-minute');
67
75
 
@@ -484,17 +492,14 @@ function showStartHelp() {
484
492
  }
485
493
 
486
494
  function showHelpShort() {
487
- console.log('atris: you say what you want; atris builds it, checks it, and shows proof.');
488
- console.log('');
489
- console.log('Golden path:');
490
- console.log(' atris init [--yes] [--minimal] scaffold this project');
491
- console.log(' atris load context (MAP, tasks, journal)');
492
- console.log(' atris task new "..." file work');
493
- console.log(' atris task claim <id> --as <m> take ownership');
494
- console.log(' atris task ready <id> --proof "..."');
495
- console.log(' atris review validate');
496
- console.log(' atris recap spoken recap of what landed');
497
- console.log(' atris mission status active goals + next move');
495
+ console.log('atris: you say what you want. already won. one next step.');
496
+ console.log('');
497
+ console.log(' atris "what do you want here?" empty folder');
498
+ console.log(' atris later "..." remember without a room');
499
+ console.log(' atris do work already here');
500
+ console.log(' atris spaceship Keep working for a few hours');
501
+ console.log(' atris autopilot Keep working until you stop');
502
+ console.log(' atris mission the one goal');
498
503
  console.log('');
499
504
  console.log('More: atris help --all · machine list: atris help --json');
500
505
  }
@@ -506,7 +511,7 @@ function helpCatalog() {
506
511
  { name: 'review', summary: 'validate work and capture learnings', json: false, interactive_risk: 'low' },
507
512
  { name: 'recap', summary: 'spoken recap of what your team did', json: true, interactive_risk: 'low' },
508
513
  { name: 'mission', summary: 'durable goal + owner + verifier + tick', json: true, interactive_risk: 'medium' },
509
- { name: 'log', summary: 'append an idea to today inbox (or --repl)', json: false, interactive_risk: 'medium' },
514
+ { name: 'log', summary: 'append an idea to today inbox (or --repl)', json: true, interactive_risk: 'low' },
510
515
  { name: 'wish', summary: 'headless inbox / mission intake', json: true, interactive_risk: 'low' },
511
516
  { name: 'learn', summary: 'structured project learnings', json: false, interactive_risk: 'low' },
512
517
  { name: 'activate', summary: 'load atris context for this session', json: false, interactive_risk: 'low' },
@@ -517,38 +522,16 @@ function helpCatalog() {
517
522
  }
518
523
 
519
524
  function showHelpAll() {
525
+ console.log('you say what you want. already won. one next step.');
520
526
  console.log('');
521
- console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
522
- console.log('atris');
523
- console.log('you say what you want in plain words. atris builds it, checks it, and shows you proof.');
524
- console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
525
- console.log('');
526
- console.log('Quick start:');
527
- console.log('');
528
- console.log(' 1. atris load context (MAP, tasks, journal)');
529
- console.log(' npx atris same after a local install');
530
- console.log(' 2. say what you want built, checked, or researched');
531
- console.log(' 3. atris files the work, then you review');
532
- console.log('');
533
- console.log('Common invocations:');
534
- console.log(' atris "<request>" one isolated change, then Review');
535
- console.log(' atris "<request>" --verify "<cmd>" --json');
536
- console.log(' atris init [--yes] scaffold this project');
537
- console.log(' npx atris init [--yes] same after a local install');
538
- console.log(' atris mission status current mission + next move');
539
- console.log(' atris task ready <id> --proof "..."');
540
- console.log(' atris computer');
541
- console.log(' atris engine validate latest');
542
- console.log(' atris playbook workspace style rules and paired trials');
543
- console.log(' atris business init "My Company"');
544
- console.log(' atris run');
545
- console.log(' atris drill');
546
- console.log(' atris status');
547
- console.log(' atris soul');
548
- console.log(' atris fleet status');
549
- console.log(' ask/stop/ready see atris mission and atris task ready');
550
- console.log('');
551
- console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
527
+ console.log(' atris later "..." remember without a room');
528
+ console.log(' atris do work already here');
529
+ console.log(' atris spaceship Keep working for a few hours');
530
+ console.log(' atris autopilot Keep working until you stop');
531
+ console.log(' atris mission the one goal');
532
+ console.log(' atris recap what got done');
533
+ console.log(' atris review check the work');
534
+ console.log(' atris stop stop the live goal');
552
535
  console.log('');
553
536
  console.log('Atris Computers:');
554
537
  console.log(' Owner = User | Business');
@@ -765,7 +748,7 @@ function showHelpAll() {
765
748
  console.log('');
766
749
  console.log('Other:');
767
750
  console.log(' version - Show Atris version');
768
- console.log(' help - Short golden path (help --all for every command)');
751
+ console.log(' help - you say what you want (help --all for every command)');
769
752
  console.log('');
770
753
  console.log('Tip: Just run "atris" to get started');
771
754
  console.log('');
@@ -1344,13 +1327,13 @@ function printFirstUseNext() {
1344
1327
  console.log(`Then: ${firstUseCommand()}`);
1345
1328
  }
1346
1329
 
1347
- function runMinimalInit() {
1330
+ function runMinimalInit({ silent = false } = {}) {
1348
1331
  const script = process.argv[1] || path.join(__dirname, 'atris.js');
1349
1332
  const result = spawnSync(process.execPath, [script, 'init', '--minimal', '--yes'], {
1350
1333
  cwd: process.cwd(),
1351
1334
  env: process.env,
1352
1335
  encoding: 'utf8',
1353
- stdio: 'inherit',
1336
+ stdio: silent ? ['ignore', 'pipe', 'pipe'] : 'inherit',
1354
1337
  });
1355
1338
  return Number.isInteger(result.status) ? result.status : 1;
1356
1339
  }
@@ -1366,16 +1349,56 @@ async function interactiveEntry(userInput, options = {}) {
1366
1349
  const workspaceDir = process.cwd();
1367
1350
  const state = detectWorkspaceState(workspaceDir);
1368
1351
 
1369
- // Fresh folder is the first-minute product: one next command, no menu.
1352
+ // Fresh folder is a conversation, not a scaffold form.
1370
1353
  if (state.state === 'fresh') {
1371
- if (options.asJson) {
1372
- console.log(JSON.stringify(freshMinuteJson(), null, 2));
1373
- return 2;
1354
+ const request = String(userInput || '').trim();
1355
+ const room = folderName(workspaceDir);
1356
+ const who = personName();
1357
+ if (!request) {
1358
+ if (options.asJson) {
1359
+ console.log(JSON.stringify(freshMinuteJson(room, listUserVisibleWork(workspaceDir), { root: workspaceDir }), null, 2));
1360
+ return 2;
1361
+ }
1362
+ if (shouldAutoInitFresh(process.argv.slice(2))) {
1363
+ return runMinimalInit();
1364
+ }
1365
+ printFirstMinuteScreen({ root: workspaceDir, fresh: true });
1366
+ return 0;
1374
1367
  }
1375
- if (shouldAutoInitFresh(process.argv.slice(2))) {
1376
- return runMinimalInit();
1368
+ if (isLeftoverVerbLook(request)) {
1369
+ if (options.asJson) {
1370
+ console.log(JSON.stringify(freshMinuteJson(room, listUserVisibleWork(workspaceDir), { root: workspaceDir }), null, 2));
1371
+ return 2;
1372
+ }
1373
+ printFirstMinuteScreen({ root: workspaceDir, fresh: true });
1374
+ return 0;
1377
1375
  }
1378
- printFirstMinuteScreen({ root: workspaceDir, fresh: true });
1376
+ if (!isFirstTalkLine(request) && isAtrisMetaQuestion(request)) {
1377
+ if (options.asJson) {
1378
+ console.log(JSON.stringify({
1379
+ schema: 'atris.overview.v1',
1380
+ ok: true,
1381
+ product: 'Atris',
1382
+ }, null, 2));
1383
+ return 0;
1384
+ }
1385
+ printAtrisOverview();
1386
+ return 0;
1387
+ }
1388
+ if (isFlagLikeAnswer(request)) {
1389
+ printFirstMinuteScreen({ root: workspaceDir, fresh: true });
1390
+ return 0;
1391
+ }
1392
+ const initStatus = runMinimalInit({ silent: true });
1393
+ if (initStatus !== 0) return initStatus;
1394
+ saveContextProfile(workspaceDir, request, { source: 'first_talk' });
1395
+ const starter = createStarterTask(workspaceDir, request);
1396
+ if (options.asJson) {
1397
+ console.log(JSON.stringify(firstTalkJson({ starter, person: who, folder: room }), null, 2));
1398
+ return 0;
1399
+ }
1400
+ console.log('');
1401
+ console.log(renderFirstTalk({ person: who, folder: room, starter }));
1379
1402
  return 0;
1380
1403
  }
1381
1404
 
@@ -1921,18 +1944,14 @@ if (command === 'init') {
1921
1944
  .catch((err) => { console.error(`\n✗ Error: ${err.message || err}`); process.exit(1); });
1922
1945
  } else if (command === 'land') {
1923
1946
  // landCommand can throw synchronously on empty repos; wrap so callers get a
1924
- // one-line error instead of an uncaught stack from Promise.resolve(fn()).
1947
+ // one-line sentence instead of an uncaught stack from Promise.resolve(fn()).
1925
1948
  Promise.resolve()
1926
1949
  .then(() => require('../commands/land').landCommand(process.argv.slice(3)))
1927
- .then((code) => process.exit(code || 0))
1950
+ .then((code) => process.exit(typeof code === 'number' ? code : 0))
1928
1951
  .catch((err) => {
1929
- const msg = String(err && err.message || err || '');
1930
- if (/no master\/main|no commits|not a git/i.test(msg)) {
1931
- console.error(msg);
1932
- } else {
1933
- console.error(`land: ${msg}`);
1934
- }
1935
- process.exit(1);
1952
+ const msg = String(err && err.message || err || 'land could not run').split('\n')[0];
1953
+ console.log(msg);
1954
+ process.exit(2);
1936
1955
  });
1937
1956
  } else if (command === 'caretaker') {
1938
1957
  Promise.resolve(require('../commands/caretaker').caretakerCommand(process.argv.slice(3)))
@@ -2035,7 +2054,6 @@ if (command === 'init') {
2035
2054
  } else if (command === 'log') {
2036
2055
  const logArgs = process.argv.slice(3);
2037
2056
  const subcommand = logArgs[0];
2038
- const positional = logArgs.filter((arg) => !String(arg).startsWith('-'));
2039
2057
  if (subcommand === 'sync') {
2040
2058
  require('../commands/log-sync').logSyncAtris()
2041
2059
  .then(() => process.exit(0))
@@ -2044,24 +2062,16 @@ if (command === 'init') {
2044
2062
  process.exit(1);
2045
2063
  });
2046
2064
  } else if (subcommand === '--help' || subcommand === '-h' || subcommand === 'help') {
2047
- console.log('Usage: atris log ["note"] | --repl | sync | <business-slug> | help');
2065
+ console.log('Usage: atris log ["note"] [--json] | --repl | sync | help');
2048
2066
  console.log('');
2049
2067
  console.log(' atris log "an idea" Append a note to today\'s ## Inbox');
2068
+ console.log(' atris log text One word still lands in Inbox. No live business required');
2069
+ console.log(' atris log "an idea" --json');
2050
2070
  console.log(' atris log --repl Open today\'s journal REPL');
2051
2071
  console.log(' atris log Open the REPL (needs a terminal)');
2052
2072
  console.log(' atris log sync Sync the local journal to cloud');
2053
- console.log(' atris log <slug> Show business log history (single slug token)');
2054
2073
  console.log(' atris wish "..." Headless inbox write; add --json --no-mission for agents');
2055
2074
  process.exit(0);
2056
- } else if (
2057
- positional.length === 1
2058
- && !logArgs.includes('--repl')
2059
- && /^[a-z0-9][a-z0-9_-]{0,63}$/i.test(positional[0])
2060
- ) {
2061
- // Business log: atris log <business-slug> (single slug token only)
2062
- require('../commands/context-sync').businessLog(positional[0])
2063
- .then(() => process.exit(0))
2064
- .catch((err) => { console.error(`\n✗ Error: ${err.message || err}`); process.exit(1); });
2065
2075
  } else {
2066
2076
  logCmd();
2067
2077
  }
@@ -2084,13 +2094,14 @@ if (command === 'init') {
2084
2094
  }
2085
2095
  } else if (command === 'now') {
2086
2096
  const args = process.argv.slice(3);
2087
- if (args.includes('--help') || args.includes('-h') || args[0] === 'help') {
2088
- require('../commands/now').nowAtris(args);
2089
- process.exit(0);
2090
- }
2091
- require('../commands/now').nowAtris(args);
2097
+ const nowCode = require('../commands/now').nowAtris(args);
2098
+ process.exit(Number.isInteger(nowCode) ? nowCode : 0);
2092
2099
  } else if (command === 'recap') {
2093
- require('../commands/recap').recapAtris(process.argv.slice(3));
2100
+ const recapCode = require('../commands/recap').recapAtris(process.argv.slice(3));
2101
+ if (Number.isInteger(recapCode)) process.exit(recapCode);
2102
+ } else if (command === 'later') {
2103
+ const laterCode = require('../commands/later').laterAtris(process.argv.slice(3));
2104
+ process.exit(Number.isInteger(laterCode) ? laterCode : 0);
2094
2105
  } else if (command === 'activate') {
2095
2106
  const args = process.argv.slice(3);
2096
2107
  if (args.includes('--help') || args.includes('-h') || args[0] === 'help') {
@@ -2463,7 +2474,21 @@ if (command === 'init') {
2463
2474
  process.exit(code);
2464
2475
  } else if (command === 'autopilot') {
2465
2476
  const args = process.argv.slice(3);
2466
- applyRunnerFlags(args);
2477
+ const { isUnboundScratchFolder, refuseUnboundScratch } = require('../lib/scratch-root');
2478
+ const { resolveWorkspaceRoot } = require('../lib/mission-root');
2479
+ const autopilotRoot = resolveWorkspaceRoot(process.cwd());
2480
+ const autopilotHelp = argsWantHelp(args) || args[0] === 'help';
2481
+ const autopilotControl = args[0] === 'stop' || args[0] === 'status';
2482
+ const autopilotJsonPlan = args.includes('--json') && !args.includes('--yes') && !args.includes('-y');
2483
+ const autopilotTalkOnly = !hasYesFlag(args) && !args.includes('--auto') && !args.includes('--legacy');
2484
+ // applyRunnerFlags seeds .atris/state/engines.json. Help, stop/status, JSON
2485
+ // plan, and an unbound scratch talk must not mint a room. --yes is not an unlock.
2486
+ if (!autopilotHelp && !autopilotControl && !autopilotJsonPlan && !autopilotTalkOnly && isUnboundScratchFolder(autopilotRoot)) {
2487
+ process.exit(refuseUnboundScratch());
2488
+ }
2489
+ if (!autopilotHelp && !isUnboundScratchFolder(autopilotRoot)) {
2490
+ applyRunnerFlags(args);
2491
+ }
2467
2492
 
2468
2493
  if (args.includes('--legacy')) {
2469
2494
  const legacyArgs = args.filter(a => a !== '--legacy');
@@ -2739,8 +2764,9 @@ if (command === 'init') {
2739
2764
  const raw = process.argv.slice(3);
2740
2765
  const { gmailCommand, extractGmailMailboxAccount } = require('../commands/integrations');
2741
2766
  const mailbox = extractGmailMailboxAccount(raw);
2742
- const gateInput = mailbox.mailboxAccountId ? ['--account', ...mailbox.args] : mailbox.args;
2743
- const gate = requireAccountBound(gateInput);
2767
+ // Mailbox --account <id> is a selector, not the workspace opt-in.
2768
+ // Scratch folders refuse every gmail verb the same way as slack/inbox.
2769
+ const gate = requireAccountBound(mailbox.args.filter((arg) => arg !== '--account'));
2744
2770
  if (!gate.ok) {
2745
2771
  process.exit(refuseAccountGlobal());
2746
2772
  return;
@@ -2775,9 +2801,15 @@ if (command === 'init') {
2775
2801
  } else if (command === 'slack') {
2776
2802
  {
2777
2803
  const raw = process.argv.slice(3);
2778
- const gate = requireAccountBound(raw);
2779
- if (!gate.ok) process.exit(refuseAccountGlobal());
2780
- const { slackCommand } = require('../commands/integrations');
2804
+ const { slackCommand, extractGmailMailboxAccount } = require('../commands/integrations');
2805
+ const account = extractGmailMailboxAccount(raw);
2806
+ // Account --account <id> is a selector, not the workspace opt-in.
2807
+ // Scratch folders refuse every slack verb the same way as gmail/inbox.
2808
+ const gate = requireAccountBound(account.args.filter((arg) => arg !== '--account'));
2809
+ if (!gate.ok) {
2810
+ process.exit(refuseAccountGlobal());
2811
+ return;
2812
+ }
2781
2813
  const subcommand = gate.args[0];
2782
2814
  const args = gate.args.slice(1);
2783
2815
  slackCommand(subcommand, ...args)