@north-light/crouter 0.3.338 → 0.3.340

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 +1 @@
1
- import{defineBranch as e}from"../../../core/command.js";import{advancedLeaf as a,basicLeaf as r}from"./tracks.js";import{scenarioBranch as s}from"./scenario.js";const c=e({name:"tutorial",description:"guided crouter lessons and disposable practice scenarios",whenToUse:"you need to learn crouter operation conversationally, one lesson at a time, or create the disposable canvas state a lesson asks you to inspect.",extensible:!0,help:{name:"sys tutorial",summary:"guided crouter lessons and disposable practice scenarios",model:"Tracks serve one lesson per invocation and never record progress, so --step can start or resume anywhere. Scenarios create only the temporary canvas state a lesson needs and clean exactly the recorded rows afterward."},children:[r,a,s]});export{c as tutorialBranch};
1
+ import{defineBranch as e}from"../../../core/command.js";import{basicLeaf as r}from"./tracks.js";import{scenarioBranch as s}from"./scenario.js";const t=e({name:"tutorial",description:"guided crouter lessons and disposable practice scenarios",whenToUse:"you need to learn crouter operation conversationally, one lesson at a time, or create the disposable canvas state a lesson asks you to inspect.",extensible:!0,help:{name:"sys tutorial",summary:"guided crouter lessons and disposable practice scenarios",model:"Tracks serve one lesson per invocation and never record progress, so --step can start or resume anywhere. Scenarios create only the temporary canvas state a lesson needs and clean exactly the recorded rows afterward."},children:[r,s]});export{t as tutorialBranch};
@@ -3,4 +3,3 @@ export interface TutorialLesson {
3
3
  readonly body: string;
4
4
  }
5
5
  export declare const BASIC_LESSONS: readonly TutorialLesson[];
