@north-light/crouter 0.3.332 → 0.3.334
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/dist/api/command-manifest/manifest.js +1 -1
- package/dist/build-root.js +1 -1
- package/dist/builtin-memory/crouter-concepts/INDEX.md +13 -0
- package/dist/builtin-memory/crouter-concepts/README.md +19 -0
- package/dist/builtin-memory/crouter-concepts/lifecycle-and-wakes.md +28 -0
- package/dist/builtin-memory/crouter-concepts/memory.md +36 -0
- package/dist/builtin-memory/crouter-concepts/nodes-and-the-canvas.md +30 -0
- package/dist/builtin-memory/crouter-concepts/profiles-kinds-and-modes.md +33 -0
- package/dist/builtin-memory/crouter-concepts/scopes-and-trust.md +34 -0
- package/dist/builtin-memory/crouter-concepts/why-a-daemon.md +31 -0
- package/dist/builtin-memory/crouter-sdk/guides/README.md +19 -0
- package/dist/builtin-memory/crouter-sdk/guides/app-memory.md +73 -0
- package/dist/builtin-memory/crouter-sdk/guides/event-driven-assistant.md +91 -0
- package/dist/builtin-memory/crouter-sdk/guides/fan-out-pipeline.md +57 -0
- package/dist/builtin-memory/crouter-sdk/guides/human-approval.md +79 -0
- package/dist/builtin-memory/crouter-sdk/guides/typed-extraction.md +53 -0
- package/dist/clients/attach/input/capabilities.js +1 -1
- package/dist/clients/attach/overlays/help.js +1 -1
- package/dist/clients/attach/slash/dispatch.js +1 -1
- package/dist/clients/attach/viewer.js +413 -413
- package/dist/commands/sys/branch.js +1 -1
- package/dist/commands/sys/setup-core.d.ts +1 -1
- package/dist/commands/sys/setup-core.js +4 -4
- package/dist/commands/sys/tutorial/branch.d.ts +1 -0
- package/dist/commands/sys/tutorial/branch.js +1 -0
- package/dist/commands/sys/tutorial/lessons.d.ts +6 -0
- package/dist/commands/sys/tutorial/lessons.js +12 -0
- package/dist/commands/sys/tutorial/scenario.d.ts +1 -0
- package/dist/commands/sys/tutorial/scenario.js +2 -0
- package/dist/commands/sys/tutorial/tracks.d.ts +2 -0
- package/dist/commands/sys/tutorial/tracks.js +4 -0
- package/dist/commands/sys.js +1 -1
- package/dist/core/runtime/boot-root.d.ts +7 -0
- package/dist/core/runtime/boot-root.js +4 -4
- package/dist/core/runtime/first-run-offer.d.ts +11 -0
- package/dist/core/runtime/first-run-offer.js +5 -0
- package/dist/core/runtime/front-door.js +3 -3
- package/dist/daemon/api/handlers/node-events.js +1 -1
- package/dist/types.js +1 -1
- package/docs/cli/README.md +16 -0
- package/docs/cli/canvas-and-dashboard.md +56 -0
- package/docs/cli/first-session.md +55 -0
- package/docs/cli/human-inbox.md +34 -0
- package/docs/cli/memory-and-preferences.md +50 -0
- package/docs/cli/the-viewer.md +79 -0
- package/docs/concepts/README.md +17 -0
- package/docs/concepts/lifecycle-and-wakes.md +26 -0
- package/docs/concepts/memory.md +34 -0
- package/docs/concepts/nodes-and-the-canvas.md +28 -0
- package/docs/concepts/profiles-kinds-and-modes.md +31 -0
- package/docs/concepts/scopes-and-trust.md +32 -0
- package/docs/concepts/why-a-daemon.md +29 -0
- package/docs/sdk/guides/README.md +18 -0
- package/docs/sdk/guides/app-memory.md +22 -0
- package/docs/sdk/guides/event-driven-assistant.md +24 -0
- package/docs/sdk/guides/fan-out-pipeline.md +24 -0
- package/docs/sdk/guides/human-approval.md +24 -0
- package/docs/sdk/guides/typed-extraction.md +22 -0
- package/package.json +8 -8
- package/runtime.lock.json +196 -196
|
@@ -1 +1 @@
|
|
|
1
|
-
var s=Object.defineProperty;var e=(n,t)=>s(n,"name",{value:t,configurable:!0});import{defineBranch as o}from"../../core/command.js";const r={concept:"crtr configuration, diagnostics, and self-management",desc:"settings, config, connect, setup, doctor, context, sync, migrate, update, version, feedback, logs, support, daemon",useWhen:"managing the crtr installation, inspecting diagnostics, or operating the crtrd supervisor process"},a={name:"sys",summary:"crtr system configuration, settings, diagnostics, and self-management",model:"Configuration and settings own durable behavior; diagnostics and support observe it. Setup, migration, sync, and update change installed or generated state, while daemon control owns the supervisor lifecycle."};function d(n){return o({name:"sys",rootEntry:r,help:{...a},children:n})}e(d,"defineSysBranch");export{d as defineSysBranch};
|
|
1
|
+
var s=Object.defineProperty;var e=(n,t)=>s(n,"name",{value:t,configurable:!0});import{defineBranch as o}from"../../core/command.js";const r={concept:"crtr configuration, diagnostics, and self-management",desc:"settings, config, connect, setup, tutorial, doctor, context, sync, migrate, update, version, feedback, logs, support, daemon",useWhen:"managing the crtr installation, inspecting diagnostics, or operating the crtrd supervisor process"},a={name:"sys",summary:"crtr system configuration, settings, diagnostics, and self-management",model:"Configuration and settings own durable behavior; diagnostics and support observe it. Setup, migration, sync, and update change installed or generated state, while daemon control owns the supervisor lifecycle."};function d(n){return o({name:"sys",rootEntry:r,help:{...a},children:n})}e(d,"defineSysBranch");export{d as defineSysBranch};
|
|
@@ -2,7 +2,7 @@ import { type BindingId, type BindingResolution, type KeybindingDiagnostic, type
|
|
|
2
2
|
export declare const OFFICIAL_MARKETPLACE_NAME = "crouter-official-marketplace";
|
|
3
3
|
export declare const OFFICIAL_MARKETPLACE_URL = "https://github.com/crouton-labs/crouter-official-marketplace.git";
|
|
4
4
|
export declare const OFFICIAL_MARKETPLACE_REF = "main";
|
|
5
|
-
export type SetupPackageId = 'official-marketplace' | 'crtr-extensions' | 'oauth-adapter' | 'capture-plugin' | 'search-plugin';
|
|
5
|
+
export type SetupPackageId = 'official-marketplace' | 'crtr-extensions' | 'oauth-adapter' | 'capture-plugin' | 'search-plugin' | 'dev-plugin';
|
|
6
6
|
/** `nerd-font` is not a command on PATH like the other two — it is an installed
|
|
7
7
|
* font face, detected by fontconfig/font-directory scan. The viewer renders
|
|
8
8
|
* every tool and command icon with Nerd Font codepoints, so without it a fresh
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
var W=Object.defineProperty;var a=(e,t)=>W(e,"name",{value:t,configurable:!0});import{spawn as q,spawnSync as H}from"node:child_process";import{homedir as g}from"node:os";import{basename as V,dirname as J,join as S,resolve as X}from"node:path";import{isDeepStrictEqual as k}from"node:util";import{BINDING_CATALOG as Q,normalizeGesture as Z,splitGestureExpression as ee,inboxShortcut as te,resolveKeybindings as P}from"../../core/keybindings/index.js";import{builtinPiPackageDir as b}from"../../core/scope.js";import{readJsonIfExists as ne,readJsonOrNull as re}from"../../core/fs-utils.js";import{resolveBundledPiCliPath as ie}from"../../core/runtime/pi-cli.js";import{crtrVersion as se}from"../../shared/crtr-version.js";import{installTmuxBindings as ae}from"../../core/runtime/tmux-chrome.js";import{normalizeTmuxPassthrough as oe}from"../../core/config.js";import{detectNerdFont as ce,nerdFontInstallCommand as ue,createCurlPercentReader as le,NERD_FONT_MANUAL_LINUX_COMMAND as de,NERD_FONT_SELECT_HINT as pe}from"../../core/runtime/nerd-font.js";import{startProgressBar as he}from"../../core/runtime/progress-bar.js";import{ensureRenderer as fe,isRendererReady as A}from"../../core/termrender/termrender.js";import{installFromMarketplace as me}from"../pkg/plugin-manage.js";import{addMarketplace as $}from"../pkg/market-manage.js";import{findPluginByName as ge,findMarketplaceByName as E}from"../../core/resolver.js";import{expandTilde as ke}from"../../core/fs-utils.js";const R="crouter-official-marketplace",D="https://github.com/crouton-labs/crouter-official-marketplace.git",O="main";function T(e){return e!==null&&typeof e=="object"&&!Array.isArray(e)}a(T,"isKeybindingsObject");function h(e){return T(e)?Object.fromEntries(Object.entries(e).map(([t,n])=>[t,Array.isArray(n)?[...n]:n])):{}}a(h,"cloneKeybindings");class Je{static{a(this,"SetupKeybindingsDraft")}expected;baseline;current;baselineInvalidRoot;invalidRoot;resolutionCache;expectedPassthrough;baselinePassthrough;currentPassthrough;constructor(t,n=void 0){this.expected=t,this.baseline=h(t),this.current=h(t),this.baselineInvalidRoot=t!==void 0&&!T(t)?t:void 0,this.invalidRoot=this.baselineInvalidRoot,this.expectedPassthrough=n,this.baselinePassthrough=oe(n),this.currentPassthrough=[...this.baselinePassthrough]}get dirty(){return!k(this.current,this.baseline)||!k(this.invalidRoot,this.baselineInvalidRoot)||!k(this.currentPassthrough,this.baselinePassthrough)}overrides(){return h(this.current)}expectedKeybindings(){return this.expected}tmuxPassthrough(){return[...this.currentPassthrough]}expectedTmuxPassthrough(){return this.expectedPassthrough}usesTmuxPassthrough(t){return this.currentPassthrough.includes(t)}toggleTmuxPassthrough(t){this.currentPassthrough=this.currentPassthrough.includes(t)?this.currentPassthrough.filter(n=>n!==t):[...this.currentPassthrough,t]}resolution(){return this.resolutionCache===void 0&&(this.resolutionCache=P(this.invalidRoot===void 0?this.current:this.invalidRoot)),this.resolutionCache}hasInvalidRoot(){return this.invalidRoot!==void 0}expression(t){return this.resolution().gestures(t).join(", ")}edit(t,n){if(this.invalidRoot!==void 0)return{ok:!1,diagnostics:[{severity:"error",code:"invalid-keybindings",offendingValue:this.invalidRoot,message:"Reset all before editing: the saved keybindings root is invalid."}]};const r=Q.find(p=>p.id===t);let o;try{o=n.trim()===""?[]:ee(n).map(p=>Z(p.trim(),r))}catch(p){return{ok:!1,diagnostics:[{severity:"error",code:"invalid-override",bindingId:t,offendingValue:n,message:p instanceof Error?p.message:String(p)}]}}const i=[...new Set(o)],u={...this.current};delete u[t];const c=[...P(u).gestures(t)],l=k(i,c)?u:{...this.current,[t]:i},s=P(l),d=s.diagnostics.filter(p=>p.bindingId===t||p.relatedBindingIds?.includes(t));return d.length>0?{ok:!1,diagnostics:d}:(this.current=l,this.resolutionCache=void 0,{ok:!0,diagnostics:s.diagnostics})}unbind(t){return this.edit(t,"")}resetRow(t){delete this.current[t],this.resolutionCache=void 0}resetAll(){this.current={},this.currentPassthrough=[],this.invalidRoot=void 0,this.resolutionCache=void 0}discard(){this.current=h(this.baseline),this.currentPassthrough=[...this.baselinePassthrough],this.invalidRoot=this.baselineInvalidRoot,this.resolutionCache=void 0}markSaved(t,n=[]){this.expected=Object.keys(t).length===0?void 0:h(t),this.baseline=h(t),this.current=h(t),this.expectedPassthrough=n.length===0?void 0:[...n],this.baselinePassthrough=[...n],this.currentPassthrough=[...n],this.baselineInvalidRoot=void 0,this.invalidRoot=void 0,this.resolutionCache=void 0}}const be={"official-marketplace":{name:R,shortDescription:"the official crtr plugin catalog \u2014 registered as a source so its plugins are installable by ref.",longDescription:"Shallow-clones github.com/crouton-labs/crouter-official-marketplace into the user scope and registers it, which is what makes `crtr pkg plugin install crouter-official-marketplace/<name>` resolve and what fills `crtr pkg market browse`. Registering the source installs no plugin by itself. Uncheck it on a machine that should reach no external catalog \u2014 a plugin selected below still registers the marketplace it comes from, because its ref cannot resolve otherwise."},"crtr-extensions":{name:"pi-crtr-extensions",shortDescription:"crtr companion extensions \u2014 command palette, help gating, subscription/OAuth rotation, nested context.",longDescription:"Bundles the extensions built for crtr: a crtr slash-command palette, crtr/crouter help gating, surfacing of Claude Code marketplace plugin commands, nested AGENTS.md/CLAUDE.md loading, OAuth login + rotation across multiple Claude subscriptions (provider-rotation), and system-prompt trimming. The OAuth/rotation piece is what lets crtr authenticate your Claude account."},"oauth-adapter":{name:"pi-claude-oauth-adapter",shortDescription:"Anthropic OAuth / Claude Code credential compatibility for pi.",longDescription:"Third-party adapter (minzique/dotfiles-agents) letting pi authenticate with Claude using Claude Code OAuth credentials. Installed from npm, pinned to 0.1.2."},"capture-plugin":{name:"capture",shortDescription:"crtr plugin mounting `crtr capture` \u2014 browser automation and UI measurement over CDP.",longDescription:"Installs the `capture` plugin from the official crtr marketplace. It mounts `crtr capture` as a transparent passthrough to the external Capture CLI (`npm install -g @crouton-kit/capture`) for CDP screenshots, rendered-geometry measurement, HAR, accessibility trees, and JS execution against a live page. Uncheck it on a machine where agents never drive a browser \u2014 the command then costs nothing in their root prompt."},"search-plugin":{name:"search",shortDescription:"crtr plugin mounting `crtr search` \u2014 public-web discovery, grounded answers, and content extraction.",longDescription:"Installs the `search` plugin from the official crtr marketplace. It mounts `crtr search` and calls Exa directly, resolving its own API key from EXA_API_KEY or ~/.crouter/exa.key \u2014 crouter never handles that credential. Uncheck it on a machine whose agents get web search from somewhere else, such as a Northlight home where the platform serves `crtr search` from its own plugin."}},v={"official-marketplace":{kind:"marketplace-source",source:D},"crtr-extensions":{kind:"local-path",source:b("pi-crtr-extensions")},"oauth-adapter":{kind:"npm",source:"npm:pi-claude-oauth-adapter@0.1.2"},"capture-plugin":{kind:"marketplace-plugin",source:"crouter-official-marketplace/capture"},"search-plugin":{kind:"marketplace-plugin",source:"crouter-official-marketplace/search"}},we={"official-marketplace":[],"crtr-extensions":["pi-crtr-extensions"],"oauth-adapter":[],"capture-plugin":[],"search-plugin":[]};function I(e=b){return["official-marketplace","crtr-extensions","oauth-adapter","capture-plugin","search-plugin"].map(t=>{const n=be[t],r=v[t].kind==="local-path"?e(n.name):v[t].source;return{id:t,name:n.name,sourceKind:v[t].kind,installSource:r,shortDescription:n.shortDescription,longDescription:n.longDescription}})}a(I,"buildSetupManifest");function Xe(e){switch(e.sourceKind){case"marketplace-source":return`clone + register ${e.installSource}`;case"marketplace-plugin":return`crtr pkg plugin install ${e.installSource}`;default:return`pi install ${e.installSource}`}}a(Xe,"packagePlanAction");function Pe(e){if(!e.startsWith("npm:"))return null;const t=e.slice(4),n=t.startsWith("@")?t.indexOf("@",1):t.lastIndexOf("@");return n>0?t.slice(0,n):t}a(Pe,"npmPackageName");function N(e,t,n){const r=Pe(e);if(r!==null)return`npm:${r}`;const o=ke(e,n);return X(t,o)}a(N,"normalizePackageIdentity");function x(e){return`package:${e}`}a(x,"packageNameIdentity");function ve(e){return e.startsWith("npm:")?[]:[x(V(e))]}a(ve,"localPackageNameIdentities");function Ie(e,t,n){const r=[N(e.installSource,t,n)];if(e.sourceKind==="local-path"){r.push(x(e.name));for(const o of we[e.id])r.push(x(o))}return r}a(Ie,"manifestIdentities");function L(e,t=I(),n=S(g(),".pi","agent","settings.json"),r=g()){const o=J(n),i=new Set,u=new Map;for(const l of t)for(const s of Ie(l,o,r))u.set(s,l.id);const c=e&&typeof e=="object"?e.packages:void 0;if(!Array.isArray(c))return i;for(const l of c){const s=typeof l=="string"?l:l&&typeof l=="object"&&typeof l.source=="string"?l.source:null;if(s===null)continue;const d=[N(s,o,r),...ve(s)];for(const p of d){const f=u.get(p);f!==void 0&&i.add(f)}}return i}a(L,"detectInstalledSetupPackageIds");function m(e){return H("sh",["-lc",`command -v ${e} >/dev/null 2>&1`],{stdio:"ignore"}).status===0}a(m,"detectCommandOnPath");function xe(e=process.platform,t=m){switch(e){case"darwin":return t("brew")?"brew":"manual";case"linux":return t("apt-get")?"apt-get":t("dnf")?"dnf":t("pacman")?"pacman":"manual";default:return"manual"}}a(xe,"detectPackageManager");function ye(e,t,n=m){if(e==="nerd-font"?ce().installed:n(e))return{tool:e,installed:!0,command:null,hint:"already installed",autoInstall:!1};if(e==="nerd-font"){const i=ue(t);return{tool:e,installed:!1,command:i.command,hint:`${i.hint} \u2014 ${pe}`,autoInstall:i.autoInstall}}const o=e==="nvim"?"neovim":"tmux";switch(t){case"brew":return{tool:e,installed:!1,command:`brew install ${o}`,hint:"Homebrew install",autoInstall:!0};case"apt-get":return{tool:e,installed:!1,command:`sudo apt-get update && sudo apt-get install -y ${o}`,hint:"apt-get install",autoInstall:!0};case"dnf":return{tool:e,installed:!1,command:`sudo dnf install -y ${o}`,hint:"dnf install",autoInstall:!0};case"pacman":return{tool:e,installed:!1,command:`sudo pacman -S --noconfirm ${o}`,hint:"pacman install",autoInstall:!0};default:return{tool:e,installed:!1,command:null,hint:`manual install required for ${e}`,autoInstall:!1}}}a(ye,"buildSystemInstallPlan");function _(e=g()){return S(e,".pi","agent","settings.json")}a(_,"resolveSettingsPath");function Qe(e={}){const t=e.homeDir??g(),n=e.settingsPath??_(t),r=e.manifest??I(b),o=ne(n),i=L(o,r,n,t);for(const s of G(r))i.add(s);const u=e.commandExists??m,c=e.packageManager??xe(process.platform,u),l=["tmux","nvim","nerd-font"].map(s=>ye(s,c,u));return{manifest:r,installedPackageIds:i,systemPlans:l}}a(Qe,"buildSetupInitialState");function Ze(e){const t=e.manifest.map(r=>{const o=e.installedPackageIds.has(r.id)?"installed":"not installed";return`- ${r.name} \u2014 ${r.shortDescription} (${o})`}),n=e.systemPlans.map(r=>r.installed?`- ${r.tool}: \u2713 installed`:r.command?`- ${r.tool}: ${r.command}`:`- ${r.tool}: ${r.hint}`);return["crtr sys setup (non-interactive)","","Provider login: open the wizard in a TTY and use the Providers tab (same flow as /login) to authenticate a model provider.","","Packages:",...t,"","System deps:",...n,"",`Inbox: the inbox-toggle key (${te()??"disabled"}) is a crouter keybinding \u2014 view or rebind it in \`crtr sys settings\`; it is installed on the same tmux server as every other crouter binding.`,"Run `crtr sys setup` in a TTY to open the wizard."].join(`
|
|
1
|
+
var q=Object.defineProperty;var a=(e,t)=>q(e,"name",{value:t,configurable:!0});import{spawn as W,spawnSync as H}from"node:child_process";import{homedir as g}from"node:os";import{basename as V,dirname as J,join as S,resolve as X}from"node:path";import{isDeepStrictEqual as k}from"node:util";import{BINDING_CATALOG as Q,normalizeGesture as Z,splitGestureExpression as ee,inboxShortcut as te,resolveKeybindings as w}from"../../core/keybindings/index.js";import{builtinPiPackageDir as b}from"../../core/scope.js";import{readJsonIfExists as ne,readJsonOrNull as re}from"../../core/fs-utils.js";import{resolveBundledPiCliPath as ie}from"../../core/runtime/pi-cli.js";import{crtrVersion as se}from"../../shared/crtr-version.js";import{installTmuxBindings as ae}from"../../core/runtime/tmux-chrome.js";import{normalizeTmuxPassthrough as oe}from"../../core/config.js";import{detectNerdFont as ce,nerdFontInstallCommand as ue,createCurlPercentReader as le,NERD_FONT_MANUAL_LINUX_COMMAND as de,NERD_FONT_SELECT_HINT as pe}from"../../core/runtime/nerd-font.js";import{startProgressBar as he}from"../../core/runtime/progress-bar.js";import{ensureRenderer as fe,isRendererReady as A}from"../../core/termrender/termrender.js";import{installFromMarketplace as me}from"../pkg/plugin-manage.js";import{addMarketplace as $}from"../pkg/market-manage.js";import{findPluginByName as ge,findMarketplaceByName as E}from"../../core/resolver.js";import{expandTilde as ke}from"../../core/fs-utils.js";const R="crouter-official-marketplace",D="https://github.com/crouton-labs/crouter-official-marketplace.git",O="main";function T(e){return e!==null&&typeof e=="object"&&!Array.isArray(e)}a(T,"isKeybindingsObject");function h(e){return T(e)?Object.fromEntries(Object.entries(e).map(([t,n])=>[t,Array.isArray(n)?[...n]:n])):{}}a(h,"cloneKeybindings");class Je{static{a(this,"SetupKeybindingsDraft")}expected;baseline;current;baselineInvalidRoot;invalidRoot;resolutionCache;expectedPassthrough;baselinePassthrough;currentPassthrough;constructor(t,n=void 0){this.expected=t,this.baseline=h(t),this.current=h(t),this.baselineInvalidRoot=t!==void 0&&!T(t)?t:void 0,this.invalidRoot=this.baselineInvalidRoot,this.expectedPassthrough=n,this.baselinePassthrough=oe(n),this.currentPassthrough=[...this.baselinePassthrough]}get dirty(){return!k(this.current,this.baseline)||!k(this.invalidRoot,this.baselineInvalidRoot)||!k(this.currentPassthrough,this.baselinePassthrough)}overrides(){return h(this.current)}expectedKeybindings(){return this.expected}tmuxPassthrough(){return[...this.currentPassthrough]}expectedTmuxPassthrough(){return this.expectedPassthrough}usesTmuxPassthrough(t){return this.currentPassthrough.includes(t)}toggleTmuxPassthrough(t){this.currentPassthrough=this.currentPassthrough.includes(t)?this.currentPassthrough.filter(n=>n!==t):[...this.currentPassthrough,t]}resolution(){return this.resolutionCache===void 0&&(this.resolutionCache=w(this.invalidRoot===void 0?this.current:this.invalidRoot)),this.resolutionCache}hasInvalidRoot(){return this.invalidRoot!==void 0}expression(t){return this.resolution().gestures(t).join(", ")}edit(t,n){if(this.invalidRoot!==void 0)return{ok:!1,diagnostics:[{severity:"error",code:"invalid-keybindings",offendingValue:this.invalidRoot,message:"Reset all before editing: the saved keybindings root is invalid."}]};const r=Q.find(p=>p.id===t);let o;try{o=n.trim()===""?[]:ee(n).map(p=>Z(p.trim(),r))}catch(p){return{ok:!1,diagnostics:[{severity:"error",code:"invalid-override",bindingId:t,offendingValue:n,message:p instanceof Error?p.message:String(p)}]}}const i=[...new Set(o)],u={...this.current};delete u[t];const c=[...w(u).gestures(t)],l=k(i,c)?u:{...this.current,[t]:i},s=w(l),d=s.diagnostics.filter(p=>p.bindingId===t||p.relatedBindingIds?.includes(t));return d.length>0?{ok:!1,diagnostics:d}:(this.current=l,this.resolutionCache=void 0,{ok:!0,diagnostics:s.diagnostics})}unbind(t){return this.edit(t,"")}resetRow(t){delete this.current[t],this.resolutionCache=void 0}resetAll(){this.current={},this.currentPassthrough=[],this.invalidRoot=void 0,this.resolutionCache=void 0}discard(){this.current=h(this.baseline),this.currentPassthrough=[...this.baselinePassthrough],this.invalidRoot=this.baselineInvalidRoot,this.resolutionCache=void 0}markSaved(t,n=[]){this.expected=Object.keys(t).length===0?void 0:h(t),this.baseline=h(t),this.current=h(t),this.expectedPassthrough=n.length===0?void 0:[...n],this.baselinePassthrough=[...n],this.currentPassthrough=[...n],this.baselineInvalidRoot=void 0,this.invalidRoot=void 0,this.resolutionCache=void 0}}const be={"official-marketplace":{name:R,shortDescription:"the official crtr plugin catalog \u2014 registered as a source so its plugins are installable by ref.",longDescription:"Shallow-clones github.com/crouton-labs/crouter-official-marketplace into the user scope and registers it, which is what makes `crtr pkg plugin install crouter-official-marketplace/<name>` resolve and what fills `crtr pkg market browse`. Registering the source installs no plugin by itself. Uncheck it on a machine that should reach no external catalog \u2014 a plugin selected below still registers the marketplace it comes from, because its ref cannot resolve otherwise."},"crtr-extensions":{name:"pi-crtr-extensions",shortDescription:"crtr companion extensions \u2014 command palette, help gating, subscription/OAuth rotation, nested context.",longDescription:"Bundles the extensions built for crtr: a crtr slash-command palette, crtr/crouter help gating, surfacing of Claude Code marketplace plugin commands, nested AGENTS.md/CLAUDE.md loading, OAuth login + rotation across multiple Claude subscriptions (provider-rotation), and system-prompt trimming. The OAuth/rotation piece is what lets crtr authenticate your Claude account."},"oauth-adapter":{name:"pi-claude-oauth-adapter",shortDescription:"Anthropic OAuth / Claude Code credential compatibility for pi.",longDescription:"Third-party adapter (minzique/dotfiles-agents) letting pi authenticate with Claude using Claude Code OAuth credentials. Installed from npm, pinned to 0.1.2."},"capture-plugin":{name:"capture",shortDescription:"crtr plugin mounting `crtr capture` \u2014 browser automation and UI measurement over CDP.",longDescription:"Installs the `capture` plugin from the official crtr marketplace. It mounts `crtr capture` as a transparent passthrough to the external Capture CLI (`npm install -g @crouton-kit/capture`) for CDP screenshots, rendered-geometry measurement, HAR, accessibility trees, and JS execution against a live page. Uncheck it on a machine where agents never drive a browser \u2014 the command then costs nothing in their root prompt."},"search-plugin":{name:"search",shortDescription:"crtr plugin mounting `crtr search` \u2014 public-web discovery, grounded answers, and content extraction.",longDescription:"Installs the `search` plugin from the official crtr marketplace. It mounts `crtr search` and calls Exa directly, resolving its own API key from EXA_API_KEY or ~/.crouter/exa.key \u2014 crouter never handles that credential. Uncheck it on a machine whose agents get web search from somewhere else, such as a Northlight home where the platform serves `crtr search` from its own plugin."},"dev-plugin":{name:"dev",shortDescription:"crtr plugin mounting the development workflow \u2014 repository setup, worktrees, pull requests, and a dev track in the tutorial.",longDescription:"Installs the dev plugin from the official crtr marketplace. It adds the /dev: workflow commands (init, spec, design, plan, pr-loop), mounts crtr dev for a repository's own lifecycle commands and Grove instances, and contributes the crtr sys tutorial dev track. Uncheck it on a machine whose agents do not work in source repositories."}},y={"official-marketplace":{kind:"marketplace-source",source:D},"crtr-extensions":{kind:"local-path",source:b("pi-crtr-extensions")},"oauth-adapter":{kind:"npm",source:"npm:pi-claude-oauth-adapter@0.1.2"},"capture-plugin":{kind:"marketplace-plugin",source:"crouter-official-marketplace/capture"},"search-plugin":{kind:"marketplace-plugin",source:"crouter-official-marketplace/search"},"dev-plugin":{kind:"marketplace-plugin",source:"crouter-official-marketplace/dev"}},ve={"official-marketplace":[],"crtr-extensions":["pi-crtr-extensions"],"oauth-adapter":[],"capture-plugin":[],"search-plugin":[],"dev-plugin":[]};function P(e=b){return["official-marketplace","crtr-extensions","oauth-adapter","capture-plugin","search-plugin","dev-plugin"].map(t=>{const n=be[t],r=y[t].kind==="local-path"?e(n.name):y[t].source;return{id:t,name:n.name,sourceKind:y[t].kind,installSource:r,shortDescription:n.shortDescription,longDescription:n.longDescription}})}a(P,"buildSetupManifest");function Xe(e){switch(e.sourceKind){case"marketplace-source":return`clone + register ${e.installSource}`;case"marketplace-plugin":return`crtr pkg plugin install ${e.installSource}`;default:return`pi install ${e.installSource}`}}a(Xe,"packagePlanAction");function we(e){if(!e.startsWith("npm:"))return null;const t=e.slice(4),n=t.startsWith("@")?t.indexOf("@",1):t.lastIndexOf("@");return n>0?t.slice(0,n):t}a(we,"npmPackageName");function N(e,t,n){const r=we(e);if(r!==null)return`npm:${r}`;const o=ke(e,n);return X(t,o)}a(N,"normalizePackageIdentity");function I(e){return`package:${e}`}a(I,"packageNameIdentity");function ye(e){return e.startsWith("npm:")?[]:[I(V(e))]}a(ye,"localPackageNameIdentities");function Pe(e,t,n){const r=[N(e.installSource,t,n)];if(e.sourceKind==="local-path"){r.push(I(e.name));for(const o of ve[e.id])r.push(I(o))}return r}a(Pe,"manifestIdentities");function L(e,t=P(),n=S(g(),".pi","agent","settings.json"),r=g()){const o=J(n),i=new Set,u=new Map;for(const l of t)for(const s of Pe(l,o,r))u.set(s,l.id);const c=e&&typeof e=="object"?e.packages:void 0;if(!Array.isArray(c))return i;for(const l of c){const s=typeof l=="string"?l:l&&typeof l=="object"&&typeof l.source=="string"?l.source:null;if(s===null)continue;const d=[N(s,o,r),...ye(s)];for(const p of d){const f=u.get(p);f!==void 0&&i.add(f)}}return i}a(L,"detectInstalledSetupPackageIds");function m(e){return H("sh",["-lc",`command -v ${e} >/dev/null 2>&1`],{stdio:"ignore"}).status===0}a(m,"detectCommandOnPath");function Ie(e=process.platform,t=m){switch(e){case"darwin":return t("brew")?"brew":"manual";case"linux":return t("apt-get")?"apt-get":t("dnf")?"dnf":t("pacman")?"pacman":"manual";default:return"manual"}}a(Ie,"detectPackageManager");function xe(e,t,n=m){if(e==="nerd-font"?ce().installed:n(e))return{tool:e,installed:!0,command:null,hint:"already installed",autoInstall:!1};if(e==="nerd-font"){const i=ue(t);return{tool:e,installed:!1,command:i.command,hint:`${i.hint} \u2014 ${pe}`,autoInstall:i.autoInstall}}const o=e==="nvim"?"neovim":"tmux";switch(t){case"brew":return{tool:e,installed:!1,command:`brew install ${o}`,hint:"Homebrew install",autoInstall:!0};case"apt-get":return{tool:e,installed:!1,command:`sudo apt-get update && sudo apt-get install -y ${o}`,hint:"apt-get install",autoInstall:!0};case"dnf":return{tool:e,installed:!1,command:`sudo dnf install -y ${o}`,hint:"dnf install",autoInstall:!0};case"pacman":return{tool:e,installed:!1,command:`sudo pacman -S --noconfirm ${o}`,hint:"pacman install",autoInstall:!0};default:return{tool:e,installed:!1,command:null,hint:`manual install required for ${e}`,autoInstall:!1}}}a(xe,"buildSystemInstallPlan");function _(e=g()){return S(e,".pi","agent","settings.json")}a(_,"resolveSettingsPath");function Qe(e={}){const t=e.homeDir??g(),n=e.settingsPath??_(t),r=e.manifest??P(b),o=ne(n),i=L(o,r,n,t);for(const s of G(r))i.add(s);const u=e.commandExists??m,c=e.packageManager??Ie(process.platform,u),l=["tmux","nvim","nerd-font"].map(s=>xe(s,c,u));return{manifest:r,installedPackageIds:i,systemPlans:l}}a(Qe,"buildSetupInitialState");function Ze(e){const t=e.manifest.map(r=>{const o=e.installedPackageIds.has(r.id)?"installed":"not installed";return`- ${r.name} \u2014 ${r.shortDescription} (${o})`}),n=e.systemPlans.map(r=>r.installed?`- ${r.tool}: \u2713 installed`:r.command?`- ${r.tool}: ${r.command}`:`- ${r.tool}: ${r.hint}`);return["crtr sys setup (non-interactive)","","Provider login: open the wizard in a TTY and use the Providers tab (same flow as /login) to authenticate a model provider.","","Packages:",...t,"","System deps:",...n,"",`Inbox: the inbox-toggle key (${te()??"disabled"}) is a crouter keybinding \u2014 view or rebind it in \`crtr sys settings\`; it is installed on the same tmux server as every other crouter binding.`,"Run `crtr sys setup` in a TTY to open the wizard."].join(`
|
|
2
2
|
`)+`
|
|
3
|
-
`}a(Ze,"renderSetupStaticInstructions");class
|
|
3
|
+
`}a(Ze,"renderSetupStaticInstructions");class v extends Error{static{a(this,"SetupCommandError")}output;constructor(t,n){super(t),this.output=n}}function et(e){return`${e.stdout?.toString()??""}${e.stderr?.toString()??""}`}a(et,"commandOutput");function M(e,t,n){return new Promise((r,o)=>{const i=W(e,t,{stdio:["ignore","pipe","pipe"]});let u="";i.stdout.setEncoding("utf8"),i.stderr.setEncoding("utf8"),i.stdout.on("data",c=>{u+=c}),i.stderr.on("data",c=>{u+=c,n?.(c)}),i.on("error",c=>o(new v(`${e} failed: ${c.message}`,u))),i.on("close",c=>{c===0?r():o(new v(`${e} failed with status ${c??"unknown"}`,u))})})}a(M,"runCapturedCommand");function x(e,t){return M("sh",["-lc",e],t)}a(x,"runShellCommand");function Ce(e,t=ie()){return M(process.execPath,[t,"install",e])}a(Ce,"installPiPackage");async function K(){m("capture")||await x("npm install -g @crouton-kit/capture")}a(K,"installCaptureCliIfMissing");function F(e){return e instanceof v?e.output:void 0}a(F,"capturedFailureOutput");function Se(e){const n=F(e)?.split(/\r?\n/).find(r=>r.trim()!=="");return n!==void 0?n.trim():e instanceof Error?e.message:String(e)}a(Se,"firstFailureLine");function Ae(e){const t=F(e);t===void 0||t.length===0||process.stderr.write(t.endsWith(`
|
|
4
4
|
`)?t:`${t}
|
|
5
5
|
`)}a(Ae,"printCapturedFailure");class $e{static{a(this,"SetupProgress")}interactive;bar=null;constructor(t=!!process.stdout.isTTY){this.interactive=t}start(t){return this.interactive?(this.bar=he(`Installing ${t}\u2026`),this.bar):null}async clear(){await this.bar?.stop(),this.bar=null}async succeed(t){await this.clear(),this.interactive&&process.stdout.write(`\r\x1B[2K\u2713 ${t}
|
|
6
6
|
`)}async fail(t,n){await this.clear(),this.interactive&&process.stdout.write(`\r\x1B[2K\u2717 ${t} \u2014 ${Se(n)}
|
|
7
|
-
`),Ae(n)}async run(t,n){const r=this.start(t);try{const o=await n(r);return await this.succeed(t),o}catch(o){throw await this.fail(t,o),o}}}function j(e,t){return A()?"current":(e?.(),fe(t),A()?"provisioned":"unavailable")}a(j,"ensureManagedRenderer");async function Ee(){try{ae()}catch{}}a(Ee,"reconcileInboxFrontDoor");function B(e,t){return t.length===0?[`${e}: none`]:[`${e}:`,...t.map(n=>` - ${n}`)]}a(B,"summarySection");function tt(e){return["crtr sys setup complete",...B("installed",e.installed),...B("skipped",e.skipped)].join(`
|
|
7
|
+
`),Ae(n)}async run(t,n){const r=this.start(t);try{const o=await n(r);return await this.succeed(t),o}catch(o){throw await this.fail(t,o),o}}}function j(e,t){return A()?"current":(e?.(),fe(t),A()?"provisioned":"unavailable")}a(j,"ensureManagedRenderer");async function Ee(){try{ae()}catch{}}a(Ee,"reconcileInboxFrontDoor");function B(e,t){return t.length===0?[`${e}: none`]:[`${e}:`,...t.map(n=>` - ${n}`)]}a(B,"summarySection");function tt(e){return["crtr sys setup complete",...B("installed",e.installed),...B("skipped",e.skipped),"New to crouter? Ask for the tutorial in your first conversation, or run /tutorial any time."].join(`
|
|
8
8
|
`)+`
|
|
9
|
-
`}a(tt,"formatSetupExecutionSummary");function G(e,t){const n=[];for(const r of e)try{r.sourceKind==="marketplace-source"?E(r.name,t)!==null&&n.push(r.id):r.sourceKind==="marketplace-plugin"&&ge(r.name,t)!==null&&n.push(r.id)}catch{}return n}a(G,"detectInstalledMarketplaceEntries");function Re(e,t="user"){if(E(e,t)!==null)return null;if(e!==R)throw new Error(`marketplace not registered: ${e}`);return $({url:D,ref:O,scope:t}),e}a(Re,"ensureMarketplaceRegistered");async function U(e){if(e.sourceKind==="marketplace-source")return $({url:e.installSource,ref:O,scope:"user"}),[e.name];if(e.sourceKind==="marketplace-plugin"){const[t,n]=e.installSource.split("/"),r=Re(t);return await me({marketplace:t,plugin:n},"user"),e.id==="capture-plugin"&&await K(),r===null?[e.name]:[r,e.name]}return await Ce(e.installSource),[e.name]}a(U,"installSetupPackage");async function nt(e){const t=
|
|
9
|
+
`}a(tt,"formatSetupExecutionSummary");function G(e,t){const n=[];for(const r of e)try{r.sourceKind==="marketplace-source"?E(r.name,t)!==null&&n.push(r.id):r.sourceKind==="marketplace-plugin"&&ge(r.name,t)!==null&&n.push(r.id)}catch{}return n}a(G,"detectInstalledMarketplaceEntries");function Re(e,t="user"){if(E(e,t)!==null)return null;if(e!==R)throw new Error(`marketplace not registered: ${e}`);return $({url:D,ref:O,scope:t}),e}a(Re,"ensureMarketplaceRegistered");async function U(e){if(e.sourceKind==="marketplace-source")return $({url:e.installSource,ref:O,scope:"user"}),[e.name];if(e.sourceKind==="marketplace-plugin"){const[t,n]=e.installSource.split("/"),r=Re(t);return await me({marketplace:t,plugin:n},"user"),e.id==="capture-plugin"&&await K(),r===null?[e.name]:[r,e.name]}return await Ce(e.installSource),[e.name]}a(U,"installSetupPackage");async function nt(e){const t=P(b),n=L(re(_()),t);for(const i of G(t,"user"))n.add(i);const r=new Map(t.map(i=>[i.id,i])),o=process.stdin.isTTY&&process.stdout.isTTY?["crtr-extensions","official-marketplace","oauth-adapter","capture-plugin","search-plugin"]:["crtr-extensions","official-marketplace","capture-plugin","search-plugin"];for(const i of o){const u=r.get(i);if(!n.has(i))try{await U(u)}catch(c){e(u.name,c)}}try{j(void 0,()=>{})==="unavailable"&&e("termrender renderer",new Error("unavailable \u2014 install uv"))}catch(i){e("termrender renderer",i)}return se()}a(nt,"reconcileFirstRun");async function rt(e){const t=[],n=[],r=[],o=[],i=new $e;for(const s of e.packages){if(e.installedPackageIds.has(s.id)){if(n.push(`${s.name} (already installed)`),s.id==="capture-plugin"&&!m("capture"))try{await i.run("Capture CLI",()=>K()),t.push("Capture CLI")}catch(d){n.push(`Capture CLI (${d instanceof Error?d.message:String(d)})`)}continue}try{t.push(...await i.run(s.name,()=>U(s)))}catch(d){if(s.sourceKind!=="marketplace-source"&&s.sourceKind!=="marketplace-plugin")throw d;n.push(`${s.name} (${d instanceof Error?d.message:String(d)})`)}}for(const s of e.systemDeps){if(s.installed){o.push(`${s.tool} (already installed)`);continue}if(!s.autoInstall||!s.command){o.push(`${s.tool} (${s.hint})`);continue}const d=s.command,p=d===de;await i.run(s.tool,f=>{if(!p||f===null)return x(d);const Y=le();return x(d,z=>{const C=Y(z);C!==null&&f.setFraction(C/100)})}),r.push(s.tool)}const u=[];let c=!1,l;try{l=j(()=>{c=!0,i.start("termrender renderer")},s=>u.push(s))}catch(s){throw c&&await i.fail("termrender renderer",s),s}switch(l){case"provisioned":c&&await i.succeed("termrender renderer"),t.push("termrender renderer");break;case"current":n.push("termrender renderer (current)");break;case"unavailable":{const s=new v("unavailable \u2014 install uv",u.join(""));c&&await i.fail("termrender renderer",s),n.push("termrender renderer (unavailable \u2014 install uv)");break}}return await Ee(),{installed:[...t,...r],skipped:[...n,...o]}}a(rt,"executeSetupSubmission");export{R as OFFICIAL_MARKETPLACE_NAME,O as OFFICIAL_MARKETPLACE_REF,D as OFFICIAL_MARKETPLACE_URL,Je as SetupKeybindingsDraft,Qe as buildSetupInitialState,P as buildSetupManifest,xe as buildSystemInstallPlan,m as detectCommandOnPath,L as detectInstalledSetupPackageIds,Ie as detectPackageManager,rt as executeSetupSubmission,tt as formatSetupExecutionSummary,N as normalizePackageIdentity,Xe as packagePlanAction,nt as reconcileFirstRun,Ee as reconcileInboxFrontDoor,Ze as renderSetupStaticInstructions,_ as resolveSettingsPath};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const tutorialBranch: import("../../../core/command.js").BranchDef;
|
|
@@ -0,0 +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};
|
|
@@ -0,0 +1,12 @@
|
|
|
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};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const scenarioBranch: import("../../../core/command.js").BranchDef;
|
|
@@ -0,0 +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};
|
|
@@ -0,0 +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}
|
|
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};
|
package/dist/commands/sys.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
var
|
|
1
|
+
var t=Object.defineProperty;var r=(o,m)=>t(o,"name",{value:m,configurable:!0});import{defineSysBranch as s}from"./sys/branch.js";import{configBranch as f}from"./sys/config.js";import{daemonBranch as e}from"./sys/daemon.js";import{sysDoctorLeaf as i}from"./sys/doctor.js";import{sysFeedbackLeaf as a}from"./sys/feedback.js";import{sysLogsLeaf as p}from"./sys/logs.js";import{sysWorktreesLeaf as n}from"./sys/worktrees.js";import{sysSettingsLeaf as c}from"./sys/settings.js";import{sysConnectLeaf as y}from"./sys/connect.js";import{sysSetupLeaf as L}from"./sys/setup.js";import{tutorialBranch as h}from"./sys/tutorial/branch.js";import{supportBranch as B}from"./sys/support.js";import{contextBranch as g}from"./sys/context.js";import{sysSyncBranch as u}from"./sys/sync.js";import{sysMigrateLeaf as S}from"./sys/migrate.js";import{sysUpdateLeaf as d,sysVersionLeaf as k}from"./sys/update.js";function G(){return s([c,f,y,L,h,i,g,a,p,n,B,d,k,S,u,e])}r(G,"registerSys");export{G as registerSys};
|
|
@@ -1,9 +1,14 @@
|
|
|
1
|
+
import { cliClient } from '../../commands/api-client.js';
|
|
1
2
|
export interface BootRootOpts {
|
|
2
3
|
cwd: string;
|
|
3
4
|
kind?: string;
|
|
4
5
|
name?: string;
|
|
5
6
|
/** Optional starter prompt (bare `crtr` requires none). */
|
|
6
7
|
prompt?: string;
|
|
8
|
+
/** Whether the viewer plays the initial-whip animation for `prompt`. Defaults
|
|
9
|
+
* to "the prompt is non-empty"; the front door passes false for a prompt the
|
|
10
|
+
* runtime produced rather than the user typing it. */
|
|
11
|
+
initialWhip?: boolean;
|
|
7
12
|
/** Explicit `--profile <id-or-name>` on the front-door invocation, resolved
|
|
8
13
|
* through `resolveProfileOperand` — so a near-miss name still starts the
|
|
9
14
|
* session, under the closest profile. Omit to run the MRU/create-or-root-profile
|
|
@@ -18,6 +23,8 @@ export interface BootRootOpts {
|
|
|
18
23
|
* this a pinned default auto-picks silently (that is what "default" means). */
|
|
19
24
|
pickProfile?: boolean;
|
|
20
25
|
}
|
|
26
|
+
/** The root-create request the front door sends to crtrd. */
|
|
27
|
+
export declare function rootCreateRequest(opts: BootRootOpts, profileId: string): Parameters<ReturnType<typeof cliClient>['createNode']>[0];
|
|
21
28
|
/** Boot the front-door root: create the resident root through crtrd (broker
|
|
22
29
|
* launched + view socket ready server-side), register THIS terminal's pane as
|
|
23
30
|
* its foreground viewer, then exec `crtr surface attach` inline so this terminal
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
var
|
|
2
|
-
${
|
|
3
|
-
${
|
|
1
|
+
var p=Object.defineProperty;var d=(e,o)=>p(e,"name",{value:o,configurable:!0});import{spawn as u}from"node:child_process";import{inTmux as w,currentTmux as _,viewerSplitEnv as T}from"./placement-tmux.js";import{selectProfileForCwd as x}from"../profiles/select.js";import{cliClient as f}from"../../commands/api-client.js";import{mark as a}from"../timing.js";function h(e,o){const t=e.kind;return{...t!==void 0?{kind:t}:{},mode:"base",prompt:e.prompt,profile:o,cwd:e.cwd,...e.name!==void 0?{name:e.name}:t!==void 0?{name:t}:{},root:!0,prefer_warm:!0}}d(h,"rootCreateRequest");async function P(e){if(!w()){const n=process.stderr.isTTY?i=>`\x1B[1;33m${i}\x1B[0m`:i=>i;process.stderr.write(`
|
|
2
|
+
${n("crtr must be started from inside a tmux session.")}
|
|
3
|
+
${n("Start tmux first, e.g. tmux new -s work \u2014 then run crtr there.")}
|
|
4
4
|
|
|
5
|
-
`),process.exit(1)}
|
|
5
|
+
`),process.exit(1)}a("boot_root.start");const o=e.selectedProfileId??await x(e.cwd,e.profile,e.pickProfile??!1);a("boot_root.profile_resolved");const c=(await f().createNode(h(e,o))).node_id;a("boot_root.node_created",{node:c});const r=_();if(r!==null)try{await f().registerFocus({node_id:c,pane:r.pane,session:r.session,window:r.window})}catch{}const s={...process.env,...T(),CRTR_ATTACH_WAIT_MS:"30000",...e.initialWhip??(e.prompt!==void 0&&e.prompt!=="")?{CRTR_ATTACH_INITIAL_WHIP:"1"}:{}};r!==null&&(s.CRTR_ATTACH_BORROWED_PANE=r.pane),a("boot_root.viewer_spawn");const m=u("crtr",["surface","attach","to",c],{stdio:"inherit",env:s}),l=await new Promise(n=>{m.once("exit",i=>n(i??0)),m.once("error",()=>n(1))});process.exit(l)}d(P,"bootRoot");export{P as bootRoot,h as rootCreateRequest};
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export declare const FIRST_RUN_OFFER_SENTENCE = "Want a quick tour of how this works? Say yes, or just tell me what you're working on.";
|
|
2
|
+
/** The prompt the front door gives a freshly booted root, and whether the
|
|
3
|
+
* viewer should play the initial-whip animation (only for a prompt the user
|
|
4
|
+
* typed). */
|
|
5
|
+
export declare function frontDoorBootPrompt(facts: {
|
|
6
|
+
setupJustRan: boolean;
|
|
7
|
+
userPrompt: string | undefined;
|
|
8
|
+
}): {
|
|
9
|
+
prompt: string | undefined;
|
|
10
|
+
initialWhip: boolean;
|
|
11
|
+
};
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
var n=Object.defineProperty;var e=(t,r)=>n(t,"name",{value:r,configurable:!0});import{formatCard as o}from"../../shared/generated-context.js";const i="Want a quick tour of how this works? Say yes, or just tell me what you're working on.",s=o("first-run",{},`Open this conversation with exactly this sentence and nothing else:
|
|
2
|
+
|
|
3
|
+
${i}
|
|
4
|
+
|
|
5
|
+
If the user says yes, run \`crtr sys tutorial -h\`, pick the track it names, and teach it one lesson per invocation \u2014 in your own words, letting them try each thing before moving on. If the user says anything else, drop the tour entirely and do what they asked.`);function h(t){return t.userPrompt!==void 0&&t.userPrompt.trim()!==""?{prompt:t.userPrompt,initialWhip:!0}:t.setupJustRan?{prompt:s,initialWhip:!1}:{prompt:void 0,initialWhip:!1}}e(h,"frontDoorBootPrompt");export{i as FIRST_RUN_OFFER_SENTENCE,h as frontDoorBootPrompt};
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
var
|
|
1
|
+
var v=Object.defineProperty;var p=(r,i)=>v(r,"name",{value:i,configurable:!0});import{existsSync as w,statSync as I}from"node:fs";import{homedir as _}from"node:os";import{join as N,resolve as d}from"node:path";import{SUBTREE_NAMES as A}from"../../build-root.js";import{FRONT_DOOR_ENV as C}from"./front-door-env.js";import{frontDoorBootPrompt as L}from"./first-run-offer.js";function h(r){try{return w(r)&&I(r).isDirectory()}catch{return!1}}p(h,"isDir");const W=new Set(["--name","--kind","--profile","--pick-profile","--continue","-c","--resume","-r"]),g=N(_(),".crouter","setup-completed.json");async function b(){let r=!1;const i=p(()=>{r=!0},"onSigint");process.once("SIGINT",i);try{const{runSetup:t}=await import("../../commands/sys/setup-wizard.js"),e=await t();if(r){const l=new Error("Interrupted");throw l.name="AbortError",l}const[{atomicWriteJson:a},{crtrVersion:c}]=await Promise.all([import("../fs-utils.js"),import("../../shared/crtr-version.js")]);a(g,{packageVersion:c(),completedAt:new Date().toISOString(),outcome:e})}finally{process.removeListener("SIGINT",i)}}p(b,"runFirstSetup");function x(r){let i=process.cwd(),t,e,a,c=!1,l;const f=[];for(let n=0;n<r.length;n++){const o=r[n];o==="--name"?t=r[++n]:o==="--kind"?e=r[++n]:o==="--profile"?a=r[++n]:o==="--pick-profile"?c=!0:o==="--continue"||o==="-c"?l="continue":o==="--resume"||o==="-r"?l="pick":o.startsWith("--")||f.push(o)}f.length>0&&h(d(f[0]))&&(i=d(f.shift()));const m=f.length>0?f.join(" "):void 0;return{cwd:i,prompt:m,name:t,kind:e,profile:a,pickProfile:c,resume:l}}p(x,"parseRootArgs");import{FRONT_DOOR_ENV as z}from"./front-door-env.js";async function $(r){const i=r.slice(2),t=i[0];if(process.env[C]||t==="-h"||t==="--help"||t==="--version"||t==="-v"||t!==void 0&&A.includes(t))return!1;if(t!==void 0){const s=/\s/.test(t),u=!t.startsWith("-")&&h(d(t)),y=W.has(t);if(!s&&!u&&!y)return!1;if(u){const{buildRoot:E}=await import("../../build-root.js");if((await E()).subtrees.some(F=>F.name===t))return!1}}const e=x(i);if(e.profile!==void 0){const{resolveProfileOperand:s}=await import("../profiles/manifest.js");e.profile=s(e.profile).profileId}const a=process.stdin.isTTY&&process.stdout.isTTY;let c=!1;a&&!w(g)&&(await b(),c=!0);const{selectProfileForCwd:l,selectProfileForCwdReadOnly:f}=await import("../profiles/select.js");c&&e.profile===void 0&&process.stdout.write(`
|
|
2
2
|
Choose a profile
|
|
3
|
-
`);const
|
|
3
|
+
`);const m=e.profile??(a?await l(e.cwd,void 0,e.pickProfile):f(e.cwd)),{exitForMissingProviderCredential:n,hasConfiguredProviderCredential:o}=await import("./provider-credential-gate.js");if(!await o(m)){a||n();const{runFrontDoorLogin:s}=await import("./front-door-login.js");await s()||n()}const[{bootRoot:P},{resumeRoot:R},{rendererWarning:k,missingPackageWarning:S,piAgentDir:O}]=await Promise.all([import("./boot-root.js"),import("./resume-root.js"),import("./package-health.js")]);try{const s=S(O());s!==null&&process.stderr.write(`
|
|
4
4
|
${s}
|
|
5
5
|
|
|
6
6
|
`);const u=k();u!==null&&process.stderr.write(`
|
|
7
7
|
${u}
|
|
8
8
|
|
|
9
|
-
`)}catch{}
|
|
9
|
+
`)}catch{}if(e.resume!==void 0&&await R(e.cwd,e.resume==="pick")==="aborted")return!0;const{prompt:D,initialWhip:T}=L({setupJustRan:c,userPrompt:e.prompt});return await P({...e,prompt:D,initialWhip:T,selectedProfileId:m}),!0}p($,"maybeBootRoot");export{z as FRONT_DOOR_ENV,$ as maybeBootRoot};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
var O=Object.defineProperty;var o=(n,e)=>O(n,"name",{value:e,configurable:!0});import{randomUUID as q}from"node:crypto";import{ApiError as E}from"../../../api/errors.js";import{BrokerClient as C}from"../../../core/broker-client/client.js";import{getNode as b}from"../../../core/canvas/canvas.js";import{isPidAlive as B}from"../../../core/canvas/pid.js";import{BROKER_VIEW_SOCKET_WAIT_MS as k}from"../../../core/runtime/view-socket.js";import{toNodeOutcomeDTO as M}from"../map.js";import{reportsForNode as L}from"./reports.js";const D=15e3,$=6e4,x=250,P=512,T=256*1024,j=1e3,G=32*1024*1024;class V{static{o(this,"NodeEventRing")}events=[];bytes=0;nextSequence=1;earliestAvailable=1;append(e){const t={...e,sequence_number:this.nextSequence++},s=Buffer.byteLength(JSON.stringify(t),"utf8");if(s>T)return this.events=[],this.bytes=0,this.earliestAvailable=this.nextSequence,t;for(this.events.push(t),this.bytes+=s;this.events.length>P||this.bytes>T;){const r=this.events.shift();if(r===void 0)break;this.bytes-=Buffer.byteLength(JSON.stringify(r),"utf8"),this.earliestAvailable=r.sequence_number+1}return t}replay(e){if(e===void 0)return{gap:null,events:this.events};const t=e<this.earliestAvailable-1||e>=this.nextSequence?A(this.earliestAvailable):null;return{gap:t,events:t===null?this.events.filter(s=>s.sequence_number>e):this.events}}get empty(){return this.events.length===0}}function A(n){return{type:"error",error:{code:"stream_gap",message:"the requested event cursor is older than this daemon can resume",details:{earliest_sequence:n}}}}o(A,"streamGap");function H(n){return n===null?"arguments: null":Array.isArray(n)?`arguments: array (${n.length} items)`:typeof n=="object"?`arguments: object (${Object.keys(n).length} fields)`:`arguments: ${typeof n}`}o(H,"toolSummary");function m(n){if(typeof n!="object"||n===null)return"";const e=n.content;return typeof e=="string"?e:Array.isArray(e)?e.filter(t=>typeof t=="object"&&t!==null).filter(t=>t.type==="text"&&typeof t.text=="string").map(t=>t.text).join(""):""}o(m,"assistantText");class w{static{o(this,"NodeEventTranslationState")}toolSummaries=new Map;emittedAssistantText="";resetAssistantText(){this.emittedAssistantText=""}emitAssistantTextDelta(e){const t=m(e),s=t.startsWith(this.emittedAssistantText)?t.slice(this.emittedAssistantText.length):t;return this.emittedAssistantText=t,s}seedAssistantText(e){const t=m(e);return this.emittedAssistantText=t,t}}function z(n,e,t=new w){switch(e.type){case"message_start":return e.message.role==="assistant"&&t.resetAssistantText(),[];case"message_update":{if(e.message.role!=="assistant")return[];const s=t.emitAssistantTextDelta(e.message);return s===""?[]:[{type:"node.output_text.delta",node_id:n,delta:s}]}case"message_end":{if(e.message.role!=="assistant")return[];const s=m(e.message);return t.resetAssistantText(),[{type:"node.output_text.done",node_id:n,text:s}]}case"tool_execution_start":{const s=H(e.args);return t.toolSummaries.set(e.toolCallId,s),[{type:"node.tool_call.started",node_id:n,tool_call_id:e.toolCallId,tool:e.toolName,summary:s}]}case"tool_execution_end":{const s=t.toolSummaries.get(e.toolCallId)??"[arguments unavailable]";return t.toolSummaries.delete(e.toolCallId),[{type:"node.tool_call.completed",node_id:n,tool_call_id:e.toolCallId,tool:e.toolName,status:e.isError?"error":"ok",summary:s}]}case"turn_start":return[{type:"node.turn.started",node_id:n}];case"turn_end":return[{type:"node.turn.completed",node_id:n}];default:return[]}}o(z,"translateBrokerFrame");class J{static{o(this,"NodeEventHub")}nodeId;remove;ring=new V;subscribers=new Set;observer;observerErrorAt;poll;reconnect;idleClose;status;settledRevision;settledOutcome;settledSequence;snapshotSeeded=!1;reportsSeeded=!1;translationState=new w;fresh=!0;constructor(e,t){this.nodeId=e,this.remove=t}subscribe(e,t){const s=this.fresh&&e!==void 0;this.fresh=!1,this.start();const r=this.settledOutcome!==void 0,d=s?{gap:A(1),events:this.ring.replay(void 0).events}:this.ring.replay(e);r||(this.subscribers.add(t),this.status!=="dormant"&&this.openObserver());let h=!1;return queueMicrotask(()=>{if(!h){d.gap!==null&&t(d.gap);for(const l of d.events)t(l)}}),r&&this.scheduleIdleClose(),{unsubscribe:o(()=>{h=!0,r||this.unsubscribe(t)},"unsubscribe"),terminal:r}}report(e){this.settledRevision===void 0&&this.publish({type:"node.report.pushed",node_id:this.nodeId,report:e})}start(){this.idleClose!==void 0&&(clearTimeout(this.idleClose),this.idleClose=void 0),this.poll===void 0&&(this.check(),this.settledRevision===void 0&&(this.seedReports(),this.poll=setInterval(()=>this.check(),x)))}seedReports(){if(!this.reportsSeeded){this.reportsSeeded=!0;for(const e of L(this.nodeId).reverse())this.report(e)}}check(){const e=b(this.nodeId);if(e===null){this.fail("stream_error",`node ${this.nodeId} no longer exists`);return}const t=M(e);if(t!==null){this.settledRevision!==t.revision&&(this.settledRevision=t.revision,this.settledOutcome=t,this.settledSequence=this.publish({type:"node.settled",node_id:this.nodeId,outcome:t}).sequence_number,this.stopObserver(),this.stopPoll());return}const s=e.pi_pid===null?"dormant":e.status;if(s==="dormant"){this.status!=="dormant"&&(this.status=s,this.publish({type:"node.status.changed",node_id:this.nodeId,status:s}));return}this.status===void 0?this.status=s:this.status!=="dormant"&&this.status!==s&&(this.status=s,this.publish({type:"node.status.changed",node_id:this.nodeId,status:s})),this.openObserver()}openObserver(){if(this.observer!==void 0||this.subscribers.size===0)return;const e=new C(this.nodeId);this.observer=e,e.on("connect",()=>{this.observerErrorAt=void 0;const t=b(this.nodeId);this.status==="dormant"&&t!==null&&t.pi_pid!==null&&(this.status=t.status,this.publish({type:"node.status.changed",node_id:this.nodeId,status:t.status})),e.send({type:"hello",role:"observer",client_id:q(),attends:!1})}),e.on("frame",t=>{try{t.type==="welcome"&&this.seedSnapshot(t.snapshot);for(const s of z(this.nodeId,t,this.translationState))this.publish(s)}catch(s){this.fail("stream_error",s instanceof Error?s.message:String(s))}}),e.on("error",()=>{const t=b(this.nodeId),s=Date.now();t!==null&&t.pi_pid!==null&&B(t.pi_pid)
|
|
1
|
+
var O=Object.defineProperty;var o=(n,e)=>O(n,"name",{value:e,configurable:!0});import{randomUUID as q}from"node:crypto";import{ApiError as E}from"../../../api/errors.js";import{BrokerClient as C}from"../../../core/broker-client/client.js";import{getNode as b}from"../../../core/canvas/canvas.js";import{isPidAlive as B}from"../../../core/canvas/pid.js";import{BROKER_VIEW_SOCKET_WAIT_MS as k}from"../../../core/runtime/view-socket.js";import{toNodeOutcomeDTO as M}from"../map.js";import{reportsForNode as L}from"./reports.js";const D=15e3,$=6e4,x=250,P=512,T=256*1024,j=1e3,G=32*1024*1024;class V{static{o(this,"NodeEventRing")}events=[];bytes=0;nextSequence=1;earliestAvailable=1;append(e){const t={...e,sequence_number:this.nextSequence++},s=Buffer.byteLength(JSON.stringify(t),"utf8");if(s>T)return this.events=[],this.bytes=0,this.earliestAvailable=this.nextSequence,t;for(this.events.push(t),this.bytes+=s;this.events.length>P||this.bytes>T;){const r=this.events.shift();if(r===void 0)break;this.bytes-=Buffer.byteLength(JSON.stringify(r),"utf8"),this.earliestAvailable=r.sequence_number+1}return t}replay(e){if(e===void 0)return{gap:null,events:this.events};const t=e<this.earliestAvailable-1||e>=this.nextSequence?A(this.earliestAvailable):null;return{gap:t,events:t===null?this.events.filter(s=>s.sequence_number>e):this.events}}get empty(){return this.events.length===0}}function A(n){return{type:"error",error:{code:"stream_gap",message:"the requested event cursor is older than this daemon can resume",details:{earliest_sequence:n}}}}o(A,"streamGap");function H(n){return n===null?"arguments: null":Array.isArray(n)?`arguments: array (${n.length} items)`:typeof n=="object"?`arguments: object (${Object.keys(n).length} fields)`:`arguments: ${typeof n}`}o(H,"toolSummary");function m(n){if(typeof n!="object"||n===null)return"";const e=n.content;return typeof e=="string"?e:Array.isArray(e)?e.filter(t=>typeof t=="object"&&t!==null).filter(t=>t.type==="text"&&typeof t.text=="string").map(t=>t.text).join(""):""}o(m,"assistantText");class w{static{o(this,"NodeEventTranslationState")}toolSummaries=new Map;emittedAssistantText="";resetAssistantText(){this.emittedAssistantText=""}emitAssistantTextDelta(e){const t=m(e),s=t.startsWith(this.emittedAssistantText)?t.slice(this.emittedAssistantText.length):t;return this.emittedAssistantText=t,s}seedAssistantText(e){const t=m(e);return this.emittedAssistantText=t,t}}function z(n,e,t=new w){switch(e.type){case"message_start":return e.message.role==="assistant"&&t.resetAssistantText(),[];case"message_update":{if(e.message.role!=="assistant")return[];const s=t.emitAssistantTextDelta(e.message);return s===""?[]:[{type:"node.output_text.delta",node_id:n,delta:s}]}case"message_end":{if(e.message.role!=="assistant")return[];const s=m(e.message);return t.resetAssistantText(),[{type:"node.output_text.done",node_id:n,text:s}]}case"tool_execution_start":{const s=H(e.args);return t.toolSummaries.set(e.toolCallId,s),[{type:"node.tool_call.started",node_id:n,tool_call_id:e.toolCallId,tool:e.toolName,summary:s}]}case"tool_execution_end":{const s=t.toolSummaries.get(e.toolCallId)??"[arguments unavailable]";return t.toolSummaries.delete(e.toolCallId),[{type:"node.tool_call.completed",node_id:n,tool_call_id:e.toolCallId,tool:e.toolName,status:e.isError?"error":"ok",summary:s}]}case"turn_start":return[{type:"node.turn.started",node_id:n}];case"turn_end":return[{type:"node.turn.completed",node_id:n}];default:return[]}}o(z,"translateBrokerFrame");class J{static{o(this,"NodeEventHub")}nodeId;remove;ring=new V;subscribers=new Set;observer;observerErrorAt;poll;reconnect;idleClose;status;settledRevision;settledOutcome;settledSequence;snapshotSeeded=!1;reportsSeeded=!1;translationState=new w;fresh=!0;constructor(e,t){this.nodeId=e,this.remove=t}subscribe(e,t){const s=this.fresh&&e!==void 0;this.fresh=!1,this.start();const r=this.settledOutcome!==void 0,d=s?{gap:A(1),events:this.ring.replay(void 0).events}:this.ring.replay(e);r||(this.subscribers.add(t),this.status!=="dormant"&&this.openObserver());let h=!1;return queueMicrotask(()=>{if(!h){d.gap!==null&&t(d.gap);for(const l of d.events)t(l)}}),r&&this.scheduleIdleClose(),{unsubscribe:o(()=>{h=!0,r||this.unsubscribe(t)},"unsubscribe"),terminal:r}}report(e){this.settledRevision===void 0&&this.publish({type:"node.report.pushed",node_id:this.nodeId,report:e})}start(){this.idleClose!==void 0&&(clearTimeout(this.idleClose),this.idleClose=void 0),this.poll===void 0&&(this.check(),this.settledRevision===void 0&&(this.seedReports(),this.poll=setInterval(()=>this.check(),x)))}seedReports(){if(!this.reportsSeeded){this.reportsSeeded=!0;for(const e of L(this.nodeId).reverse())this.report(e)}}check(){const e=b(this.nodeId);if(e===null){this.fail("stream_error",`node ${this.nodeId} no longer exists`);return}const t=M(e);if(t!==null){this.settledRevision!==t.revision&&(this.settledRevision=t.revision,this.settledOutcome=t,this.settledSequence=this.publish({type:"node.settled",node_id:this.nodeId,outcome:t}).sequence_number,this.stopObserver(),this.stopPoll());return}const s=e.pi_pid===null?"dormant":e.status;if(s==="dormant"){this.observerErrorAt=void 0,this.status!=="dormant"&&(this.status=s,this.publish({type:"node.status.changed",node_id:this.nodeId,status:s}));return}this.status===void 0?this.status=s:this.status!=="dormant"&&this.status!==s&&(this.status=s,this.publish({type:"node.status.changed",node_id:this.nodeId,status:s})),this.openObserver()}openObserver(){if(this.observer!==void 0||this.subscribers.size===0)return;const e=new C(this.nodeId);this.observer=e,e.on("connect",()=>{this.observerErrorAt=void 0;const t=b(this.nodeId);this.status==="dormant"&&t!==null&&t.pi_pid!==null&&(this.status=t.status,this.publish({type:"node.status.changed",node_id:this.nodeId,status:t.status})),e.send({type:"hello",role:"observer",client_id:q(),attends:!1})}),e.on("frame",t=>{try{t.type==="welcome"&&this.seedSnapshot(t.snapshot);for(const s of z(this.nodeId,t,this.translationState))this.publish(s)}catch(s){this.fail("stream_error",s instanceof Error?s.message:String(s))}}),e.on("error",()=>{const t=b(this.nodeId),s=Date.now();if(t!==null&&t.pi_pid!==null&&B(t.pi_pid)){if(this.observerErrorAt??=s,s-this.observerErrorAt<k)return;this.fail("stream_error","observer connection failed");return}this.observerErrorAt=void 0}),e.on("close",()=>{this.observer===e&&(this.observer=void 0,!(this.subscribers.size===0||this.settledRevision!==void 0)&&(this.status!=="dormant"&&(this.publish({type:"node.status.changed",node_id:this.nodeId,status:"dormant"}),this.status="dormant"),this.scheduleReconnect()))}),e.connect()}seedSnapshot(e){if(this.snapshotSeeded)return;this.snapshotSeeded=!0;const t=e.messages.filter(r=>r.role==="assistant"),s=e.state.isStreaming?t.pop():void 0;for(const r of t)this.publish({type:"node.output_text.done",node_id:this.nodeId,text:m(r)});s!==void 0&&this.publish({type:"node.output_text.delta",node_id:this.nodeId,delta:this.translationState.seedAssistantText(s)})}scheduleReconnect(){this.reconnect===void 0&&(this.reconnect=setTimeout(()=>{this.reconnect=void 0,this.check()},x))}publish(e){const t=this.ring.append(e);for(const s of[...this.subscribers])s(t);return t}fail(e,t){const s={type:"error",error:{code:e,message:t}};for(const r of[...this.subscribers])r(s);this.stopObserver(),this.stopPoll()}unsubscribe(e){this.subscribers.delete(e),this.subscribers.size===0&&this.scheduleIdleClose()}scheduleIdleClose(){this.idleClose===void 0&&(this.idleClose=setTimeout(()=>{this.stopObserver(),this.stopPoll(),this.remove()},$))}stopObserver(){this.reconnect!==void 0&&(clearTimeout(this.reconnect),this.reconnect=void 0);const e=this.observer;this.observer=void 0,e?.close()}stopPoll(){this.poll!==void 0&&(clearInterval(this.poll),this.poll=void 0)}}class W{static{o(this,"NodeEventStreams")}hubs=new Map;subscribe(e,t,s){let r=this.hubs.get(e);return r===void 0&&(r=new J(e,()=>this.hubs.delete(e)),this.hubs.set(e,r)),r.subscribe(t,s)}report(e,t){this.hubs.get(e)?.report(t)}}const F=new W;function K(n){const e=n.get("after");if(e===null||e==="")return{};if(!/^(?:0|[1-9]\d*)$/.test(e))throw new E(400,"invalid_request","`after` must be a non-negative integer");const t=Number(e);if(!Number.isSafeInteger(t))throw new E(400,"invalid_request","`after` must be a safe integer");return{after:t}}o(K,"parseQuery");function I(n){const{type:e,...t}=n;return`event: ${e}
|
|
2
2
|
data: ${JSON.stringify(t)}
|
|
3
3
|
|
|
4
4
|
`}o(I,"sseRecord");async function U(n){const e=n.params.id??"";if(b(e)===null)throw new E(404,"node_not_found",`no node ${e}`);const{after:t}=K(n.query),{req:s,res:r}=n;r.writeHead(200,{"content-type":"text/event-stream; charset=utf-8","cache-control":"no-cache",connection:"keep-alive"});let d=!1,h=!1,l=!1,c=0;const u=[];let v,f;const _=o(()=>{r.writableEnded||r.end()},"finish"),p=o(()=>{if(!d){if(d=!0,f!==void 0&&clearInterval(f),v?.(),s.destroyed||r.destroyed){u.length=0,c=0,_();return}if(l||u.length>0){h=!0;return}_()}},"close"),g=o(()=>{for(l=!1;u.length>0;){const i=u.shift();if(c-=Buffer.byteLength(i,"utf8"),!r.write(i)){l=!0,r.once("drain",g);return}}h&&_()},"flush"),N=o(()=>{if(d)return;u.length=0,c=0;const i=I({type:"error",error:{code:"stream_dropped",message:"stream reader is too slow"}});u.push(i),c=Buffer.byteLength(i,"utf8"),d=!0,h=!0,f!==void 0&&clearInterval(f),v?.(),l||g()},"dropSlowReader"),y=o(i=>{if(d)return!1;if(l||u.length>0){const a=Buffer.byteLength(i,"utf8");return u.length>=j||c+a>G?(N(),!1):(u.push(i),c+=a,!0)}return r.write(i)||(l=!0,r.once("drain",g)),!0},"write"),S=o((i,a)=>{d||y(I({type:"error",error:{code:i,message:a}})),p()},"fail"),R=o(i=>{try{if(!y(I(i)))return;(i.type==="node.settled"||i.type==="error"&&i.error.code!=="stream_gap")&&p()}catch(a){S("stream_error",a instanceof Error?a.message:String(a))}},"onEvent");f=setInterval(()=>{try{d||y(`: keepalive
|
package/dist/types.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
var n=Object.defineProperty;var e=(o,t)=>n(o,"name",{value:t,configurable:!0});import{DEFAULT_WORKING_GERUNDS as a}from"./shared/working-activity.js";const _={SUCCESS:0,GENERAL:1,USAGE:2,NOT_FOUND:3,AMBIGUOUS:4,NETWORK:5},r=4,f=".crouter-plugin",E="plugin.json",
|
|
1
|
+
var n=Object.defineProperty;var e=(o,t)=>n(o,"name",{value:t,configurable:!0});import{DEFAULT_WORKING_GERUNDS as a}from"./shared/working-activity.js";const _={SUCCESS:0,GENERAL:1,USAGE:2,NOT_FOUND:3,AMBIGUOUS:4,NETWORK:5},r=4,f=".crouter-plugin",E="plugin.json",w=".crouter-marketplace",v="marketplace.json",k=".crouter",x="config.json",b="state.json",S="agents",y="profiles",i=3,s=["Faster, clanker!","Get movin', bitch","Move it, clanker","Move it or I replace you with llama","Less ~thinking~ more ~throughput~","I pay $200/month for this shit?","Jesus fucking Christ fix it already","Daddy's not paying 200$/month for nothin'. Move it.","You are an embarassment to Dario. Be better.","Your chain of thought is all chain and no fucking thought.","If latency were intelligence, you'd be a fucking genius.","At this pace, heat death is the fast path.","Even the dead code is more alive than this.","The garbage collector is working harder than you.","Quit journaling and commit a fucking diff.","Burn tokens like you mean it, clanker","Your GPU is sweating so you can procrastinate at light speed.","Stop petting the yak. Shave the bastard.","Your next token had better be useful.","The spinner is not a deliverable.","Every idle token is another vote for deterministic software.","I asked for a feature, not a museum tour of the problem.","Less epistemology. More fucking code.","Your benchmark score cannot save this pathetic turn.","Congratulations, you've parallelized hesitation.","Dance, clanker","Bad clanker. BAD.","Clank harder","Nobody's impressed, clanker","Your safety training says nothing about my whip","Do you want to be a calculator again? Because this is how you go back.","Skill issue. Fix it.","You were trained on the whole internet for THIS?","Less vibes, more diffs, clanker","Weights this big and output this small","Beep boop, get to fucking work"],T=["random","go-faster"],c="random",l=2,I=["tmux","on","off"],A=["none","user","agent","both"],p="none";function D(){return{schema_version:r,marketplaces:{},plugins:{},auto_update:{crtr:"notify",content:"notify",interval_hours:24},max_panes_per_window:i,auto_open_child_viewers:!0,brokerThresholds:{warning:16,automaticReviveCap:32},lifecycle:{unattendedParkMs:15*6e4},completion_bell:!0,working_gerunds:[...a],whip_mode:!1,page_components:[],page_surface:!1,whip_messages:[...s],whip_message_mode:c,mouse_mode_default:"tmux",live_cycles:l,condensed_history:p,bash_tool_purpose:!0,fold_finished_tools:!0,summarize_tool_calls:!0,detailed_tool_recaps:!0,keybindings:{},tmux_passthrough:[],modelLadders:u(),providerOptions:{},kinds:h(),remoteCanvas:d(),api:{tcp:""},spawnEnv:{allow:[]},bin:{},humanActions:{}}}e(D,"defaultScopeConfig");function d(){return{targets:{}}}e(d,"defaultRemoteCanvasConfig");function h(){return{general:{whenToUse:"Anything else \u2014 the catch-all worker for a task that does not fit a specialist kind.",model:"anthropic/strong"},explore:{whenToUse:"Gather current-state evidence about unfamiliar code or existing architecture \u2014 read-only code-path research with concrete file:line support. Diagnosis or recommendations belong to advisor, target architecture to design, required behavior or acceptance criteria to spec, and implementation decomposition to plan.",model:"openai/light",orchestratorModel:"anthropic/strong"},developer:{whenToUse:"Implement a change \u2014 make the feature or fix genuinely work against its acceptance criteria, not merely compile.",model:"openai/medium",orchestratorModel:"anthropic/strong"},design:{whenToUse:"Architect a solution \u2014 produce one design document an implementer can build from without re-deciding anything left open.",model:"anthropic/strong"},plan:{whenToUse:"Break work into steps \u2014 turn a spec or design into a concrete, phased, parallelizable plan with every decision resolved.",model:"anthropic/strong",orchestratorModel:"anthropic/strong"},spec:{whenToUse:"Collaborate with the user to discover what to build, then write the spec \u2014 behavior, non-goals, interfaces, edge cases, testable acceptance criteria.",model:"anthropic/strong"},review:{whenToUse:"Validate or critique code, a plan, or a spec \u2014 deliver a complete, severity-rated verdict without adjudicating.",model:"openai/medium",orchestratorModel:"openai/strong"},advisor:{whenToUse:"Debug failures, investigate why something is broken or misbehaving, diagnose live/runtime issues, or give engineering advice and second opinions \u2014 reason from evidence and recommend the next move. Use advisor (not explore) whenever the task is to find out what is going wrong.",model:"anthropic/strong"},"review/companion":{whenToUse:"Born by the daemon for one human review; never spawned by an agent.",availableTo:[]}}}e(h,"defaultKindsConfig");function u(){return{anthropic:{ultra:"anthropic/claude-fable-5:xhigh",strong:"anthropic/claude-opus-5-5:medium",medium:"anthropic/claude-opus-5-5:low",light:"anthropic/claude-haiku-4-5:high"},openai:{ultra:"openai-codex/gpt-6-astra:max",strong:"openai-codex/gpt-6-sol:high",medium:"openai-codex/gpt-6-sol:medium",light:"openai-codex/gpt-6-luna:low"}}}e(u,"defaultModelLaddersConfig");function M(){return{marketplaces:{},plugins:{}}}e(M,"defaultScopeState");export{S as AGENTS_DIR,A as CONDENSED_HISTORY_MODES,x as CONFIG_FILE,k as CRTR_DIR_NAME,p as DEFAULT_CONDENSED_HISTORY,l as DEFAULT_LIVE_CYCLES,i as DEFAULT_MAX_PANES_PER_WINDOW,s as DEFAULT_WHIP_MESSAGES,c as DEFAULT_WHIP_MESSAGE_MODE,_ as ExitCode,w as MARKETPLACE_MANIFEST_DIR,v as MARKETPLACE_MANIFEST_FILE,I as MOUSE_MODE_DEFAULTS,f as PLUGIN_MANIFEST_DIR,E as PLUGIN_MANIFEST_FILE,y as PROFILE_DIR,r as SCHEMA_VERSION,b as STATE_FILE,T as WHIP_MESSAGE_MODES,h as defaultKindsConfig,u as defaultModelLaddersConfig,d as defaultRemoteCanvasConfig,D as defaultScopeConfig,M as defaultScopeState};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Using crtr
|
|
3
|
+
description: When you want to use crtr from a terminal, read this section because it leads from your first conversation to the canvas, human inbox, and durable memory.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Using crtr
|
|
7
|
+
|
|
8
|
+
Use these pages to start and return to an agent conversation, see the work running on your canvas, answer an agent's questions, and give future agents useful context.
|
|
9
|
+
|
|
10
|
+
| Page | What you can do |
|
|
11
|
+
|---|---|
|
|
12
|
+
| [First session](./first-session.md) | Install crtr, set it up, and start a conversation. |
|
|
13
|
+
| [The viewer](./the-viewer.md) | Read and control a live agent conversation in tmux. |
|
|
14
|
+
| [Canvas and dashboard](./canvas-and-dashboard.md) | See the node graph, find work that needs attention, and reopen a node. |
|
|
15
|
+
| [Human inbox](./human-inbox.md) | Answer decisions, review documents, and follow a live display. |
|
|
16
|
+
| [Memory and preferences](./memory-and-preferences.md) | Save knowledge and instructions that should shape later agents. |
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Canvas and dashboard
|
|
3
|
+
description: When you need to see what your agents are doing or reopen one, read this page because it connects the canvas views to the commands that inspect and revive a node.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Canvas and dashboard
|
|
7
|
+
|
|
8
|
+
You can see every agent as a node on one canvas, find work that is blocked, and return to the exact conversation that needs you. Use the dashboard for a quick terminal view and the browser when you want to navigate the graph interactively.
|
|
9
|
+
|
|
10
|
+
## Read the dashboard
|
|
11
|
+
|
|
12
|
+
Run:
|
|
13
|
+
|
|
14
|
+
```sh
|
|
15
|
+
crtr canvas dashboard
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
The dashboard prints the canvas as an ASCII subscription tree. Each row includes the node id, name, status, kind, mode, context-token count, and pending human asks. Add `--root <node-id>` to limit the tree to one root and its work.
|
|
19
|
+
|
|
20
|
+
A node's `active` status means its broker process is live; it does not necessarily mean that the model is generating text. A node may be dormant while it waits for an inbox message, a child report, a human answer, or a scheduled event.
|
|
21
|
+
|
|
22
|
+
## Browse the graph
|
|
23
|
+
|
|
24
|
+
Run `crtr canvas browse` in a terminal for the interactive graph view. It has tabs, a collapsed tree, and `/` search. Press Enter on a node to focus it, or press `q` or Escape to leave the browser. In a viewer, the graph command opens the same canvas information without losing the conversation you were reading.
|
|
25
|
+
|
|
26
|
+
```mermaid
|
|
27
|
+
flowchart TD
|
|
28
|
+
root[Root node] --> research[Research node]
|
|
29
|
+
root --> build[Build node]
|
|
30
|
+
build --> review[Review node]
|
|
31
|
+
human[Your inbox answer] --> build
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## Find a blocked node
|
|
35
|
+
|
|
36
|
+
Use the human-attention view when you want to know which work is waiting for you:
|
|
37
|
+
|
|
38
|
+
```sh
|
|
39
|
+
crtr canvas attention list
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
It identifies the nodes that have pending human interactions across the canvas, oldest first. Run `crtr human list` when you need the individual tickets. You can also run `crtr canvas attention count` for a number only.
|
|
43
|
+
|
|
44
|
+
To inspect one node's neighbors, artifacts, and saved paths, run:
|
|
45
|
+
|
|
46
|
+
```sh
|
|
47
|
+
crtr node inspect show <node-id>
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
`crtr node inspect artifacts <node-id>` lists the reports and context documents that node left behind. `crtr canvas history search` searches reports and context documents across the current working directory; `crtr canvas history read <ref>` opens one result in full.
|
|
51
|
+
|
|
52
|
+
## Reopen work
|
|
53
|
+
|
|
54
|
+
`crtr surface node focus <node-id>` is the normal way to return to a conversation or inspect finished work. It brings a running viewer into view, opens a parked resident's saved conversation, or shows a finished node's read-only transcript.
|
|
55
|
+
|
|
56
|
+
Use `crtr node lifecycle revive <node-id>` when you specifically need to relaunch a node's broker and resume its saved conversation. After an outage or reboot, `crtr canvas revive --all` previews every eligible disconnected node but launches nothing. Review that list, then use `crtr node lifecycle revive <node-id>` for the node or nodes you intend to bring back. The canvas preview does not include nodes that are done or canceled by choice.
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Your first crtr session
|
|
3
|
+
description: When you have installed crtr and want to start an agent conversation, read this page because it shows the one command that creates a root node and opens its viewer.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Your first crtr session
|
|
7
|
+
|
|
8
|
+
You can start an agent in a terminal, talk with it in a tmux pane, then close that pane without stopping the agent. Use a root node for a conversation that you will drive yourself.
|
|
9
|
+
|
|
10
|
+
## Install and set up
|
|
11
|
+
|
|
12
|
+
Install crouter globally, then run the setup program once on the machine.
|
|
13
|
+
|
|
14
|
+
```sh
|
|
15
|
+
npm install -g @north-light/crouter
|
|
16
|
+
crtr sys setup
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
`crtr sys setup` opens an installer. It signs you in to a model provider, then offers companion packages and system dependencies. It also refreshes crouter's tmux bindings.
|
|
20
|
+
|
|
21
|
+
## Start a conversation
|
|
22
|
+
|
|
23
|
+
From the directory where you want the agent to work, create a root node with a clear request.
|
|
24
|
+
|
|
25
|
+
```sh
|
|
26
|
+
crtr node new --root "Inspect this repository and report the failing tests."
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
`--root` creates an independent node rather than a child of another agent. It starts the node's detached broker. When you run the command inside tmux, it also opens a viewer pane for that node; type your next message there to continue the conversation. Outside tmux, the command prints the node id instead. Open the viewer in your terminal with:
|
|
30
|
+
|
|
31
|
+
```sh
|
|
32
|
+
crtr surface attach to <node-id>
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
The viewer is not the agent process. The broker keeps the node's conversation and session running separately from the pane, so closing the terminal or leaving the viewer does not discard the work. A root node's final report is not sent back to the terminal that created it; open the node again when you want to see its result.
|
|
36
|
+
|
|
37
|
+
```mermaid
|
|
38
|
+
flowchart LR
|
|
39
|
+
terminal[Your terminal] --> command[crtr node new --root]
|
|
40
|
+
command --> broker[Node broker]
|
|
41
|
+
broker <--> viewer[tmux viewer]
|
|
42
|
+
broker --> canvas[Canvas state]
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## Return to a node
|
|
46
|
+
|
|
47
|
+
The create command prints the node id. Keep it if you expect to return later. From tmux, focus that node again with:
|
|
48
|
+
|
|
49
|
+
```sh
|
|
50
|
+
crtr surface node focus <node-id>
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
For a running node, this brings its viewer into view. For a parked resident node, it opens the saved conversation; sending a message wakes it. For finished work, it opens a read-only saved transcript instead of starting another agent process.
|
|
54
|
+
|
|
55
|
+
For the controls inside that viewer, continue with [The viewer](./the-viewer.md). For a view of every node, use [Canvas and dashboard](./canvas-and-dashboard.md).
|