atris 3.82.25 → 3.83.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.
Files changed (41) hide show
  1. package/AGENTS.md +10 -12
  2. package/README.md +77 -0
  3. package/bin/atris.js +95 -44
  4. package/commands/activate.js +39 -33
  5. package/commands/agent-home.js +403 -0
  6. package/commands/agent-signin.js +214 -0
  7. package/commands/agent-spawn.js +1 -0
  8. package/commands/autoland.js +173 -71
  9. package/commands/brain.js +211 -410
  10. package/commands/doctor.js +38 -26
  11. package/commands/init.js +4 -4
  12. package/commands/join.js +159 -0
  13. package/commands/memory.js +2 -2
  14. package/commands/morning.js +132 -0
  15. package/commands/night.js +178 -0
  16. package/commands/recap.js +4 -4
  17. package/commands/task.js +31 -2
  18. package/commands/worktree.js +18 -0
  19. package/lib/agent-home.js +289 -0
  20. package/lib/agent-page.js +1257 -0
  21. package/lib/cleanup-summary.js +4 -2
  22. package/lib/first-minute.js +41 -20
  23. package/lib/gm-phone-page.js +8 -2
  24. package/lib/known-commands.js +1 -1
  25. package/lib/member-autoland.js +39 -13
  26. package/lib/member-export.js +31 -0
  27. package/lib/memory-folder.js +122 -24
  28. package/lib/memory-live.js +2 -2
  29. package/lib/night-learned.js +171 -0
  30. package/lib/night-shift.js +123 -0
  31. package/lib/task-core.js +2 -1
  32. package/lib/task-db.js +32 -0
  33. package/package.json +1 -1
  34. package/templates/members/designer-1/MEMBER.md +34 -0
  35. package/templates/members/designer-1/MISSION.md +11 -0
  36. package/templates/members/designer-1/SOUL.md +11 -0
  37. package/templates/members/designer-1/context/why.md +5 -0
  38. package/templates/members/designer-1/goals.json +5 -0
  39. package/templates/members/designer-1/goals.md +3 -0
  40. package/templates/members/designer-1/skills/designer-1/SKILL.md +179 -0
  41. package/templates/members/designer-1/skills/designer-1/parts.md +55 -0
package/AGENTS.md CHANGED
@@ -208,14 +208,14 @@ named none. These rules are the difference, in priority order:
208
208
  **Protocol:** See `atris/atris.md` for full spec.
209
209
 
210
210
  <!-- ATRIS_BRAIN_COMPILE:START -->
211
- ## Atris Brain Compile
211
+ ## Atris start-up notes
212
212
 
213
- This workspace has a compiled agent brain.
213
+ This folder keeps notes that give every agent the same starting context.
214
214
 
215
- On session start, activate it first:
216
- `atris brain activate --root . --verify`
215
+ At the start of a session, run this and read what it prints:
216
+ `atris brain activate --root .`
217
217
 
218
- Load these first:
218
+ Then read these files in order:
219
219
  - `atris/now.md`
220
220
  - `atris/brain/STATUS.md`
221
221
  - `atris/brain/self_improvement_ledger.md`
@@ -227,12 +227,10 @@ Load these first:
227
227
  - `atris/TODO.md`
228
228
  - `atris/wiki/index.md`
229
229
 
230
- First-message rule: lead with the move before writing to the operator.
231
- Purpose: optimize for decision-speed; lead with the move, then use descriptions only when they help the operator act.
232
- Shape: `<operator>, today is about <move>` -> `I picked this because <why now>` -> `Ready: <draft/proof/context>` -> `Go deeper: <paths>`.
233
- Definitions: operator = current person or agent; move = one concrete high-leverage workflow; why now = business reason; ready = prepared action or proof; paths = 2-4 optional deeper views.
230
+ First reply: open with the one thing most worth doing today. Then say why, what is already prepared and where to look for more.
231
+ Shape: `<name>, today is about <the one thing>` -> `I picked this because <why now>` -> `Ready: <what is prepared>` -> `Go deeper: <2 to 4 paths>`.
234
232
 
235
- Keep this voice beside every reply:
233
+ Keep this voice in every reply:
236
234
  <!-- ATRIS_VOICE_CARD:START -->
237
235
  ## Voice card
238
236
 
@@ -247,6 +245,6 @@ Landing example:
247
245
  Replies now get a plain-language check before they reach you. The checks passed, and the change is ready.
248
246
  <!-- ATRIS_VOICE_CARD:END -->
249
247
 
250
- Re-run after meaningful work:
251
- `atris brain compile --root .`
248
+ After meaningful work, save fresh notes:
249
+ `atris brain compile --root . --yes`
252
250
  <!-- ATRIS_BRAIN_COMPILE:END -->
package/README.md CHANGED
@@ -137,6 +137,15 @@ atris host import people.jsonl # bulk add people and who already knows who
137
137
  atris host room # the host's weekly read