6
- export declare const ADVANCED_LESSONS: readonly TutorialLesson[];
@@ -1,12 +1,7 @@
1
- import{resolveNodeVisual as e}from"../../../core/canvas/status-glyph.js";const t=e("active",{}),n=e("idle",{}),a=e("done",{}),o=e("dead",{}),i=e("canceled",{}),s=e("active",{streaming:!0}),r=e("active",{frozen:!0}),h=e("active",{hanging:{kind:"wedged"}}),l=[{title:"Terminal Option key \u2014 do this first",body:["Every crouter global is an Alt chord, and on macOS terminals Option defaults to emitting accented characters. Until this is fixed, `Alt+C` types `\xE7` and the user concludes crouter is broken. Fix it before teaching anything else.","","Read `TERM_PROGRAM` to identify the terminal, then give the exact path:","","- **iTerm2** \u2014 Settings \u2192 Profiles \u2192 Keys \u2192 General \u2192 set **Left option** and **Right option** to `Esc+`.","- **Ghostty** \u2014 `macos-option-as-alt = true` in `~/.config/ghostty/config`.","- **kitty** \u2014 `macos_option_as_alt yes` in `~/.config/kitty/kitty.conf`.","- **WezTerm** \u2014 `send_composed_key_when_left_alt_is_pressed = false`.",'- **Alacritty** \u2014 `window.option_as_alt = "Both"`.','- **Terminal.app** \u2014 Settings \u2192 Profiles \u2192 Keyboard \u2192 check "Use Option as Meta key".',"","For the file-based terminals, offer to make the edit yourself: show the exact line you would add, wait for a yes, then write it and tell them to reload. iTerm2 and Terminal.app are GUI-only, so walk them through it.","","**Verify interactively before moving on.** Ask the user to press `Alt+C` and tell you what happened. A menu means it works. A `\xE7`, or nothing, means the setting did not take \u2014 a GUI terminal usually needs a new window, and inside tmux it needs the terminal restarted. Do not advance until they confirm the menu opened; every remaining lesson depends on it."].join(`
2
- `)},{title:"tmux \u2014 meet the user where they are",body:["crouter runs its viewers as tmux panes, so a user who cannot split, close, and move between panes cannot drive it. Teach the four verbs that matter: **new pane**, **new window**, **close pane**, **next pane / next window**.","","Adapt to what is already there. Check for `~/.tmux.conf` and `~/.config/tmux/tmux.conf`, and run `tmux show-options -g prefix` if a server is up.","","- **No config at all** \u2014 offer the starter config below wholesale.","- **An existing config** \u2014 read it first. Teach in *their* prefix and *their* bindings, and only offer to fill genuine gaps. Someone who already bound `|` and `-` for splits does not want your opinion; they want to know how their muscle memory maps onto crouter.","","The starter config, for a user who has nothing:","","```tmux","# prefix on Ctrl-a \u2014 easier to reach than the default Ctrl-b","set -g prefix C-a","unbind C-b","bind C-a send-prefix","","# splits that look like what they do, opening in the current directory",'bind | split-window -h -c "#{pane_current_path}"','bind - split-window -v -c "#{pane_current_path}"','bind c new-window -c "#{pane_current_path}"',"","# move between panes without the prefix","bind -n M-Left select-pane -L","bind -n M-Right select-pane -R","bind -n M-Up select-pane -U","bind -n M-Down select-pane -D","","# sane defaults","set -g mouse on","set -g base-index 1","setw -g pane-base-index 1","set -g escape-time 0","set -g history-limit 50000","```","","**Never write this silently.** Show the user exactly what you propose to add, as a diff against what is already in the file, and wait for an explicit yes. If they have a config, back it up first and say where the backup went. After writing, tell them to run `tmux source-file <path>` or start a fresh session.","","One caveat worth stating out loud: `escape-time 0` matters for crouter specifically, because a nonzero value makes tmux wait after every Escape and Alt chord, which shows up as the Alt+C menu feeling laggy or dropping keys."].join(`
3
- `)},{title:"The crouter menu \u2014 Alt+C",body:["Teach `Alt+C` as **the** crouter prefix. It opens an action menu, and nearly everything is `Alt+C` then one key. Frame it as a menu the user opens and reads, not a table to memorize \u2014 the menu shows its own options, so the only thing they truly have to remember is `Alt+C`.","","Have them press `Alt+C` now and read the menu aloud to you. Then walk the entries that matter early:","","| Chord | What it does |","|---|---|","| `Alt+C` `/` | **Find node** \u2014 the canvas navigator |","| `Alt+C` `g` | **Canvas graph** |","| `Alt+C` `i` | **Inspect** \u2014 opens a second menu (metadata, context directory) for the focused node |","| `Alt+C` `s` | Generate a **recap** \u2014 goal and state, plus a refreshed title |","| `Alt+C` `,` | **Settings** |","| `Alt+C` `x` | **Close** this node and its subtree |","","Two globals skip the menu entirely, because they are used constantly:","","| Chord | What it does |","|---|---|","| `Alt+I` | Toggle the human **inbox** |","| `Alt+]` / `Alt+[` | Next / previous node |","","Mention that `Alt+C` `,` is where every one of these bindings can be rebound, so nothing you just taught is a permanent constraint. Do not walk the settings panel now; just plant that it exists."].join(`
4
- `)},{title:"The words crouter uses",body:["The menu and graph use a few words precisely. Learn them against what is already in front of you, not as a glossary to memorize: this conversation is a **root node**, this pane is a **viewer**, and the process behind it that runs the agent is the **broker**.","","A **node** is one conversation or unit of work. The **canvas** is every node on this machine and the links between them. A root has no manager above it; when it delegates, it creates child nodes below it.","","An **orchestrator** is a node that breaks work into independent pieces and manages children. A base node does the work itself. **Kinds** specialize a node with a particular role and instructions, such as development or research; they do not change which conversation the node is.","","The **spine** is the reporting path from a child to the manager that subscribed to it. `crtr push` sends a report up that path and wakes the manager, so a manager can end its turn instead of polling every child.","","A **yield** refreshes a node that is running out of context while keeping its identity and mandate. A **wedged** node is one the runtime has detected as stuck rather than merely waiting; its fault state tells you it needs attention. These distinctions matter because an idle node may simply be waiting for a person, while a wedged one needs recovery."].join(`
5
- `)},{title:"Resuming a conversation",body:["`Alt+C` then `/` opens the canvas navigator: a searchable list of every conversation on this machine. Pick one, land in it, keep going.","","Have the user open it and look around. On a fresh install there may be only one entry \u2014 that is fine, the point is the reflex, not the contents.","","The idea to land, which matters more than the keystroke: **conversations are durable and addressable.** A crouter conversation is not a terminal session that dies when the window closes. It is a node on the canvas with an id, a saved transcript, and a context directory. Walking away does not lose it, and finding it again is a search rather than an act of archaeology.","","**Say this part explicitly \u2014 it changes how people use crouter.** The viewer and the node are decoupled. Closing the tmux pane, closing the window, or quitting the terminal outright detaches the viewer and leaves the node running. Nothing is killed by walking away, and reattaching later picks up exactly where it was. New users assume the opposite and babysit panes they do not need to; tell them plainly that they can close it. Ending a node is a deliberate, separate action (`Alt+C` `x`). Say it once, here \u2014 later lessons assume it and do not repeat it.","","Worth mentioning while they are in the navigator: `p` pins a conversation so it sorts to the top, which is how people keep their two or three live threads reachable without searching."].join(`
6
- `)},{title:"Memory",body:["Keep this abstract. The user does not need the storage model; they need to know memory exists, that it is scoped, and that they can drive it by just asking.","","Cover four points:","","1. **Two kinds.** *Knowledge* is how to do something \u2014 a playbook, a technique, a fact about a system. *Preference* is how the user wants the agent to behave \u2014 a standing instruction or a correction.","","2. **Where a document lives decides who sees it.** A memory can be scoped to everything the user does, to one project, to one profile, or to a single node. That scope choice is what keeps a work convention from leaking into a personal project.","","3. **Documents are routed, not loaded.** Each one carries a condition describing when it is worth reading. An agent pulls in the handful that match the task instead of loading everything, which is why a large memory store does not slow anything down or crowd out the actual work.","",'4. **The practical instruction is one sentence: just say "remember that."** The agent decides the kind, writes the document, and picks the scope. The user can correct any of those in follow-up. This is most of what a new user needs; the rest is detail they can pick up when they care.',"","If you have genuinely learned something durable about this user during the tutorial \u2014 their terminal, their tmux prefix, a convention they stated \u2014 offer to save it now as a real demonstration. A memory the user watched get created from a real fact beats any explanation of the feature."].join(`
7
- `)},{title:"The graph \u2014 Alt+C then g",body:["The graph shows the node tree: who spawned whom, what is running, what has finished.","","A tour against a single row teaches nothing, so put a few nodes on the canvas first:","","```bash","crtr sys tutorial scenario start idle-nodes","```","","That creates three idle nodes purely for the demo. They never run a turn, so it costs nothing. Then have the user press `Alt+C` `g` and navigate:","","| Key | Action |","|---|---|","| `j` / `k` | Move down / up |","| `Enter` | Focus the selected node \u2014 open it in this pane |","| `l` / `h` | Move to a child / back to the manager |","| `m` | Jump to the selected node\u2019s manager |","| `r` | Revive a dormant node |","| `x` | Close the selected subtree |","| `Esc` | Dismiss the graph |","","**Be honest about what the demo shows.** The scenario creates independent roots, not a tree, so `j`/`k`/`Enter`/`Esc` are the keys they can actually practice here. Explain `h` and `l` as what they will use once they delegate work and have real parent-child structure \u2014 the advanced track covers where that structure comes from.","","Then tear the demo down:","","```bash","crtr sys tutorial scenario clean idle-nodes","```"].join(`
8
- `)},{title:"Status glyphs",body:["The graph gives every node a status glyph, so you can read the canvas before opening each conversation. The five ordinary statuses are:","","| Glyph | Status | What it means |","|---|---|---|",`| ${t.glyph} | active | The node is working. |`,`| ${n.glyph} | idle | The node is not currently running a turn. |`,`| ${a.glyph} | done | The node finished its work. |`,`| ${o.glyph} | dead | The node ended without remaining live work. |`,`| ${i.glyph} | canceled | The node was canceled. |`,"",`Three overlays replace that ordinary status when they are more important: ${s.glyph} live means a turn is streaming now; ${r.glyph} frozen means the node is waiting for broker capacity; ${h.glyph} fault means the runtime found a problem that needs attention.`,"","The precedence is `fault > frozen > live > status`. Read the leftmost applicable condition: a fault hides frozen or live, frozen hides live, and live hides the ordinary status.","","Put a few rows in the graph so the glyphs have something to describe:","","```bash","crtr sys tutorial scenario start idle-nodes","```","","Open `Alt+C` then `g`, identify the idle rows, then clean up the demo yourself:","","```bash","crtr sys tutorial scenario clean idle-nodes","```","","Do not leave this state for another lesson. A user may jump to any lesson, and every demo must leave the canvas as it found it."].join(`
9
- `)},{title:"The inbox \u2014 Alt+I",body:["The inbox is how a node reaches the user. When an agent needs a decision, wants a document reviewed, or is blocked on something only a person can settle, it posts there and goes dormant. Without the inbox that agent looks stuck; with it, the user can see it is simply waiting on them.","","Give them something real to answer:","","```bash","crtr sys tutorial scenario start pending-ask","```","","Tell them the entry is titled **A question from the tutorial**, so they can find it even if real requests are already waiting. Then have them press `Alt+I`, read it, and respond to it. Walk them through selecting the item and answering, and point out that a real answer wakes the waiting node immediately \u2014 a reply is not filed for later, it resumes the work that was blocked on it. (This demo request has no node behind it, so nothing wakes; say so rather than implying otherwise.)","","Clean up when the point has landed:","","```bash","crtr sys tutorial scenario clean pending-ask","```","","The habit worth leaving them with: **check the inbox when something seems idle.** An agent that appears to have stopped has very often asked a question and is waiting."].join(`
10
- `)}],c=[{title:"Promotion to orchestrator",body:["Every node is one of two modes. A **base node** does the work itself \u2014 reads, edits, runs commands. An **orchestrator** holds a roadmap and delegates, spawning children for the pieces and integrating what comes back.","","- `Alt+C` `o` promotes the focused node to orchestrator.","- `Alt+C` `Shift+O` demotes it back to a base node.","- From inside a conversation, `/promote` does the same thing, and `/promote <kind>` specializes it at the same time.","","Teach *when*, because the keystroke is the easy part. Promotion earns its cost when the work has **genuinely independent parts** that can run at the same time, or **many large phases** that would otherwise fill a single context window. It is the wrong move for a two-file change: spinning up a child and waiting for its report costs more than doing the work directly.","","The mechanism worth naming: a child reports up to whoever subscribes to it, and that report **wakes the manager**. Nobody polls. An orchestrator delegates, ends its turn, and comes back when there is something to integrate \u2014 which is why a fan-out of five children costs one manager context window rather than five.","","The failure mode to warn about is recursive delegation: an orchestrator that hands its entire assignment to one child has not decomposed anything, it has added a layer. Each child needs a bounded outcome genuinely smaller than the whole."].join(`
11
- `)},{title:"Terminal and resident nodes",body:["Lifecycle is the other axis, orthogonal to mode. It answers: what does this node owe, and when is it allowed to end?","","- A **terminal** node owes a final report up the spine. It does its work, delivers, and reaps itself. This is the shape for bounded delegated work.","- A **resident** node is interactable, never forced to submit a final, and stays wakeable indefinitely. This is the shape for a conversation with a person.","","`Alt+C` `d` demotes a node to terminal. `Alt+C` `Shift+R` makes one resident. Roots default to resident; managed children default to terminal.","","**Why it works this way** \u2014 this is the part worth understanding, not memorizing.","","A terminal node is *depended upon to finish*. Its manager subscribed to it and will be woken by its report, so the guarantee that it must eventually deliver one is exactly what makes delegation safe. Without it, an orchestrator could wait forever on a child that quietly decided it was done.","","A resident node must never be forced to close mid-sentence. A conversation with a person has no natural completion \u2014 the user may come back tomorrow \u2014 so requiring a final report would either truncate the conversation or produce a meaningless one. Residency removes that requirement, and going dormant costs nothing: the node holds no window and burns no compute while it waits.","","This is why **the user's own conversation is resident and cannot be finished on its behalf**, while the children it spawns are terminal and reap themselves when they report. The user sees this as agents that clean up after themselves while their own thread persists \u2014 that is the lifecycle axis doing its job.","","Related and worth a mention: a **yield** lets a resident node whose context is filling revive fresh under the same identity and mandate, carrying a note to its future self. The conversation survives; only the context window is recycled."].join(`
12
- `)}];export{c as ADVANCED_LESSONS,l as BASIC_LESSONS};
1
+ const e=[{title:"Return to a conversation \u2014 recap, find, pin",body:["Start with a conversation the user actually wants to return to. `Alt+C` opens the crouter action menu; its entries show the keys, so do not ask the user to memorize a shortcut list.","","Ask them to press `Alt+C`. If Option types an accented character instead of opening the menu, identify their terminal and help set Option to send Alt/Esc; show any proposed config edit and get permission before writing it. If they cannot use tmux panes yet, inspect their existing tmux prefix and bindings and teach only the pane actions they need. These are conditional setup steps, not separate lessons.","","Have them choose `s` from the menu to refresh this conversation\u2019s Goal/State recap and generated title. Point out the Goal above the transcript and the State above the editor. This generates content using an agent; do not describe it as a free visual toggle. Then open Find node with `Alt+C`, `/`, search for the refreshed title, and select the conversation. The navigator searches conversations across the canvas; the graph in a later lesson is only for nodes connected to the current conversation.","","In Find node, have them select this conversation and press `p` to pin it; the star and sorting change. If it was already pinned, do not unpin it merely for practice. A pin persists, so ask before changing it and restore the original state if this is only a demonstration.","","Explain the important distinction: closing the viewer or terminal detaches from the conversation; it does not close the node. They can find it again. `Alt+C`, `x` deliberately closes the node and its subtree, so do not use it for detaching. Let them try recap, find, and pin before proceeding."].join(`
2
+ `)},{title:"Answer an agent \u2014 the inbox",body:["When an agent needs a decision, it can put a question in the human inbox and wait without polling. The answer to an agent\u2019s own question wakes that agent. Teach the habit: if work seems paused, check the inbox with `Alt+I`.","","Ask before creating the disposable tutorial question. Once they agree, run:","","```bash",'crtr sys tutorial scenario start pending-ask --parent "$CRTR_NODE_ID"',"```","","Have them open **A question from the tutorial** with `Alt+I`, select an option with `j`/`k`, toggle it with Space, optionally add a comment with `c`, and submit. This question links back to the tutorial conversation but **no agent waits on its answer**; it practices the same inbox controls as a real request. Clean up afterward, even if they decide not to submit:","","```bash","crtr sys tutorial scenario clean pending-ask","```"].join(`
3
+ `)},{title:"Make it yours \u2014 settings and plugins",body:["Open `Alt+C`, `,` to inspect settings and keybindings. Let the user find one binding they care about. In the keybinding editor, `/` filters, Enter edits, and Escape discards an unsaved edit. Do not save a binding change unless they ask for that exact change; saved bindings affect their future sessions.","","Then use `Alt+C`, `l` to browse plugins. Have them inspect one plugin\u2019s capabilities and decide whether it is relevant to their work. Browsing does not install it. Installing or enabling a plugin changes local configuration, so do that only if they choose a specific plugin and approve the change.","","These are discovery tools, not a feature tour: the user should leave knowing how to find a setting or capability when they need it, not with a new configuration they did not request."].join(`
4
+ `)},{title:"Take another direction \u2014 fork",body:["Forking lets the user pursue another direction without replacing the original conversation. `Alt+C`, `f` forks the focused node into a separate, idle root. It does not start a model turn; the fork is a new conversation that remains on the canvas until closed.","","Ask whether they have a disposable conversation to fork and want to try it. Do not fork the current long-lived conversation just to make a demonstration. If they agree, focus that conversation, use `Alt+C`, `f`, then find both the original and the new root with `Alt+C`, `/`. Confirm which is which before offering to close the disposable fork; closing it is a separate, destructive action that needs permission.","","If there is no suitable conversation or they decline to create durable state, explain the action and move on. Mention `/fork` only if they specifically need to choose a point within the conversation; the menu action is enough for this lesson."].join(`
5
+ `)},{title:"Keep a useful fact \u2014 memory",body:["The practical instruction is \u201Cremember that.\u201D The user does not need to choose a storage format: the agent can propose whether the fact is knowledge about how to do something or a preference about how it should behave, and where that memory should apply.","","Ask whether there is a genuine, durable fact from their work that they want the agent to remember. State the proposed wording and scope, and get their approval before writing. Do not manufacture a personal preference or save a fact just to complete the exercise. If none exists, let them try the phrase as a future request and move on.","","A project fact should not silently become a global preference. The useful outcome is that the user knows they can say \u201Cremember that\u201D and correct the proposed wording or scope."].join(`
6
+ `)},{title:"Follow delegated work \u2014 the graph",body:["Use a real task with independent parts if one is already underway. Delegation is useful when pieces can run separately or a task has several large phases; do not promote a node or spawn children solely to fill a graph. A small task stays with one agent.","","Press `Alt+g` to open the graph of this conversation and its connected nodes. With real children present, `l` expands a parent or moves to its first child, `j`/`k` selects rows, `h` moves toward the parent, Enter focuses a child, and Esc closes the graph. Do not use `x`: it asks to close a subtree. If no real children are present, describe these controls briefly and let the user revisit the graph when an agent delegates actual work.","","A manager may look idle while its children run. That is expected: it ends its turn and waits for a child\u2019s report, which wakes it to integrate the result. A running broker is not proof that a model turn is streaming; a fault is different from ordinary waiting and needs attention. Explain status only in response to what the user actually sees, not as a glyph glossary.","","The visible result should be a user who can open a working child and return to its manager without wondering why the manager stopped speaking. End the tutorial here; promotion, node modes, and lifecycle commands are details to explain only when a real task requires them."].join(`
7
+ `)}];export{e as BASIC_LESSONS};
@@ -1,2 +1,2 @@
1
- var S=Object.defineProperty;var a=(e,t)=>S(e,"name",{value:t,configurable:!0});import{mkdirSync as w,readFileSync as A}from"node:fs";import{dirname as y,join as R}from"node:path";import{ApiError as b}from"../../../api/index.js";import{withMetaLock as q,writeMetaObjectLocked as O}from"../../../core/canvas/meta-file.js";import{defineBranch as T,defineLeaf as f}from"../../../core/command.js";import{InputError as u}from"../../../core/io.js";import{userScopeRoot as E}from"../../../core/scope.js";import{cliClient as c,getNodeOrNull as L,rethrowAsCliError as l}from"../../api-client.js";const _={"idle-nodes":{title:"Three idle nodes on the canvas",teaches:"the graph lesson \u2014 j/k navigation is unteachable against a single row",builds:"three independent nodes that never run a turn, so they cost nothing"},"pending-ask":{title:"A question waiting in the inbox",teaches:"the inbox lesson \u2014 reading a pending item and answering it",builds:"one durable pending request in the human inbox"}},C=["export default function TutorialAsk() {"," return (",' <Page title="A question from the tutorial" subtitle="This is what an agent waiting on you looks like.">'," <UserQuestion",' id="answer"',' label="Which of these should the tutorial cover next?"',' body="Nothing depends on this answer \u2014 it exists so you can practice responding to a real inbox item."',' mode="single"'," options={["," { id: 'orchestration', label: 'Delegating work to other agents' },"," { id: 'memory', label: 'Teaching it my preferences' },"," { id: 'done', label: 'Nothing \u2014 I want to start working' },"," ]}"," />"," </Page>"," );","}"].join(`
2
- `);function h(){return R(E(),"tutorial-scenarios.json")}a(h,"ledgerPath");function k(e){return Array.isArray(e)&&e.every(t=>typeof t=="string")}a(k,"isStringArray");function v(){let e;try{e=JSON.parse(A(h(),"utf8"))}catch(r){if(r.code==="ENOENT")return{};throw r}if(typeof e!="object"||e===null||Array.isArray(e))throw new u({error:"invalid_scenario_ledger",message:`tutorial scenario ledger at ${h()} must be an object`,next:"Remove or repair the ledger file, then retry the scenario command."});const t={};for(const r of Object.keys(_)){const n=e[r];if(n===void 0)continue;if(typeof n!="object"||n===null||typeof n.started!="string")throw new u({error:"invalid_scenario_ledger",message:`tutorial scenario ledger has an invalid ${r} record`,next:"Remove or repair the ledger file, then retry the scenario command."});const s=n;if(s.nodes!==void 0&&!k(s.nodes)||s.requests!==void 0&&!k(s.requests))throw new u({error:"invalid_scenario_ledger",message:`tutorial scenario ledger has invalid ids for ${r}`,next:"Remove or repair the ledger file, then retry the scenario command."});t[r]={started:s.started,...s.nodes===void 0?{}:{nodes:s.nodes},...s.requests===void 0?{}:{requests:s.requests}}}return t}a(v,"readLedger");function x(e){const t=h();return w(y(t),{recursive:!0}),q(t,()=>e(v()))}a(x,"withLedger");function p(e){const t=h();return w(y(t),{recursive:!0}),q(t,()=>{const r=v(),n=e(r);return O(t,r),n})}a(p,"updateLedger");function I(e){return p(t=>{if(t[e]!==void 0)throw new u({error:"scenario_already_standing",message:`the '${e}' scenario is already standing (started ${t[e].started})`,field:"scenario",next:`Run \`crtr sys tutorial scenario clean ${e}\` first, then start it again.`});const r={started:new Date().toISOString(),...e==="idle-nodes"?{nodes:[]}:{requests:[]}};return t[e]=r,r})}a(I,"reserveScenario");async function N(e,t,r){if(!p(s=>{const i=s[e];return i===void 0?!1:(i[t]=[...i[t]??[],r],!0)})){try{t==="nodes"?await c().closeNode(r):await c().cancelHumanRequest(r,{reason:"Tutorial scenario start interrupted."})}catch(s){m(s)||l(s)}throw new Error(`tutorial scenario '${e}' disappeared while it was starting`)}}a(N,"appendScenarioId");function m(e){return e instanceof b&&(e.status===404||e.code==="already_settled")}a(m,"isAlreadyCleaned");function $(e){return e==="done"||e==="dead"||e==="canceled"}a($,"isTerminalNode");function j(e){const t=e.scenario;if(t==="idle-nodes"||t==="pending-ask")return t;throw new u({error:"unknown_scenario",message:`no tutorial scenario named '${String(t)}'`,field:"scenario",next:"Use idle-nodes or pending-ask. Run `crtr sys tutorial scenario list` for what each one builds."})}a(j,"scenario");async function P(e){const t=["tutorial-demo-one","tutorial-demo-two","tutorial-demo-three"];for(const r of t){const n=await c().createNode({root:!0,no_kickoff:!0,prompt:"",cwd:process.cwd(),prefer_warm:!0,name:r}).catch(l);await N("idle-nodes","nodes",n.node_id),e.nodes.push(n.node_id)}}a(P,"startIdleNodes");async function U(e){const t=await c().createHumanRequest({page:{dialect:"jsx",source:C},delivery:{placement:"inline",inbox:!0,reply:!1},source:{askedBy:"crtr sys tutorial",sessionName:"tutorial"},creator_cwd:process.cwd()}).catch(l);await N("pending-ask","requests",t.request_id),e.requests.push(t.request_id)}a(U,"startPendingAsk");const B=f({name:"list",description:"list the tutorial practice scenarios",whenToUse:"you need to choose a disposable canvas scenario for a lesson or see whether one is already standing.",help:{name:"sys tutorial scenario list",summary:"list the tutorial practice scenarios and their standing state",inputNote:"No input parameters.",output:[{name:"scenarios",type:"object[]",required:!0,constraint:"One row per scenario: {id, title, teaches, builds, standing}."},{name:"standing",type:"number",required:!0,constraint:"Number of scenarios currently recorded as standing."}],outputKind:"object",effects:["None. Read-only."]},run:a(async()=>{const e=x(r=>r),t=Object.entries(_).map(([r,n])=>({id:r,title:n.title,teaches:n.teaches,builds:n.builds,standing:e[r]!==void 0}));return{scenarios:t,standing:t.filter(r=>r.standing).length}},"run")}),D=f({name:"start",description:"create a tutorial practice scenario",whenToUse:"a tutorial lesson needs its disposable graph rows or inbox request before the user can practice against a real canvas surface.",help:{name:"sys tutorial scenario start",summary:"create one tutorial practice scenario",params:[{kind:"positional",name:"scenario",type:"string",required:!0,constraint:"Scenario to create: idle-nodes (three no-kickoff roots) or pending-ask (one durable inbox request)."}],output:[{name:"nodes",type:"string[]",required:!1,constraint:"Created root node ids for idle-nodes."},{name:"requests",type:"string[]",required:!1,constraint:"Created human request ids for pending-ask."},{name:"clean_with",type:"string",required:!0,constraint:"Exact command that removes this scenario."},{name:"look_at",type:"string",required:!0,constraint:"Viewer surface that shows the created state."}],outputKind:"object",effects:["idle-nodes creates three independent root rows with no kickoff turn and records their ids in ~/.crouter/tutorial-scenarios.json.","pending-ask publishes one durable human request and records its id in ~/.crouter/tutorial-scenarios.json.","Refuses a scenario already recorded as standing; clean it before starting it again."]},run:a(async e=>{const t=j(e),r=I(t);return t==="idle-nodes"?await P(r):await U(r),{...r.nodes===void 0?{}:{nodes:r.nodes},...r.requests===void 0?{}:{requests:r.requests},clean_with:`crtr sys tutorial scenario clean ${t}`,look_at:t==="idle-nodes"?"Open the graph with `Alt+C` then `g`.":"Open the inbox with `Alt+I`."}},"run")}),H=f({name:"clean",description:"remove a tutorial practice scenario",whenToUse:"the lesson has finished with a scenario and you need to remove exactly the nodes or request it recorded.",help:{name:"sys tutorial scenario clean",summary:"remove exactly the rows recorded for one tutorial practice scenario",params:[{kind:"positional",name:"scenario",type:"string",required:!0,constraint:"Scenario to remove: idle-nodes or pending-ask."}],output:[{name:"was_standing",type:"boolean",required:!0,constraint:"Whether this scenario had a ledger record to remove."},{name:"closed_nodes",type:"number",required:!1,constraint:"Number of ledger-recorded node ids closed. Present when the scenario was standing."},{name:"canceled_requests",type:"number",required:!1,constraint:"Number of ledger-recorded request ids canceled. Present when the scenario was standing."},{name:"already_settled_requests",type:"number",required:!1,constraint:"Number of ledger-recorded request ids already terminal when clean processed them. Present when the scenario was standing."}],outputKind:"object",effects:["Closes or cancels only ids recorded for this scenario in ~/.crouter/tutorial-scenarios.json, then removes that scenario record."]},run:a(async e=>{const t=j(e),r=x(o=>o[t]);if(r===void 0)return{was_standing:!1};let n=0,s=0,i=0;for(const o of r.nodes??[]){const d=await L(o);if(!(d===null||$(d.status)))try{await c().closeNode(o),n++}catch(g){m(g)||l(g)}}for(const o of r.requests??[])try{await c().cancelHumanRequest(o,{reason:"Tutorial scenario cleaned up."}),s++}catch(d){d instanceof b&&d.code==="already_settled"?i++:m(d)||l(d)}return p(o=>{delete o[t]}),{was_standing:!0,closed_nodes:n,canceled_requests:s,...i===0?{}:{already_settled_requests:i}}},"run")}),X=T({name:"scenario",description:"create and remove disposable tutorial practice state",whenToUse:"a tutorial lesson needs real but disposable canvas rows or an inbox request to inspect and practice against.",help:{name:"sys tutorial scenario",summary:"create and remove disposable canvas state for tutorial lessons",model:"Each scenario records only the ids it creates. Start refuses an already-standing scenario, and clean acts only on that recorded list."},children:[B,D,H]});export{X as scenarioBranch};
1
+ var A=Object.defineProperty;var a=(e,t)=>A(e,"name",{value:t,configurable:!0});import{mkdirSync as w,readFileSync as R}from"node:fs";import{dirname as y,join as T}from"node:path";import{ApiError as b}from"../../../api/index.js";import{withMetaLock as q,writeMetaObjectLocked as O}from"../../../core/canvas/meta-file.js";import{defineBranch as E,defineLeaf as p}from"../../../core/command.js";import{InputError as c}from"../../../core/io.js";import{userScopeRoot as P}from"../../../core/scope.js";import{cliClient as l,getNodeOrNull as _,rethrowAsCliError as u}from"../../api-client.js";const v={"idle-nodes":{title:"Three practice children of a tutorial conversation",teaches:"the graph lesson \u2014 navigate a parent and its children",builds:"three child nodes without tasks under the specified parent; their brokers run until cleanup"},"pending-ask":{title:"A question waiting in the inbox",teaches:"the inbox lesson \u2014 reading a pending item and answering it",builds:"one durable pending request in the human inbox"}},$=["export default function TutorialAsk() {"," return (",' <Page title="A question from the tutorial" subtitle="Practice choosing and commenting on an option.">'," <UserQuestion",' id="answer"',' label="Which crouter topics would you like to explore?"',' body="Select one or more topics with Space and add a comment to an option with c. This is practice; no agent acts on your answer."',' mode="multi"'," options={["," { id: 'orchestration', label: 'Delegating work to other agents' },"," { id: 'memory', label: 'Teaching it my preferences' },"," { id: 'navigation', label: 'Finding conversations and navigating the canvas' },"," ]}"," />"," </Page>"," );","}"].join(`
2
+ `);function h(){return T(P(),"tutorial-scenarios.json")}a(h,"ledgerPath");function k(e){return Array.isArray(e)&&e.every(t=>typeof t=="string")}a(k,"isStringArray");function x(){let e;try{e=JSON.parse(R(h(),"utf8"))}catch(r){if(r.code==="ENOENT")return{};throw r}if(typeof e!="object"||e===null||Array.isArray(e))throw new c({error:"invalid_scenario_ledger",message:`tutorial scenario ledger at ${h()} must be an object`,next:"Remove or repair the ledger file, then retry the scenario command."});const t={};for(const r of Object.keys(v)){const n=e[r];if(n===void 0)continue;if(typeof n!="object"||n===null||typeof n.started!="string")throw new c({error:"invalid_scenario_ledger",message:`tutorial scenario ledger has an invalid ${r} record`,next:"Remove or repair the ledger file, then retry the scenario command."});const s=n;if(s.nodes!==void 0&&!k(s.nodes)||s.requests!==void 0&&!k(s.requests))throw new c({error:"invalid_scenario_ledger",message:`tutorial scenario ledger has invalid ids for ${r}`,next:"Remove or repair the ledger file, then retry the scenario command."});t[r]={started:s.started,...s.nodes===void 0?{}:{nodes:s.nodes},...s.requests===void 0?{}:{requests:s.requests}}}return t}a(x,"readLedger");function N(e){const t=h();return w(y(t),{recursive:!0}),q(t,()=>e(x()))}a(N,"withLedger");function f(e){const t=h();return w(y(t),{recursive:!0}),q(t,()=>{const r=x(),n=e(r);return O(t,r),n})}a(f,"updateLedger");function I(e){return f(t=>{if(t[e]!==void 0)throw new c({error:"scenario_already_standing",message:`the '${e}' scenario is already standing (started ${t[e].started})`,field:"scenario",next:`Run \`crtr sys tutorial scenario clean ${e}\` first, then start it again.`});const r={started:new Date().toISOString(),...e==="idle-nodes"?{nodes:[]}:{requests:[]}};return t[e]=r,r})}a(I,"reserveScenario");async function S(e,t,r){if(!f(s=>{const i=s[e];return i===void 0?!1:(i[t]=[...i[t]??[],r],!0)})){try{t==="nodes"?await l().closeNode(r):await l().cancelHumanRequest(r,{reason:"Tutorial scenario start interrupted."})}catch(s){m(s)||u(s)}throw new Error(`tutorial scenario '${e}' disappeared while it was starting`)}}a(S,"appendScenarioId");function m(e){return e instanceof b&&(e.status===404||e.code==="already_settled")}a(m,"isAlreadyCleaned");function L(e){return e==="done"||e==="dead"||e==="canceled"}a(L,"isTerminalNode");function j(e){const t=e.scenario;if(t==="idle-nodes"||t==="pending-ask")return t;throw new c({error:"unknown_scenario",message:`no tutorial scenario named '${String(t)}'`,field:"scenario",next:"Use idle-nodes or pending-ask. Run `crtr sys tutorial scenario list` for what each one builds."})}a(j,"scenario");async function C(e,t){const r=["tutorial-demo-one","tutorial-demo-two","tutorial-demo-three"];for(const n of r){const s=await l().createNode({parent:t,creator:t,root_lifecycle:"resident",no_kickoff:!0,prompt:"",cwd:process.cwd(),name:n}).catch(u);await S("idle-nodes","nodes",s.node_id),e.nodes.push(s.node_id)}}a(C,"startIdleNodes");async function U(e,t){const r=await l().createHumanRequest({page:{dialect:"jsx",source:$},delivery:{placement:"inline",inbox:!0,reply:!1},source:{askedBy:"crtr sys tutorial",sessionName:"tutorial",nodeId:t},creator_cwd:process.cwd()}).catch(u);await S("pending-ask","requests",r.request_id),e.requests.push(r.request_id)}a(U,"startPendingAsk");const B=p({name:"list",description:"list the tutorial practice scenarios",whenToUse:"you need to choose a disposable canvas scenario for a lesson or see whether one is already standing.",help:{name:"sys tutorial scenario list",summary:"list the tutorial practice scenarios and their standing state",inputNote:"No input parameters.",output:[{name:"scenarios",type:"object[]",required:!0,constraint:"One row per scenario: {id, title, teaches, builds, standing}."},{name:"standing",type:"number",required:!0,constraint:"Number of scenarios currently recorded as standing."}],outputKind:"object",effects:["None. Read-only."]},run:a(async()=>{const e=N(r=>r),t=Object.entries(v).map(([r,n])=>({id:r,title:n.title,teaches:n.teaches,builds:n.builds,standing:e[r]!==void 0}));return{scenarios:t,standing:t.filter(r=>r.standing).length}},"run")}),D=p({name:"start",description:"create a tutorial practice scenario",whenToUse:"a tutorial lesson needs its disposable graph rows or inbox request before the user can practice against a real canvas surface.",help:{name:"sys tutorial scenario start",summary:"create one tutorial practice scenario",params:[{kind:"positional",name:"scenario",type:"string",required:!0,constraint:"idle-nodes (three children without tasks) or pending-ask (one durable inbox request)."},{kind:"flag",name:"parent",type:"string",required:!0,constraint:"Node id of the tutorial conversation; children and the inbox request are linked to this node."}],output:[{name:"nodes",type:"string[]",required:!1,constraint:"Created child node ids for idle-nodes."},{name:"requests",type:"string[]",required:!1,constraint:"Created human request ids for pending-ask."},{name:"clean_with",type:"string",required:!0,constraint:"Exact command that removes this scenario."},{name:"look_at",type:"string",required:!0,constraint:"Viewer surface that shows the created state."}],outputKind:"object",effects:["idle-nodes creates three resident children of --parent with no kickoff turn; their brokers run until cleanup. Their ids are recorded in ~/.crouter/tutorial-scenarios.json.","pending-ask publishes one durable human request with --parent as its source; answering it does not wake that node. Its id is recorded in ~/.crouter/tutorial-scenarios.json.","Refuses a scenario already recorded as standing; clean it before starting it again."]},run:a(async e=>{const t=j(e),r=e.parent;if(r===void 0||r.trim()==="")throw new c({error:"parent_required",message:`${t} needs the tutorial conversation node id in --parent`,field:"parent",next:"Pass the node id of the conversation the user is viewing."});if(await _(r)===null)throw new c({error:"unknown_parent",message:`no local node exists for --parent ${r}`,field:"parent",next:"Pass the node id of a conversation on this canvas."});const n=I(t);return t==="idle-nodes"?await C(n,r):await U(n,r),{...n.nodes===void 0?{}:{nodes:n.nodes},...n.requests===void 0?{}:{requests:n.requests},clean_with:`crtr sys tutorial scenario clean ${t}`,look_at:t==="idle-nodes"?"Open the graph with `Alt+g`.":"Open the inbox with `Alt+I`."}},"run")}),H=p({name:"clean",description:"remove a tutorial practice scenario",whenToUse:"the lesson has finished with a scenario and you need to remove exactly the nodes or request it recorded.",help:{name:"sys tutorial scenario clean",summary:"remove exactly the rows recorded for one tutorial practice scenario",params:[{kind:"positional",name:"scenario",type:"string",required:!0,constraint:"Scenario to remove: idle-nodes or pending-ask."}],output:[{name:"was_standing",type:"boolean",required:!0,constraint:"Whether this scenario had a ledger record to remove."},{name:"closed_nodes",type:"number",required:!1,constraint:"Number of ledger-recorded node ids closed. Present when the scenario was standing."},{name:"canceled_requests",type:"number",required:!1,constraint:"Number of ledger-recorded request ids canceled. Present when the scenario was standing."},{name:"already_settled_requests",type:"number",required:!1,constraint:"Number of ledger-recorded request ids already terminal when clean processed them. Present when the scenario was standing."}],outputKind:"object",effects:["Closes or cancels only ids recorded for this scenario in ~/.crouter/tutorial-scenarios.json, then removes that scenario record."]},run:a(async e=>{const t=j(e),r=N(o=>o[t]);if(r===void 0)return{was_standing:!1};let n=0,s=0,i=0;for(const o of r.nodes??[]){const d=await _(o);if(!(d===null||L(d.status)))try{await l().closeNode(o),n++}catch(g){m(g)||u(g)}}for(const o of r.requests??[])try{await l().cancelHumanRequest(o,{reason:"Tutorial scenario cleaned up."}),s++}catch(d){d instanceof b&&d.code==="already_settled"?i++:m(d)||u(d)}return f(o=>{delete o[t]}),{was_standing:!0,closed_nodes:n,canceled_requests:s,...i===0?{}:{already_settled_requests:i}}},"run")}),X=E({name:"scenario",description:"create and remove disposable tutorial practice state",whenToUse:"a tutorial lesson needs real but disposable canvas rows or an inbox request to inspect and practice against.",help:{name:"sys tutorial scenario",summary:"create and remove disposable canvas state for tutorial lessons",model:"Each scenario records only the ids it creates. Start refuses an already-standing scenario, and clean acts only on that recorded list."},children:[B,D,H]});export{X as scenarioBranch};
@@ -1,2 +1 @@
1
1
  export declare const basicLeaf: import("../../../core/command.js").LeafDef;
