atris 3.56.0 → 3.56.2

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.
Files changed (45) hide show
  1. package/atris/skills/engines/SKILL.md +11 -3
  2. package/atris/skills/x-search/SKILL.md +20 -1
  3. package/atris/skills/youtube/SKILL.md +51 -43
  4. package/bin/atris.js +134 -90
  5. package/commands/auth.js +6 -1
  6. package/commands/autopilot-front.js +29 -13
  7. package/commands/brainstorm.js +77 -476
  8. package/commands/business.js +13 -1
  9. package/commands/engine.js +7 -0
  10. package/commands/experiments.js +178 -0
  11. package/commands/fleet-report.js +2 -2
  12. package/commands/founder.js +12 -0
  13. package/commands/human-missions.js +64 -2
  14. package/commands/init.js +2 -35
  15. package/commands/integrations.js +100 -0
  16. package/commands/land.js +53 -23
  17. package/commands/later.js +52 -0
  18. package/commands/launchpad.js +45 -10
  19. package/commands/log.js +79 -30
  20. package/commands/mission.js +117 -22
  21. package/commands/next.js +153 -63
  22. package/commands/now.js +115 -38
  23. package/commands/recap.js +97 -4
  24. package/commands/run-front.js +20 -5
  25. package/commands/spaceship.js +52 -12
  26. package/commands/status.js +38 -7
  27. package/commands/task.js +56 -19
  28. package/commands/terminal.js +5 -5
  29. package/commands/workflow.js +19 -5
  30. package/commands/x-search.js +123 -13
  31. package/commands/youtube.js +941 -36
  32. package/lib/account-bound.js +7 -0
  33. package/lib/apply-gate.js +102 -0
  34. package/lib/config-guard.js +106 -0
  35. package/lib/context-gatherer.js +55 -8
  36. package/lib/engine-ask.js +16 -6
  37. package/lib/engine-registry.js +14 -4
  38. package/lib/first-minute.js +571 -26
  39. package/lib/known-commands.js +1 -1
  40. package/lib/pack-capabilities.js +13 -3
  41. package/lib/runner-command.js +5 -3
  42. package/lib/scratch-root.js +44 -0
  43. package/lib/workspace-scaffold.js +3 -1
  44. package/package.json +1 -1
  45. package/utils/auth.js +84 -6
@@ -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
 
@@ -203,7 +211,7 @@ function parseNaturalEntryArgs(args = []) {
203
211
  };
204
212
  }
205
213
 