138
138
  ```
139
139
 
140
+ ### Design screens with Designer-1
141
+
142
+ Designer-1 is a team member that builds web screens from a fixed set of 19 parts (button, field, select, dialog, tabs, row list and the rest). It reuses a part before writing one, keeps one spacing scale, writes very few words, and checks its work by keyboard, by control name, and at phone and desktop widths before it reports. A project's own taste files in `atris/taste/` win over its defaults.
143
+
144
+ ```bash
145
+ atris member install designer-1
146
+ atris member install designer-1 --update # pick up the latest skill; your edits to its other files stay
147
+ ```
148
+
140
149
  ### Put members to work
141
150
 
142
151
  Members are helpers that work in their own copy of the repo. Three words cover it.
@@ -393,6 +402,74 @@ atris business record atris/reports/2026-04-12-operator-recap.md --outcome mixed
393
402
  - `atris pull` and `atris push` sync cloud workspaces and journals
394
403
  - `atris live` keeps a business brain fresh by checking/fixing the workspace, pushing local state, pulling cloud state, and pushing again after local changes go quiet
395
404
 
405
+ ## Agent page (beta)
406
+
407
+ Agent page is one small browser file, in beta. An AI agent reads the controls on a page by their names, and presses them, without screenshots. It works on the page a person already has when that page is built from normal form controls and ARIA roles. It has no dependencies. The limits are listed below.
408
+
409
+ The marks are a guardrail for well-behaved agents, not security. A page must still enforce its real rules on its server. Do not put real secrets in a page and rely on a mark to hide them: any software running in that page can read them.
410
+
411
+ Load it with a plain script tag, or `require` it in Node and bundlers:
412
+
413
+ ```html
414
+ <script src="node_modules/atris/lib/agent-page.js"></script>
415
+ ```
416
+
417
+ ```js
418
+ const agent = AgentPage.createAgent({
419
+ allow: (request) => window.confirm(`Let the agent ${request.action} "${request.label}"?`),
420
+ timeout: 5 * 60 * 1000,
421
+ });
422
+ agent.snapshot();
423
+ await agent.act({ steps: [{ label: 'Project name', action: 'set', args: { value: 'Atlas' } }] });
424
+ await agent.query({ text: 'Role', kind: 'select' });
425
+ ```
426
+
427
+ Three calls do the work.
428
+
429
+ `snapshot()` lists the controls. Each one has a ref, a kind, a label, its value or checked or open state, and the actions it accepts right now. The list is capped at 100. A disabled or read-only control says so and accepts no actions.
430
+
431
+ `act({ steps })` runs steps in order and stops at the first failure. Each step names a control by `ref` (from a snapshot) or by `label`, and gives an `action`: `click`, `set`, `check`, or `uncheck`. Each result shows the changes, the current value, and any error.
432
+
433
+ After a change, the file finds the control again in the live page (the same element, or else the one visible control with the same kind and name) and reads its value there. A typed value, a tick, or a choice that did not stick reads as a failure, and so does a press on something that should open or close but did not. A plain press on a button or link says ok once it is delivered; the changes list shows what the page did with it. A disabled choice is refused.
434
+
435
+ A select or listbox that holds more than one choice shows every chosen value as a list. To change it, send `set` the full list of choices to keep, for example `args: { value: ['Email', 'Chat'] }`. A single value is refused when it would clear other choices, so nothing is cleared unless the agent names the whole list. Hidden choices in such a list are left as they are.
436
+
437
+ `query({ text, within, kind })` finds controls and lists the choices of a select or listbox. The list is capped at 50. With `text`, only the matching choices are listed, so a choice past the cap can still be found.
438
+
439
+ Three marks on the page set the rules, with the `allow` callback.
440
+
441
+ The `allow` callback decides, and it may return a promise. If you pass one, it runs before every change the agent makes, so the person sees each click, change, or tick first. It gets the control's label, the action and its args, whether the step is marked for confirmation, and the page's own confirm sentences in `reasons`. If the callback throws, the step fails with a fixed sentence; the error's own message is never passed to the agent.
442
+
443
+ Before asking, the file lists the elements the step involves and the confirm marks around them. It lists them again after the answer and refuses the step if anything differs: a mark added, removed or reworded, or the control moved under a different mark. It reads the marks again before each part of the change too (after focus, between events, and before each choice it clicks), and stops with the rest undone if a new mark appears.
444
+
445
+ One approval at a time per page. While a step waits for the person, every other agent on the page is refused, so one agent cannot press another's confirm button. A host's own confirm dialog should still carry `data-agent-hidden`, so no agent can see it at all.
446
+
447
+ `data-agent-confirm` marks a control that needs a person. A step involves the control, its labels (wrapping and `for`), the form it belongs to (also through the `form` attribute), every field of that form when the press submits or resets it, each choice the step picks, and each choice or radio the step clears, hidden ones included, with their labels. A mark on any of these, or around them, counts. Without an `allow` callback, that step fails and tells the agent to ask the person.
448
+
449
+ `data-agent-hidden` leaves a region out entirely. On a single choice, it leaves out that choice. A choice hidden by the `hidden` attribute or by css is left out the same way.
450
+
451
+ `data-agent-secret` hides the value and all state of the marked control and everything inside it: value, checked, selected, open, and its choices all read `[redacted]`, and its actions do not change with its state. Its name stays visible, and comes only from a label element, `aria-label`, or `aria-labelledby`; placeholder, title, alt and the control's own text are never used. To hide the name too, use `data-agent-hidden`. Other text inside a secret mark is not read. The agent can still type into a secret field or pick a secret choice it already knows. Password, card, and one-time-code fields count as secret without a mark.
452
+
453
+ A mark covers the element and everything inside it. Labels, values, changes and error sentences are built when the result is returned, with the marks as they are then: a value that became secret reads `[redacted]` on both sides of a change, and a control that is gone or hidden is reported by its ref only.
454
+
455
+ A page can tell when the agent is acting. While an act step runs, `document.documentElement` has `data-agent-acting="true"`. Every event the agent fires carries `event.agentAct = { ref, action }`.
456
+
457
+ What it cannot do:
458
+
459
+ It reads the main page only. Controls inside iframes and shadow roots are not listed, so the agent cannot reach them. Snapshot and query add an entry of kind `unread` that counts what was skipped, so the agent knows. A custom element may hold a closed shadow root, so each one is counted as possibly unread.
460
+
461
+ These limits are accepted. A closed shadow root on an ordinary element, or one made before this file loads, cannot be detected at all. Frames from other sites cannot be read. The file trusts the page it runs in: page scripts can always change anything, including the marks.
462
+
463
+ A page whose own scripts add, remove or move marks, or swap controls, in the middle of an event is handled only as far as the checks above go: marks read again before each part of a change, and the control found again after it. Beyond that it is out of scope.
464
+
465
+ Editable text regions (`contenteditable`) are not typed into. Plain ones are counted as unread. One with a textbox role is listed with its text and no actions.
466
+
467
+ The tests here drive real Chrome. The controlled-input test imitates React with a hand-built value tracker; a check against real React is planned and not done yet.
468
+
469
+ For agents that speak tools, `agentTools` lists three tools (`page_snapshot`, `page_act`, `page_query`) with JSON schemas. `registerTools(agent, { allow })` registers them on `document.modelContext` when the page offers it, and does nothing otherwise.
470
+
471
+ The mark names and the snapshot, act, and query shape come from Vercel's UI SDK agent bridge. The code here is our own.
472
+
396
473
  ## Engines
397
474
 
398
475
  An engine is the intelligence that builds a mission. Every installed headless coding CLI is a swappable worker behind one contract: a bounded prompt goes in, verified proof comes out, and engines never self-certify their own work. The house default is `atris-fast` (`ax`); `claude`, `codex`, `cursor`, `devin`, `grok`, and others join when their CLIs are installed.
package/bin/atris.js CHANGED
@@ -529,25 +529,25 @@ function showStartHelp() {
529
529
  }
530
530
 
531
531
  function showHelpShort() {
532
- console.log('atris: you say what you want. already won. one next step.');
532
+ console.log('atris: say what you want. see what is already done. get one next step.');
533
533
  console.log('');
534
534
  console.log('Start here:');
535
535
  console.log(' 1. atris init set up this folder');
536
536
  console.log(' 2. atris doctor check this computer is ready');
537
- console.log(' 3. atris "what you want" say it, the work starts');
537
+ console.log(' 3. atris "what you want" save what you want in plain words');
538
538
  console.log('');
539
- console.log(' atris "what do you want here?" empty folder');
540
- console.log(' atris later "..." remember without a room');
541
- console.log(' atris idea "..." drop an idea; atris builds it later');
542
- console.log(' atris guide "..." translate your words');
543
- console.log(' atris do work already here');
544
- console.log(' atris done land what your agent built');
545
- console.log(' atris spaceship Keep working for a few hours');
546
- console.log(' atris autopilot Keep working until you stop');
547
- console.log(' atris mission the one goal');
548
- console.log(' atris gm play your team as a front office');
539
+ console.log(' atris see where things stand and the next step');
540
+ console.log(' atris later "..." save a thought for later');
541
+ console.log(' atris idea "..." save an idea for later work');
542
+ console.log(' atris guide "..." suggest a command from your words');
543
+ console.log(' atris do see the next step for work here');
544
+ console.log(' atris done finish and keep what your agent built');
545
+ console.log(' atris spaceship plan a few hours of work');
546
+ console.log(' atris autopilot plan work that runs until you stop it');
547
+ console.log(' atris mission see the current goal');
548
+ console.log(' atris gm run your team like a sports front office');
549
549
  console.log('');
550
- console.log('More: atris help --all · machine list: atris help --json');
550
+ console.log('More: atris help --all · command list for scripts: atris help --json');
551
551
  }
552
552
 
553
553
  function helpCatalog() {
@@ -571,24 +571,24 @@ function helpCatalog() {
571
571
  }
572
572
 
573
573
  function showHelpAll() {
574
- console.log('you say what you want. already won. one next step.');
574
+ console.log('atris: say what you want. see what is already done. get one next step.');
575
575
  console.log('');
576
- console.log(' atris later "..." remember without a room');
577
- console.log(' atris idea "..." drop an idea; atris builds it later');
578
- console.log(' atris guide "..." translate your words');
579
- console.log(' atris do work already here');
580
- console.log(' atris done land what your agent built');
581
- console.log(' atris spaceship Keep working for a few hours');
582
- console.log(' atris autopilot Keep working until you stop');
583
- console.log(' atris mission the one goal');
576
+ console.log(' atris later "..." save a thought for later');
577
+ console.log(' atris idea "..." save an idea for later work');
578
+ console.log(' atris guide "..." suggest a command from your words');
579
+ console.log(' atris do see the next step for work here');
580
+ console.log(' atris done finish and keep what your agent built');
581
+ console.log(' atris spaceship plan a few hours of work');
582
+ console.log(' atris autopilot plan work that runs until you stop it');
583
+ console.log(' atris mission see the current goal');
584
584
  console.log(' atris recap what your ai agent did today');
585
585
  console.log(' atris review check the work');
586
586
  console.log(' atris stop stop the live goal');
587
587
  console.log('');
588
- console.log('Atris Computers:');
589
- console.log(' Owner = User | Business');
590
- console.log(' Owners have Computers: workspace + files + tools + secrets + memory + agents + validation');
591
- console.log(' Types: code, research, CRM, reporting, events, support, business ops');
588
+ console.log('How it fits together:');
589
+ console.log(' you or your business can own computers.');
590
+ console.log(' a computer brings a workspace, files, tools, saved logins, memory, agents and checks together.');
591
+ console.log(' computers can be for code, research, sales, reporting, events, support or operations.');
592
592
  console.log('');
593
593
  console.log('Setup:');
594
594
  console.log(' setup - Guided first-time setup (login, pick business, pull)');
@@ -760,6 +760,9 @@ function showHelpAll() {
760
760
  console.log('');
761
761
  console.log('Cloud & agents:');
762
762
  console.log(' computer - Open a scoped AI computer (cloud/local, personal/business)');
763
+ console.log(' night - Give your agent a goal for tonight with a dollar cap (atris night "<goal>" --cap 1)');
764
+ console.log(' morning - How last night went: score, what it learned (saved to your atris/ folder), spend');
765
+ console.log(' join - Join a computer as a named agent (--name), or accept an invite (<code>)');
763
766
  console.log(' receipt - Save evidence from an agent run');
764
767
  console.log(' console - Start/attach always-on coding console (tmux daemon)');
765
768
  console.log(' soul - Show, snapshot, or fork workspace identity');
@@ -767,6 +770,8 @@ function showHelpAll() {
767
770
  console.log(' loops - Self-improving loop audit/scaffold (`init`, `audit`, `tick`, `board`)');
768
771
  console.log(' self-improve - Alias for `atris loops init`');
769
772
  console.log(' agent - Select cloud agent, spawn worker requests, or run `agent doctor`');
773
+ console.log(' agent pull|push|snapshot: carry an agent\'s home folder; agent spawn --budget: make a child agent');
774
+ console.log(' agent signin <agent> --key-file <path>: sign in with the agent\'s own key, print a token');
770
775
  console.log(' chat - Chat with Atris 2 Fast in this workspace (--agent for cloud agent; or: atris chat scan)');
771
776
  console.log(' fast - Chat with Atris2 Fast');
772
777
  console.log(' login - Sign in, add an account, or mint a scoped agent token (--agent)');
@@ -819,6 +824,7 @@ function showHelpAll() {
819
824
  console.log('Team:');
820
825
  console.log(' member create <name> - Scaffold a new team member (MEMBER.md)');
821
826
  console.log(' member list - Show all team members');
827
+ console.log(' member install <n> - Add a ready-made member: host, designer-1');
822
828
  console.log(' member activate <n> - Activate a member (link skills, show context)');
823
829
  console.log(' member upgrade <n> - Convert flat file to directory format');
824
830
  console.log(' member result <n> - Newest run of a member: run record, receipt, artifacts, result number');
@@ -840,7 +846,7 @@ function showHelpAll() {
840
846
  console.log(' version - Show Atris version');
841
847
  console.log(' help - you say what you want (help --all for every command)');
842
848
  console.log('');
843
- console.log('Tip: Just run "atris" to get started');
849
+ console.log('tip: run "atris" to get started');
844
850
  console.log('');
845
851
  }
846
852
 
@@ -1732,7 +1738,13 @@ function showWelcomeVisualization() {
1732
1738
  const projectName = path.basename(cwd);
1733
1739
  const row = (label, value) => ` ${label.padEnd(9)}${value}`;
1734
1740
  const sub = (text) => ` ${' '.repeat(9)}- ${text}`;
1735
- const trimTitle = (t) => (String(t).length > 64 ? `${String(t).slice(0, 61)}...` : String(t));
1741
+ const trimTitle = (t) => {
1742
+ const title = require('../lib/first-minute').plainTitle(t);
1743
+ if (title.length <= 64) return title;
1744
+ const cut = title.slice(0, 61);
1745
+ const boundary = cut.lastIndexOf(' ');
1746
+ return `${(boundary > 0 ? cut.slice(0, boundary) : cut).trimEnd()}...`;
1747
+ };
1736
1748
 
1737
1749
  let glance = {
1738
1750
  backlog: 0, active: 0, review: 0, reviewCertified: 0,
@@ -1818,7 +1830,7 @@ function showWelcomeVisualization() {
1818
1830
  console.log('');
1819
1831
 
1820
1832
  if (!isInitialized) {
1821
- console.log(' no atris workspace here yet.');
1833
+ console.log(' atris is not set up in this folder yet.');
1822
1834
  console.log('');
1823
1835
  console.log(row('next', 'atris init (set up this folder)'));
1824
1836
  console.log('');
@@ -1866,11 +1878,14 @@ function showWelcomeVisualization() {
1866
1878
 
1867
1879
  try {
1868
1880
  const health = require('../commands/doc-health').computeDocHealth(cwd);
1869
- if (health.ok) {
1870
- const detail = health.lookup_hops.missing
1871
- ? 'add atris/doc-health/questions.jsonl'
1872
- : `${Math.round((health.lookup_hops.score || 0) * 100)}% one hop · boot ${(health.boot_load.approximate_tokens / 1000).toFixed(1)}k tokens`;
1873
- console.log(row('docs', `${health.overall.total}/100 · ${detail}`));
1881
+ const map = fs.readFileSync(path.join(atrisDir, 'MAP.md'), 'utf8');
1882
+ const starterMap = map.includes('Run your AI agent with atris.md to populate this file.');
1883
+ if (health.ok && map.trim() && !starterMap) {
1884
+ const score = health.lookup_hops.score;
1885
+ const detail = score === null
1886
+ ? 'add sample questions to test the map: atris/doc-health/questions.jsonl'
1887
+ : `the map answers ${score >= 1 ? 'every sample question' : `${Math.min(99, Math.round(score * 100))}% of sample questions`} · ${(health.boot_load.approximate_tokens / 1000).toFixed(1)}k tokens to read at start`;
1888
+ console.log(row('docs', `${Math.round(health.overall.total)}/100 · ${detail}`));
1874
1889
  }
1875
1890
  } catch {
1876
1891
  // Document health is advisory and must never prevent startup.
@@ -1881,7 +1896,10 @@ function showWelcomeVisualization() {
1881
1896
  // journal, plus agent branches parked for a person.
1882
1897
  try {
1883
1898
  const lately = require('../lib/cleanup-summary').latestCleanupLine(cwd);
1884
- if (lately) console.log(row('lately', lately));
1899
+ if (lately) {
1900
+ const plain = lately.replace(/^[a-z][a-z -]{0,30}:\s*/i, '').replace(/\blanded\b/gi, 'completed').replace(/\bland on their own\b/gi, 'finish on their own').replace(/\b([0-9]+) agent branch(es)? parked for you\b/g, (m, n, many) => `${n} ${many ? 'pieces' : 'piece'} of agent work set aside for you`);
1901
+ console.log(row('lately', plain.charAt(0).toLowerCase() + plain.slice(1)));
1902
+ }
1885
1903
  } catch {
1886
1904
  // Advisory only; never blocks boot.
1887
1905
  }
@@ -1940,7 +1958,7 @@ function showWelcomeVisualization() {
1940
1958
  if (landInfo.due > 0) landText += ` (${landInfo.due} overdue)`;
1941
1959
  tidyBits.push(landText);
1942
1960
  }
1943
- if (rotInfo && rotInfo.worktrees > 0) tidyBits.push(`${rotInfo.worktrees} old cop${rotInfo.worktrees === 1 ? 'y' : 'ies'} to toss`);
1961
+ if (rotInfo && rotInfo.worktrees > 0) tidyBits.push(`${rotInfo.worktrees} old cop${rotInfo.worktrees === 1 ? 'y' : 'ies'} to clear out`);
1944
1962
  if (rotInfo && rotInfo.lessons > 0) tidyBits.push(`${rotInfo.lessons} open problem${rotInfo.lessons === 1 ? '' : 's'}`);
1945
1963
  if (tidyBits.length) {
1946
1964
  console.log(row('tidy', tidyBits.join(', ')));
@@ -1953,7 +1971,7 @@ function showWelcomeVisualization() {
1953
1971
  const rev = require('../commands/improve').collectRevisionSignals(cwd, { days: 7 });
1954
1972
  const fixed = Number.isFinite(rev.fixed) ? rev.fixed : rev.revised;
1955
1973
  if (fixed > 0) {
1956
- console.log(` ${fixed} landing${fixed === 1 ? '' : 's'} this week needed a human fix; run atris improve revisions`);
1974
+ console.log(` ${fixed} finished change${fixed === 1 ? '' : 's'} needed a human fix this week; run atris improve revisions (see what needed fixing)`);
1957
1975
  }
1958
1976
  } catch {
1959
1977
  // Silent: the gauge never blocks boot.
@@ -1966,7 +1984,7 @@ function showWelcomeVisualization() {
1966
1984
  wikiNudge = '';
1967
1985
  }
1968
1986
  if (wikiNudge) {
1969
- console.log(` ${wikiNudge}`);
1987
+ console.log(` ${wikiNudge.replace('the wiki has grown a lot since its last pruning; run: atris wiki consolidate', 'the project notes have grown; run atris wiki consolidate (review notes to combine)')}`);
1970
1988
  }
1971
1989
 
1972
1990
  console.log(row('logs', journalEntries > 0
@@ -1990,7 +2008,7 @@ function showWelcomeVisualization() {
1990
2008
  const teamLine = require('../lib/team-lineup').bootTeamLine(cwd, { width: 80 - 11, jobs });
1991
2009
  if (teamLine) console.log(row('team', teamLine));
1992
2010
  const endsLine = engineView.bootExpiringLine(engineView.expiringWorkers(jobs));
1993
- if (endsLine) console.log(row('roster', endsLine));
2011
+ if (endsLine) console.log(row('tools', endsLine.replace('renew: atris engine roster confirm', 'run atris engine roster confirm (renew the assigned tools)')));
1994
2012
  } catch {
1995
2013
  // Silent: the lineup is a hint, not a gate.
1996
2014
  }
@@ -2012,7 +2030,7 @@ function showWelcomeVisualization() {
2012
2030
  .filter((intent) => intent.offer === 'boot_empty')
2013
2031
  .map((intent) => intent.say[0])
2014
2032
  .slice(0, 2);
2015
- console.log(row('say', `"${phrases[0]}" or "${phrases[1]}" (your agent translates)`));
2033
+ console.log(row('say', `"${phrases[0]}" or "${phrases[1]}" (say it to your agent in your own words)`));
2016
2034
  }
2017
2035
 
2018
2036
  // The next command always carries a plain-english gloss: a newcomer should
@@ -2023,7 +2041,7 @@ function showWelcomeVisualization() {
2023
2041
  } else if (landInfo && landInfo.due > 0) {
2024
2042
  next = `atris land --reap (put away the overdue work)`;
2025
2043
  } else if (endgameState.slug !== 'unset') {
2026
- next = 'atris autopilot (do the next piece of work)';
2044
+ next = 'atris autopilot (keep working on the goal; it asks before it starts)';
2027
2045
  } else {
2028
2046
  next = 'atris plan (plan the first tasks)';
2029
2047
  }
@@ -2287,10 +2305,21 @@ if (command === 'guide') {
2287
2305
  if (process.argv.slice(3).includes('--json')) {
2288
2306
  console.log(JSON.stringify({ ok: false, error: message }, null, 2));
2289
2307
  } else {
2290
- console.error(`\n✗ Error: ${message}`);
2308
+ console.error(message);
2291
2309
  }
2292
2310
  process.exit(1);
2293
2311
  });
2312
+ } else if (command === 'agent' && process.argv[3] === 'signin') {
2313
+ // An agent signs in with its own private key; no owner login, no --account gate.
2314
+ Promise.resolve().then(() => require('../commands/agent-signin').agentSigninCommand(process.argv.slice(4)))
2315
+ .then((code) => exitAfterStdoutDrain(code || 0))
2316
+ .catch((err) => { console.error(`\nerror: ${err.message || err}`); process.exit(1); });
2317
+ } else if (command === 'agent' && require('../commands/agent-home').isHomeCommand(process.argv.slice(3))) {
2318
+ // pull/push/snapshot/spawn --budget name one agent (by id, saved name, or the
2319
+ // pulled folder), and the server checks ownership, so no --account gate.
2320
+ Promise.resolve().then(() => require('../commands/agent-home').agentHomeCommand(process.argv.slice(3)))
2321
+ .then((code) => exitAfterStdoutDrain(code || 0))
2322
+ .catch((err) => { console.error(`\nerror: ${err.message || err}`); process.exit(1); });
2294
2323
  } else if (command === 'agent') {
2295
2324
  {
2296
2325
  const gate = requireAccountBound(process.argv.slice(3));
@@ -3598,8 +3627,24 @@ if (command === 'guide') {
3598
3627
  .catch((err) => { console.error(`✗ Error: ${err.message || err}`); process.exit(1); });
3599
3628
  }
3600
3629
  } else if (command === 'join') {
3601
- require('../commands/social').joinCommand()
3602
- .then(() => exitAfterStdoutDrain(0))
3630
+ // `atris join --name <name>` joins as an agent; `atris join <code>` accepts an invite.
3631
+ const joinArgs = process.argv.slice(3);
3632
+ if (require('../commands/join').isAgentJoin(joinArgs)) {
3633
+ Promise.resolve().then(() => require('../commands/join').joinAgentCommand(joinArgs))
3634
+ .then((code) => exitAfterStdoutDrain(typeof code === 'number' ? code : 0))
3635
+ .catch((err) => { console.error(`\n✗ Error: ${err.message || err}`); process.exit(1); });
3636
+ } else {
3637
+ require('../commands/social').joinCommand()
3638
+ .then(() => exitAfterStdoutDrain(0))
3639
+ .catch((err) => { console.error(`\n✗ Error: ${err.message || err}`); process.exit(1); });
3640
+ }
3641
+ } else if (command === 'night') {
3642
+ Promise.resolve().then(() => require('../commands/night').nightCommand(process.argv.slice(3)))
3643
+ .then((code) => exitAfterStdoutDrain(typeof code === 'number' ? code : 0))
3644
+ .catch((err) => { console.error(`\n✗ Error: ${err.message || err}`); process.exit(1); });
3645
+ } else if (command === 'morning') {
3646
+ Promise.resolve().then(() => require('../commands/morning').morningCommand(process.argv.slice(3)))
3647
+ .then((code) => exitAfterStdoutDrain(typeof code === 'number' ? code : 0))
3603
3648
  .catch((err) => { console.error(`\n✗ Error: ${err.message || err}`); process.exit(1); });
3604
3649
  } else if (command === 'invite') {
3605
3650
  require('../commands/social').inviteCommand()
@@ -3780,8 +3825,14 @@ async function agentAtris() {
3780
3825
  const firstArg = process.argv[3];
3781
3826
  if (firstArg === '-h' || firstArg === '--help' || firstArg === 'help') {
3782
3827
  console.log('Usage: atris agent [doctor|spawn|spawns|spawn-status]');
3828
+ console.log(' atris agent [pull|push|snapshot] and atris agent spawn <parent> --name <name> --budget <credits>');
3829
+ console.log(' atris agent signin <agent> [--key-file <path>] [--json]');
3783
3830
  console.log('');
3784
3831
  console.log(' Pick which cloud agent to chat with from this workspace.');
3832
+ console.log(' Run `atris agent pull <agent> --dir <folder>` to copy an agent\'s home here; any engine started there becomes it.');
3833
+ console.log(' Run `atris agent push` inside that folder to send changes back, `atris agent snapshot` to save a version.');
3834
+ console.log(' Run `atris agent spawn <parent> --name <name> --budget <credits>` to make a child agent; its key goes to ~/.atris/agents.');
3835
+ console.log(' Run `atris agent signin <agent> [--key-file <path>]` to sign in with the agent\'s own key; prints only a token.');
3785
3836
  console.log(' Run `atris agent spawn <role> --task "..."` to create a worker request.');
3786
3837
  console.log(' Run `atris agent spawns` to list worker requests.');
3787
3838
  console.log(' Run `atris agent doctor` to verify local AI CLIs can see Atris context.');
@@ -5,6 +5,7 @@ const { getLogPath, ensureLogDirectory, createLogFile } = require('../lib/journa
5
5
  const { detectWorkspaceState, loadContext } = require('../lib/state-detection');
6
6
  const { readWikiStatus } = require('../lib/wiki');
7
7
  const { gateForHuman, numberWord } = require('../lib/voice-gate');
8
+ const { plainTitle } = require('../lib/first-minute');
8
9
 
9
10
  const CLARITY_FIELDS = [
10
11
  { key: 'focus', label: 'Focus' },
@@ -120,7 +121,7 @@ function activeWorkSentence(state, context) {
120
121
  const tasks = Array.isArray(context?.inProgressTasks) ? context.inProgressTasks : [];
121
122
  if (tasks.length) {
122
123
  const count = tasks.length;
123
- const focus = focusFragment(tasks[0]);
124
+ const focus = focusFragment(plainTitle(tasks[0]));
124
125
  const subject = count === 1 ? 'task is' : 'tasks are';
125
126
  return humanSentence(`right now: ${countWord(count)} ${subject} in progress; the focus is ${closeSentence(focus)}`);
126
127
  }
@@ -152,8 +153,7 @@ function activeWorkSentence(state, context) {
152
153
  return null;
153
154
  }
154
155
 
155
- function activateAtris() {
156
- const workspaceDir = process.cwd();
156
+ function buildActivationSummary(workspaceDir) {
157
157
  const targetDir = path.join(workspaceDir, 'atris');
158
158
 
159
159
  if (!fs.existsSync(targetDir)) {
@@ -166,12 +166,7 @@ function activateAtris() {
166
166
  const todoFile = path.join(targetDir, 'TODO.md');
167
167
  const legacyTaskContextsFile = path.join(targetDir, 'TASK_CONTEXTS.md');
168
168
 
169
- // Journal (create today's file if missing so the system is always runnable)
170
- ensureLogDirectory();
171
- const { logFile, dateFormatted } = getLogPath();
172
- if (!fs.existsSync(logFile)) {
173
- createLogFile(logFile, dateFormatted);
174
- }
169
+ const logFile = path.join(workspaceDir, 'atris', 'logs', path.relative(path.join(process.cwd(), 'atris', 'logs'), getLogPath().logFile));
175
170
 
176
171
  const state = detectWorkspaceState(workspaceDir);
177
172
  const context = loadContext(workspaceDir);
@@ -247,7 +242,7 @@ function activateAtris() {
247
242
  try {
248
243
  const { nextMoves } = require('../lib/next-moves');
249
244
  const { profilePaths } = require('../lib/clarity');
250
- const root = process.cwd();
245
+ const root = workspaceDir;
251
246
  moves = nextMoves(root, 3);
252
247
  const { profile, mdPath } = readActivationClarityProfile(root, profilePaths);
253
248
  clarityLine = formatClarityLine(profile, rel(mdPath));
@@ -258,19 +253,6 @@ function activateAtris() {
258
253
  const { buildBriefData } = require('./brief');
259
254
  briefData = buildBriefData(workspaceDir);
260
255
  } catch { /* brief is best-effort */ }
261
- try {
262
- const { syncStatus } = require('../lib/sync-status');
263
- syncStatus(workspaceDir);
264
- } catch { /* sync status is best-effort */ }
265
-
266
- void dateFormatted;
267
- void learningCount;
268
- fs.existsSync(personaFile);
269
- fs.existsSync(mapFile);
270
- void taskFilePath;
271
-
272
- console.log(humanSentence('atris is up.'));
273
-
274
256
  const statusLines = [
275
257
  handoffSentence(handoffContent),
276
258
  completionSentence(recentCompletions),
@@ -286,20 +268,44 @@ function activateAtris() {
286
268
  statusLines.push(`waiting on you: ${countWord(approvalCount)} ${approvalLabel}. see them: atris task reviews`);
287
269
  }
288
270
  }
289
- if (statusLines.length) {
271
+ const nextMove = moves[0] || briefData?.moves?.[0];
272
+ const nextMoveTitle = focusFragment(plainTitle(nextMove?.title), 100);
273
+ const today = nextMoveTitle ? humanSentence(`today's move: ${closeSentence(nextMoveTitle)}`) : null;
274
+ return {
275
+ lines: statusLines,
276
+ note: statusLines.find(line => line.startsWith('last session left a note:')) || null,
277
+ landed: statusLines.find(line => line.startsWith('since last time:')) || null,
278
+ right_now: statusLines.find(line => line.startsWith('right now:')) || null,
279
+ waiting: statusLines.find(line => line.startsWith('waiting on you:') || line === 'nothing is waiting on you.') || null,
280
+ today, clarityLine,
281
+ };
282
+ }
283
+
284
+ function activateAtris() {
285
+ const workspaceDir = process.cwd();
286
+ if (!fs.existsSync(path.join(workspaceDir, 'atris'))) {
287
+ console.log('✗ atris/ folder not found. Run "atris init" first.');
288
+ process.exit(1);
289
+ }
290
+ ensureLogDirectory();
291
+ const { logFile, dateFormatted } = getLogPath();
292
+ if (!fs.existsSync(logFile)) createLogFile(logFile, dateFormatted);
293
+ const summary = buildActivationSummary(workspaceDir);
294
+ try {
295
+ const { syncStatus } = require('../lib/sync-status');
296
+ syncStatus(workspaceDir);
297
+ } catch { /* sync status is best-effort */ }
298
+ console.log(humanSentence('atris is up.'));
299
+ if (summary.lines.length) {
290
300
  console.log('');
291
- statusLines.forEach((line) => console.log(line));
301
+ summary.lines.forEach((line) => console.log(line));
292
302
  }
293
-
294
303
  console.log('');
295
- console.log(clarityLine || 'Tip: run atris clarity once so agents learn how you work.');
296
-
297
- const nextMove = moves[0] || briefData?.moves?.[0];
298
- const nextMoveTitle = focusFragment(nextMove?.title, 100);
299
- if (nextMoveTitle) {
304
+ console.log(summary.clarityLine || 'tip: run atris clarity once so agents learn how you work.');
305
+ if (summary.today) {
300
306
  console.log('');
301
- console.log(humanSentence(`today's move: ${closeSentence(nextMoveTitle)}`));
307
+ console.log(summary.today);
302
308
  }
303
309
  }
304
310
 
305
- module.exports = { activateAtris, wikiHealthSentence };
311
+ module.exports = { activateAtris, wikiHealthSentence, buildActivationSummary };