2
- export declare const advancedLeaf: import("../../../core/command.js").LeafDef;
@@ -1,4 +1,4 @@
1
- var p=Object.defineProperty;var a=(e,t)=>p(e,"name",{value:t,configurable:!0});import{defineLeaf as g}from"../../../core/command.js";import{InputError as b}from"../../../core/io.js";import{ADVANCED_LESSONS as w,BASIC_LESSONS as v}from"./lessons.js";const $=["Teach this conversationally, in your own words \u2014 do not paste this lesson at the user.","Ask before you change anything on their machine, and show the exact diff you propose.","Let the user actually try each thing and confirm it worked before you move on."].join(" "),m={basic:v,advanced:w};function T(e){return e.map((t,n)=>`${n+1}. ${t.title}`).join(`
2
- `)}a(T,"tableOfContents");async function k(){const{resolveRoot:e}=await import("../../../build-root.js"),n=(await e("sys")).subtrees.find(s=>s.name==="sys");if(n?.kind!=="branch")throw new Error("The assembled sys subtree is missing.");const r=n.children.find(s=>s.name==="tutorial");if(r?.kind!=="branch")throw new Error("The assembled sys tutorial branch is missing.");if(r.help.listing===void 0)throw new Error("The assembled sys tutorial listing is missing.");return r.help.listing}a(k,"tutorialListing");function x(e){return e.length===0?"That is the whole tutorial.":`What's next: ${e.map(({command:t,description:n})=>`${t} \u2014 ${n}`).join("; ")}`}a(x,"nextTracksLine");async function L(e,t){const n=m[e],r=n.length,s=t.step,o=s==null?1:Number(s);if(!Number.isInteger(o)||o<1||o>r)throw new b({error:"step_out_of_range",message:`--step must be between 1 and ${r} (got ${String(s)})`,field:"step",next:`Run \`crtr sys tutorial ${e} -h\` and read the contents, then pick a lesson in range.`});const d=n[o-1],u=o<r?`crtr sys tutorial ${e} --step ${o+1}`:void 0,l=u===void 0?await k():void 0;let c;if(l!==void 0){const f=l.filter(i=>i.plugin!==void 0).map(i=>({command:`crtr sys tutorial ${i.name}`,description:i.description}));if(e==="basic"){const i=l.find(y=>y.name==="advanced");if(i===void 0)throw new Error("The assembled sys tutorial advanced track is missing.");c=[{command:"crtr sys tutorial advanced",description:i.description},...f]}else c=f}return{track:e,step:o,total:r,title:d.title,body:c===void 0?d.body:`${d.body}
1
+ var h=Object.defineProperty;var i=(e,n)=>h(e,"name",{value:n,configurable:!0});import{defineLeaf as m}from"../../../core/command.js";import{InputError as y}from"../../../core/io.js";import{BASIC_LESSONS as p}from"./lessons.js";const g=["Teach this conversationally, in your own words \u2014 do not paste this lesson at the user.","Ask before you change anything on their machine, and show the exact diff you propose.","Let the user actually try each thing and confirm it worked before you move on."].join(" "),f={basic:p};function b(e){return e.map((n,t)=>`${t+1}. ${n.title}`).join(`
2
+ `)}i(b,"tableOfContents");async function w(){const{resolveRoot:e}=await import("../../../build-root.js"),t=(await e("sys")).subtrees.find(s=>s.name==="sys");if(t?.kind!=="branch")throw new Error("The assembled sys subtree is missing.");const r=t.children.find(s=>s.name==="tutorial");if(r?.kind!=="branch")throw new Error("The assembled sys tutorial branch is missing.");if(r.help.listing===void 0)throw new Error("The assembled sys tutorial listing is missing.");return r.help.listing}i(w,"tutorialListing");function $(e){return e.length===0?"That is the whole tutorial.":`What's next: ${e.map(({command:n,description:t})=>`${n} \u2014 ${t}`).join("; ")}`}i($,"nextTracksLine");async function T(e,n){const t=f[e],r=t.length,s=n.step,o=s==null?1:Number(s);if(!Number.isInteger(o)||o<1||o>r)throw new y({error:"step_out_of_range",message:`--step must be between 1 and ${r} (got ${String(s)})`,field:"step",next:`Run \`crtr sys tutorial ${e} -h\` and read the contents, then pick a lesson in range.`});const c=t[o-1],l=o<r?`crtr sys tutorial ${e} --step ${o+1}`:void 0,d=l===void 0?await w():void 0;let a;return d!==void 0&&(a=d.filter(u=>u.plugin!==void 0).map(u=>({command:`crtr sys tutorial ${u.name}`,description:u.description}))),{track:e,step:o,total:r,title:c.title,body:a===void 0?c.body:`${c.body}
3
3
 
4
- ${x(c)}`,...u===void 0?{}:{next:u},teaching_contract:$,...o===1?{contents:T(n)}:{},...c===void 0?{}:{next_tracks:c}}}a(L,"runTrack");function h(e,t,n){return g({name:e,description:t,whenToUse:n,help:{name:`sys tutorial ${e}`,summary:`serve one lesson from the ${e} crouter tutorial track`,params:[{kind:"flag",name:"step",type:"int",required:!1,default:1,constraint:`Lesson number, from 1 through ${m[e].length}. Default: 1.`}],output:[{name:"track",type:"string",required:!0,constraint:`The served track: ${e}.`},{name:"step",type:"number",required:!0,constraint:"The served lesson number."},{name:"total",type:"number",required:!0,constraint:"Number of lessons in this track."},{name:"title",type:"string",required:!0,constraint:"Lesson title."},{name:"body",type:"string",required:!0,constraint:"Lesson instructions for the teaching agent."},{name:"next",type:"string",required:!1,constraint:"The exact command for the next lesson. Absent on the last lesson."},{name:"teaching_contract",type:"string",required:!0,constraint:"Rules for teaching the lesson conversationally and safely."},{name:"contents",type:"string",required:!1,constraint:"Numbered titles for every lesson in this track. Present only for step 1."},{name:"next_tracks",type:"array",required:!1,constraint:"Available core and plugin tutorial tracks, each with command and description. Present only on the last lesson."}],outputKind:"object",effects:["None. Read-only. Progress is not stored."]},run:a(async r=>L(e,r),"run")})}a(h,"trackLeaf");const E=h("basic","serve a basic crouter lesson","you are learning the terminal, viewer, canvas, memory, graph, and inbox foundations before running real crouter work."),S=h("advanced","serve an advanced crouter lesson","you already know the basic operation and want the runtime model for delegation, lifecycle, and longer-lived work.");export{S as advancedLeaf,E as basicLeaf};
4
+ ${$(a)}`,...l===void 0?{}:{next:l},teaching_contract:g,...o===1?{contents:b(t)}:{},...a===void 0?{}:{next_tracks:a}}}i(T,"runTrack");function k(e,n,t){return m({name:e,description:n,whenToUse:t,help:{name:`sys tutorial ${e}`,summary:`serve one lesson from the ${e} crouter tutorial track`,params:[{kind:"flag",name:"step",type:"int",required:!1,default:1,constraint:`Lesson number, from 1 through ${f[e].length}. Default: 1.`}],output:[{name:"track",type:"string",required:!0,constraint:`The served track: ${e}.`},{name:"step",type:"number",required:!0,constraint:"The served lesson number."},{name:"total",type:"number",required:!0,constraint:"Number of lessons in this track."},{name:"title",type:"string",required:!0,constraint:"Lesson title."},{name:"body",type:"string",required:!0,constraint:"Lesson instructions for the teaching agent."},{name:"next",type:"string",required:!1,constraint:"The exact command for the next lesson. Absent on the last lesson."},{name:"teaching_contract",type:"string",required:!0,constraint:"Rules for teaching the lesson conversationally and safely."},{name:"contents",type:"string",required:!1,constraint:"Numbered titles for every lesson in this track. Present only for step 1."},{name:"next_tracks",type:"array",required:!1,constraint:"Available core and plugin tutorial tracks, each with command and description. Present only on the last lesson."}],outputKind:"object",effects:["None. Read-only. Progress is not stored."]},run:i(async r=>T(e,r),"run")})}i(k,"trackLeaf");const N=k("basic","serve one task-first crouter lesson","you want to learn everyday crouter actions, from returning to a conversation through following delegated work.");export{N as basicLeaf};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@north-light/crouter",
3
- "version": "0.3.338",
3
+ "version": "0.3.340",
4
4
  "description": "crtr — agent runtime with memory, plugins, and marketplaces",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