206
- function applyRunnerFlags(args) {
214
+ function applyRunnerFlags(args, options = {}) {
207
215
  // --engine <name> is the operator-facing spelling of --runner-profile:
208
216
  // one flag rents a specific intelligence for this run.
209
217
  const engineFlag = readOptionArg(args, '--engine');
@@ -243,6 +251,9 @@ function applyRunnerFlags(args) {
243
251
  process.env.ATRIS_RUNNER_MODEL = runnerModel;
244
252
  process.env.ATRIS_CLAUDE_MODEL = runnerModel;
245
253
  }
254
+ // Talk-only and unbound scratch still validate --engine / --runner-profile.
255
+ // They skip this seed so resolveDefaultEngine cannot mint engines.json.
256
+ if (options.seed === false) return;
246
257
  // No per-run choice and no env: the workspace's saved engine
247
258
  // (.atris/engine.json, written by `atris engine <name>`) becomes the
248
259
  // profile for every loop spawn. For the loop commands themselves, fall all
@@ -484,17 +495,14 @@ function showStartHelp() {
484
495
  }
485
496
 
486
497
  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');
498
+ console.log('atris: you say what you want. already won. one next step.');
499
+ console.log('');
500
+ console.log(' atris "what do you want here?" empty folder');
501
+ console.log(' atris later "..." remember without a room');
502
+ console.log(' atris do work already here');
503
+ console.log(' atris spaceship Keep working for a few hours');
504
+ console.log(' atris autopilot Keep working until you stop');
505
+ console.log(' atris mission the one goal');
498
506
  console.log('');
499
507
  console.log('More: atris help --all · machine list: atris help --json');
500
508
  }
@@ -506,7 +514,7 @@ function helpCatalog() {
506
514
  { name: 'review', summary: 'validate work and capture learnings', json: false, interactive_risk: 'low' },
507
515
  { name: 'recap', summary: 'spoken recap of what your team did', json: true, interactive_risk: 'low' },
508
516
  { 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' },
517
+ { name: 'log', summary: 'append an idea to today inbox (or --repl)', json: true, interactive_risk: 'low' },
510
518
  { name: 'wish', summary: 'headless inbox / mission intake', json: true, interactive_risk: 'low' },
511
519
  { name: 'learn', summary: 'structured project learnings', json: false, interactive_risk: 'low' },
512
520
  { name: 'activate', summary: 'load atris context for this session', json: false, interactive_risk: 'low' },
@@ -517,38 +525,16 @@ function helpCatalog() {
517
525
  }
518
526
 
519
527
  function showHelpAll() {
528
+ console.log('you say what you want. already won. one next step.');
520
529
  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('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
530
+ console.log(' atris later "..." remember without a room');
531
+ console.log(' atris do work already here');
532
+ console.log(' atris spaceship Keep working for a few hours');
533
+ console.log(' atris autopilot Keep working until you stop');
534
+ console.log(' atris mission the one goal');
535
+ console.log(' atris recap what got done');
536
+ console.log(' atris review check the work');
537
+ console.log(' atris stop stop the live goal');
552
538
  console.log('');
553
539
  console.log('Atris Computers:');
554
540
  console.log(' Owner = User | Business');
@@ -649,6 +635,8 @@ function showHelpAll() {
649
635
  console.log(' experiments init [slug] - Prepare atris/experiments/ or scaffold a pack');
650
636
  console.log(' experiments validate - Validate experiment packs');
651
637
  console.log(' experiments run <slug> - Execute a pack or record an Endstate receipt');
638
+ console.log(' experiments keep <slug> - Keep a pack only when measure.py moves 0 to 1');
639
+ console.log(' experiments revert <slug> - Run a pack reset.py after a refused keep');
652
640
  console.log(' experiments benchmark [m] - Run validate/runtime experiment benchmarks');
653
641
  console.log(' bench - run core benchmark gates');
654
642
  console.log('');
@@ -765,7 +753,7 @@ function showHelpAll() {
765
753
  console.log('');
766
754
  console.log('Other:');
767
755
  console.log(' version - Show Atris version');
768
- console.log(' help - Short golden path (help --all for every command)');
756
+ console.log(' help - you say what you want (help --all for every command)');
769
757
  console.log('');
770
758
  console.log('Tip: Just run "atris" to get started');
771
759
  console.log('');
@@ -1344,13 +1332,13 @@ function printFirstUseNext() {
1344
1332
  console.log(`Then: ${firstUseCommand()}`);
1345
1333
  }
1346
1334
 
1347
- function runMinimalInit() {
1335
+ function runMinimalInit({ silent = false } = {}) {
1348
1336
  const script = process.argv[1] || path.join(__dirname, 'atris.js');
1349
1337
  const result = spawnSync(process.execPath, [script, 'init', '--minimal', '--yes'], {
1350
1338
  cwd: process.cwd(),
1351
1339
  env: process.env,
1352
1340
  encoding: 'utf8',
1353
- stdio: 'inherit',
1341
+ stdio: silent ? ['ignore', 'pipe', 'pipe'] : 'inherit',
1354
1342
  });
1355
1343
  return Number.isInteger(result.status) ? result.status : 1;
1356
1344
  }
@@ -1366,16 +1354,56 @@ async function interactiveEntry(userInput, options = {}) {
1366
1354
  const workspaceDir = process.cwd();
1367
1355
  const state = detectWorkspaceState(workspaceDir);
1368
1356
 
1369
- // Fresh folder is the first-minute product: one next command, no menu.
1357
+ // Fresh folder is a conversation, not a scaffold form.
1370
1358
  if (state.state === 'fresh') {
1371
- if (options.asJson) {
1372
- console.log(JSON.stringify(freshMinuteJson(), null, 2));
1373
- return 2;
1359
+ const request = String(userInput || '').trim();
1360
+ const room = folderName(workspaceDir);
1361
+ const who = personName();
1362
+ if (!request) {
1363
+ if (options.asJson) {
1364
+ console.log(JSON.stringify(freshMinuteJson(room, listUserVisibleWork(workspaceDir), { root: workspaceDir }), null, 2));
1365
+ return 2;
1366
+ }
1367
+ if (shouldAutoInitFresh(process.argv.slice(2))) {
1368
+ return runMinimalInit();
1369
+ }
1370
+ printFirstMinuteScreen({ root: workspaceDir, fresh: true });
1371
+ return 0;
1374
1372
  }
1375
- if (shouldAutoInitFresh(process.argv.slice(2))) {
1376
- return runMinimalInit();
1373
+ if (isLeftoverVerbLook(request)) {
1374
+ if (options.asJson) {
1375
+ console.log(JSON.stringify(freshMinuteJson(room, listUserVisibleWork(workspaceDir), { root: workspaceDir }), null, 2));
1376
+ return 2;
1377
+ }
1378
+ printFirstMinuteScreen({ root: workspaceDir, fresh: true });
1379
+ return 0;
1377
1380
  }
1378
- printFirstMinuteScreen({ root: workspaceDir, fresh: true });
1381
+ if (!isFirstTalkLine(request) && isAtrisMetaQuestion(request)) {
1382
+ if (options.asJson) {
1383
+ console.log(JSON.stringify({
1384
+ schema: 'atris.overview.v1',
1385
+ ok: true,
1386
+ product: 'Atris',
1387
+ }, null, 2));
1388
+ return 0;
1389
+ }
1390
+ printAtrisOverview();
1391
+ return 0;
1392
+ }
1393
+ if (isFlagLikeAnswer(request)) {
1394
+ printFirstMinuteScreen({ root: workspaceDir, fresh: true });
1395
+ return 0;
1396
+ }
1397
+ const initStatus = runMinimalInit({ silent: true });
1398
+ if (initStatus !== 0) return initStatus;
1399
+ saveContextProfile(workspaceDir, request, { source: 'first_talk' });
1400
+ const starter = createStarterTask(workspaceDir, request);
1401
+ if (options.asJson) {
1402
+ console.log(JSON.stringify(firstTalkJson({ starter, person: who, folder: room }), null, 2));
1403
+ return 0;
1404
+ }
1405
+ console.log('');
1406
+ console.log(renderFirstTalk({ person: who, folder: room, starter }));
1379
1407
  return 0;
1380
1408
  }
1381
1409
 
@@ -1921,18 +1949,14 @@ if (command === 'init') {
1921
1949
  .catch((err) => { console.error(`\n✗ Error: ${err.message || err}`); process.exit(1); });
1922
1950
  } else if (command === 'land') {
1923
1951
  // 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()).
1952
+ // one-line sentence instead of an uncaught stack from Promise.resolve(fn()).
1925
1953
  Promise.resolve()
1926
1954
  .then(() => require('../commands/land').landCommand(process.argv.slice(3)))
1927
- .then((code) => process.exit(code || 0))
1955
+ .then((code) => process.exit(typeof code === 'number' ? code : 0))
1928
1956
  .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);
1957
+ const msg = String(err && err.message || err || 'land could not run').split('\n')[0];
1958
+ console.log(msg);
1959
+ process.exit(2);
1936
1960
  });
1937
1961
  } else if (command === 'caretaker') {
1938
1962
  Promise.resolve(require('../commands/caretaker').caretakerCommand(process.argv.slice(3)))
@@ -2035,7 +2059,6 @@ if (command === 'init') {
2035
2059
  } else if (command === 'log') {
2036
2060
  const logArgs = process.argv.slice(3);
2037
2061
  const subcommand = logArgs[0];
2038
- const positional = logArgs.filter((arg) => !String(arg).startsWith('-'));
2039
2062
  if (subcommand === 'sync') {
2040
2063
  require('../commands/log-sync').logSyncAtris()
2041
2064
  .then(() => process.exit(0))
@@ -2044,24 +2067,16 @@ if (command === 'init') {
2044
2067
  process.exit(1);
2045
2068
  });
2046
2069
  } else if (subcommand === '--help' || subcommand === '-h' || subcommand === 'help') {
2047
- console.log('Usage: atris log ["note"] | --repl | sync | <business-slug> | help');
2070
+ console.log('Usage: atris log ["note"] [--json] | --repl | sync | help');
2048
2071
  console.log('');
2049
2072
  console.log(' atris log "an idea" Append a note to today\'s ## Inbox');
2073
+ console.log(' atris log text One word still lands in Inbox. No live business required');
2074
+ console.log(' atris log "an idea" --json');
2050
2075
  console.log(' atris log --repl Open today\'s journal REPL');
2051
2076
  console.log(' atris log Open the REPL (needs a terminal)');
2052
2077
  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
2078
  console.log(' atris wish "..." Headless inbox write; add --json --no-mission for agents');
2055
2079
  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
2080
  } else {
2066
2081
  logCmd();
2067
2082
  }
@@ -2084,13 +2099,14 @@ if (command === 'init') {
2084
2099
  }
2085
2100
  } else if (command === 'now') {
2086
2101
  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);
2102
+ const nowCode = require('../commands/now').nowAtris(args);
2103
+ process.exit(Number.isInteger(nowCode) ? nowCode : 0);
2092
2104
  } else if (command === 'recap') {
2093
- require('../commands/recap').recapAtris(process.argv.slice(3));
2105
+ const recapCode = require('../commands/recap').recapAtris(process.argv.slice(3));
2106
+ if (Number.isInteger(recapCode)) process.exit(recapCode);
2107
+ } else if (command === 'later') {
2108
+ const laterCode = require('../commands/later').laterAtris(process.argv.slice(3));
2109
+ process.exit(Number.isInteger(laterCode) ? laterCode : 0);
2094
2110
  } else if (command === 'activate') {
2095
2111
  const args = process.argv.slice(3);
2096
2112
  if (args.includes('--help') || args.includes('-h') || args[0] === 'help') {
@@ -2431,7 +2447,14 @@ if (command === 'init') {
2431
2447
  process.exit(0);
2432
2448
  }
2433
2449
 
2434
- applyRunnerFlags(args);
2450
+ const { isUnboundScratchFolder } = require('../lib/scratch-root');
2451
+ const { runObjective, runMissionFront } = require('../commands/run-front');
2452
+ const runTalkOnly = !runObjective(args) && !hasYesFlag(args) && !args.includes('--legacy');
2453
+ // Flag validation always runs. The engine-registry seed is what mints
2454
+ // .atris/state/engines.json, so first-talk and unbound scratch skip only that.
2455
+ applyRunnerFlags(args, {
2456
+ seed: !runTalkOnly && !isUnboundScratchFolder(process.cwd()),
2457
+ });
2435
2458
 
2436
2459
  if (args.includes('--legacy')) {
2437
2460
  const legacyArgs = args.filter(a => a !== '--legacy');
@@ -2451,7 +2474,7 @@ if (command === 'init') {
2451
2474
  process.exit(1);
2452
2475
  });
2453
2476
  } else {
2454
- require('../commands/run-front').runMissionFront(args)
2477
+ runMissionFront(args)
2455
2478
  .then((code) => process.exit(code || 0))
2456
2479
  .catch((error) => {
2457
2480
  console.error(`\u2717 Run failed: ${error.message || error}`);
@@ -2463,7 +2486,21 @@ if (command === 'init') {
2463
2486
  process.exit(code);
2464
2487
  } else if (command === 'autopilot') {
2465
2488
  const args = process.argv.slice(3);
2466
- applyRunnerFlags(args);
2489
+ const { isUnboundScratchFolder, refuseUnboundScratch } = require('../lib/scratch-root');
2490
+ const { resolveWorkspaceRoot } = require('../lib/mission-root');
2491
+ const autopilotRoot = resolveWorkspaceRoot(process.cwd());
2492
+ const autopilotHelp = argsWantHelp(args) || args[0] === 'help';
2493
+ const autopilotControl = args[0] === 'stop' || args[0] === 'status';
2494
+ const autopilotJsonPlan = args.includes('--json') && !args.includes('--yes') && !args.includes('-y');
2495
+ const autopilotTalkOnly = !hasYesFlag(args) && !args.includes('--auto') && !args.includes('--legacy');
2496
+ // applyRunnerFlags seeds .atris/state/engines.json. Help, stop/status, JSON
2497
+ // plan, and an unbound scratch talk must not mint a room. --yes is not an unlock.
2498
+ if (!autopilotHelp && !autopilotControl && !autopilotJsonPlan && !autopilotTalkOnly && isUnboundScratchFolder(autopilotRoot)) {
2499
+ process.exit(refuseUnboundScratch());
2500
+ }
2501
+ if (!autopilotHelp) {
2502
+ applyRunnerFlags(args, { seed: !isUnboundScratchFolder(autopilotRoot) });
2503
+ }
2467
2504
 
2468
2505
  if (args.includes('--legacy')) {
2469
2506
  const legacyArgs = args.filter(a => a !== '--legacy');
@@ -2739,8 +2776,9 @@ if (command === 'init') {
2739
2776
  const raw = process.argv.slice(3);
2740
2777
  const { gmailCommand, extractGmailMailboxAccount } = require('../commands/integrations');
2741
2778
  const mailbox = extractGmailMailboxAccount(raw);
2742
- const gateInput = mailbox.mailboxAccountId ? ['--account', ...mailbox.args] : mailbox.args;
2743
- const gate = requireAccountBound(gateInput);
2779
+ // Mailbox --account <id> is a selector, not the workspace opt-in.
2780
+ // Scratch folders refuse every gmail verb the same way as slack/inbox.
2781
+ const gate = requireAccountBound(mailbox.args.filter((arg) => arg !== '--account'));
2744
2782
  if (!gate.ok) {
2745
2783
  process.exit(refuseAccountGlobal());
2746
2784
  return;
@@ -2775,9 +2813,15 @@ if (command === 'init') {
2775
2813
  } else if (command === 'slack') {
2776
2814
  {
2777
2815
  const raw = process.argv.slice(3);
2778
- const gate = requireAccountBound(raw);
2779
- if (!gate.ok) process.exit(refuseAccountGlobal());
2780
- const { slackCommand } = require('../commands/integrations');
2816
+ const { slackCommand, extractGmailMailboxAccount } = require('../commands/integrations');
2817
+ const account = extractGmailMailboxAccount(raw);
2818
+ // Account --account <id> is a selector, not the workspace opt-in.
2819
+ // Scratch folders refuse every slack verb the same way as gmail/inbox.
2820
+ const gate = requireAccountBound(account.args.filter((arg) => arg !== '--account'));
2821
+ if (!gate.ok) {
2822
+ process.exit(refuseAccountGlobal());
2823
+ return;
2824
+ }
2781
2825
  const subcommand = gate.args[0];
2782
2826
  const args = gate.args.slice(1);
2783
2827
  slackCommand(subcommand, ...args)