package/runtime.lock.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@north-light/crouter",
3
- "version": "0.3.338",
3
+ "version": "0.3.340",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "@north-light/crouter",
9
- "version": "0.3.338",
9
+ "version": "0.3.340",
10
10
  "hasInstallScript": true,
11
11
  "license": "GPL-3.0-only",
12
12
  "workspaces": [
@@ -10859,28 +10859,28 @@
10859
10859
  },
10860
10860
  "packages/crouter-api": {
10861
10861
  "name": "@north-light/crouter-api",
10862
- "version": "0.3.338",
10862
+ "version": "0.3.340",
10863
10863
  "license": "GPL-3.0-only"
10864
10864
  },
10865
10865
  "packages/crouter-env-docker": {
10866
10866
  "name": "@north-light/crouter-env-docker",
10867
- "version": "0.3.338",
10867
+ "version": "0.3.340",
10868
10868
  "license": "GPL-3.0-only"
10869
10869
  },
10870
10870
  "packages/crouter-plugin": {
10871
10871
  "name": "@north-light/crouter-plugin",
10872
- "version": "0.3.338",
10872
+ "version": "0.3.340",
10873
10873
  "license": "GPL-3.0-only",
10874
10874
  "dependencies": {
10875
- "@north-light/crouter-api": "^0.3.338"
10875
+ "@north-light/crouter-api": "^0.3.340"
10876
10876
  }
10877
10877
  },
10878
10878
  "packages/crouter-sdk": {
10879
10879
  "name": "@north-light/crouter-sdk",
10880
- "version": "0.3.338",
10880
+ "version": "0.3.340",
10881
10881
  "license": "GPL-3.0-only",
10882
10882
  "dependencies": {
10883
- "@north-light/crouter-api": "^0.3.338"
10883
+ "@north-light/crouter-api": "^0.3.340"
10884
10884
  }
10885
10885
  }
10886
10886
  }