[{"data":1,"prerenderedAt":914},["ShallowReactive",2],{"navigation_docs":3,"-guide-cli":52,"-guide-cli-surround":909},[4,28],{"title":5,"path":6,"stem":7,"children":8,"icon":27},"Guide","\u002Fguide","1.guide\u002F01.index",[9,11,15,19,23],{"title":10,"path":6,"stem":7},"Getting Started",{"title":12,"path":13,"stem":14},"Defining Tools","\u002Fguide\u002Fdefining-tools","1.guide\u002F02.defining-tools",{"title":16,"path":17,"stem":18},"Validation and Errors","\u002Fguide\u002Fvalidation","1.guide\u002F03.validation",{"title":20,"path":21,"stem":22},"CLI","\u002Fguide\u002Fcli","1.guide\u002F04.cli",{"title":24,"path":25,"stem":26},"Moving a Package Over","\u002Fguide\u002Fmigrating","1.guide\u002F05.migrating","i-lucide-book-open",{"title":29,"path":30,"stem":31,"children":32,"icon":51},"Hosts","\u002Fhosts","2.hosts\u002F00.index",[33,35,39,43,47],{"title":34,"path":30,"stem":31},"Every host",{"title":36,"path":37,"stem":38},"MCP","\u002Fhosts\u002Fmcp","2.hosts\u002F01.mcp",{"title":40,"path":41,"stem":42},"Pi","\u002Fhosts\u002Fpi","2.hosts\u002F02.pi",{"title":44,"path":45,"stem":46},"OMP","\u002Fhosts\u002Fomp","2.hosts\u002F03.omp",{"title":48,"path":49,"stem":50},"AI SDK","\u002Fhosts\u002Fai-sdk","2.hosts\u002F04.ai-sdk","i-lucide-table",{"id":53,"title":54,"body":55,"description":900,"extension":901,"links":902,"meta":903,"navigation":905,"path":21,"seo":906,"stem":22,"__hash__":908},"docs\u002F1.guide\u002F04.cli.md","The CLI",{"type":56,"value":57,"toc":885},"minimark",[58,71,191,198,207,214,219,224,257,261,276,309,320,323,329,333,340,400,414,420,423,429,435,439,446,472,482,488,513,517,530,541,547,550,556,578,582,601,605,615,621,632,642,645,649,663,713,720,724,735,750,756,766,770,782,843,859,881],[59,60,61,62,66,67,70],"p",{},"Your tools already know their arguments. The schema says which ones are required, what type each one is and which values an enum takes. So why write a CLI by hand? ",[63,64,65],"code",{},"runCli"," reads all of that and gives every tool its own command. No CLI library underneath, just ",[63,68,69],{},"util.parseArgs"," from Node.",[72,73,79],"pre",{"className":74,"code":75,"filename":76,"language":77,"meta":78,"style":78},"language-ts shiki shiki-themes tools tools tools","import { runCli } from \"@agntn\u002Ftools\u002Fcli\";\nimport { slugTool } from \".\u002Ftools.ts\";\n\nawait runCli({\n  name: \"slugs\",\n  version: \"1.0.0\",\n  description: \"Slugs from titles\",\n  tools: [slugTool],\n  mcp: true,\n});\n","src\u002Fcli.ts","ts","",[63,80,81,104,119,126,139,151,162,173,179,185],{"__ignoreMap":78},[82,83,86,90,94,97,101],"span",{"class":84,"line":85},"line",1,[82,87,89],{"class":88},"skH_V","import",[82,91,93],{"class":92},"s38Sx"," { runCli } ",[82,95,96],{"class":88},"from",[82,98,100],{"class":99},"shU9J"," \"@agntn\u002Ftools\u002Fcli\"",[82,102,103],{"class":92},";\n",[82,105,107,109,112,114,117],{"class":84,"line":106},2,[82,108,89],{"class":88},[82,110,111],{"class":92}," { slugTool } ",[82,113,96],{"class":88},[82,115,116],{"class":99}," \".\u002Ftools.ts\"",[82,118,103],{"class":92},[82,120,122],{"class":84,"line":121},3,[82,123,125],{"emptyLinePlaceholder":124},true,"\n",[82,127,129,132,136],{"class":84,"line":128},4,[82,130,131],{"class":88},"await",[82,133,135],{"class":134},"sK71F"," runCli",[82,137,138],{"class":92},"({\n",[82,140,142,145,148],{"class":84,"line":141},5,[82,143,144],{"class":92},"  name: ",[82,146,147],{"class":99},"\"slugs\"",[82,149,150],{"class":92},",\n",[82,152,154,157,160],{"class":84,"line":153},6,[82,155,156],{"class":92},"  version: ",[82,158,159],{"class":99},"\"1.0.0\"",[82,161,150],{"class":92},[82,163,165,168,171],{"class":84,"line":164},7,[82,166,167],{"class":92},"  description: ",[82,169,170],{"class":99},"\"Slugs from titles\"",[82,172,150],{"class":92},[82,174,176],{"class":84,"line":175},8,[82,177,178],{"class":92},"  tools: [slugTool],\n",[82,180,182],{"class":84,"line":181},9,[82,183,184],{"class":92},"  mcp: true,\n",[82,186,188],{"class":84,"line":187},10,[82,189,190],{"class":92},"});\n",[59,192,193,194,197],{},"Point your ",[63,195,196],{},"bin"," at the built file and that's it:",[72,199,205],{"className":200,"code":202,"filename":203,"language":204,"meta":78},[201],"language-text","$ slugs slug --text \"Crème brûlée\"\ncreme-brulee\n","terminal","text",[63,206,202],{"__ignoreMap":78},[59,208,209,210,213],{},"It's the one Node part of the package. The core and the host adapters run in a browser tab, this one reads ",[63,211,212],{},"process.argv",".",[215,216,218],"h2",{"id":217},"how-a-tool-turns-into-a-command","How a tool turns into a command",[220,221,223],"h3",{"id":222},"where-does-the-command-name-come-from","Where does the command name come from?",[59,225,226,227,230,231,234,235,230,238,241,242,245,246,249,250,253,254,213],{},"From the tool name, minus the package prefix. ",[63,228,229],{},"text_slug"," becomes ",[63,232,233],{},"slug",", ",[63,236,237],{},"hashes_digest_identify",[63,239,240],{},"digest-identify",". Want another word? The ",[63,243,244],{},"cli"," hint on ",[63,247,248],{},"defineTool"," takes a ",[63,251,252],{},"command"," and a few ",[63,255,256],{},"aliases",[220,258,260],{"id":259},"flags-straight-from-the-schema","Flags straight from the schema",[59,262,263,264,267,268,271,272,275],{},"Every property is a flag, in kebab case. ",[63,265,266],{},"maxWords"," and ",[63,269,270],{},"max_words"," both turn into ",[63,273,274],{},"--max-words",". Strings go in as they are. Integers and booleans get converted against the schema. Objects and arrays come in as JSON:",[72,277,281],{"className":278,"code":279,"filename":203,"language":280,"meta":78,"style":78},"language-bash shiki shiki-themes tools tools tools","$ slugs batch --titles '[\"Hello World\", \"Crème brûlée\"]' --max-words 2 --no-lower\n","bash",[63,282,283],{"__ignoreMap":78},[82,284,285,288,291,294,297,300,303,306],{"class":84,"line":85},[82,286,287],{"class":134},"$",[82,289,290],{"class":99}," slugs",[82,292,293],{"class":99}," batch",[82,295,296],{"class":99}," --titles",[82,298,299],{"class":99}," '[\"Hello World\", \"Crème brûlée\"]'",[82,301,302],{"class":99}," --max-words",[82,304,305],{"class":99}," 2",[82,307,308],{"class":99}," --no-lower\n",[59,310,311,312,315,316,319],{},"A boolean answers to ",[63,313,314],{},"--lower"," and to ",[63,317,318],{},"--no-lower",". Say both and the command refuses, it doesn't pick a winner. Same for an unknown option, a missing value or a word too many. Nothing on the line is quietly skipped.",[59,321,322],{},"Then the core validates, like on every other host. A typo here gets the same treatment a model's typo gets:",[72,324,327],{"className":325,"code":326,"filename":203,"language":204,"meta":78},[201],"$ slugs slug --text Hello --sep _\nInvalid arguments: unknown option \"--sep\"; takes --text, --separator, --json\n$ slugs slug --text Hello --separator \"~\"\nInvalid arguments at \u002Fseparator: must be one of -, _, .\n",[63,328,326],{"__ignoreMap":78},[220,330,332],{"id":331},"positionals-and-stdin","Positionals and stdin",[59,334,335,336,339],{},"Typing ",[63,337,338],{},"--text"," every time gets old fast. Two more hints fix that:",[72,341,344],{"className":74,"code":342,"filename":343,"language":77,"meta":78,"style":78},"export const slugTool = defineTool({\n  name: \"text_slug\",\n  \u002F\u002F …\n  cli: { positional: [\"text\"], stdin: [\"text\"] },\n});\n","tools.ts",[63,345,346,365,374,380,396],{"__ignoreMap":78},[82,347,348,351,354,357,360,363],{"class":84,"line":85},[82,349,350],{"class":88},"export",[82,352,353],{"class":88}," const",[82,355,356],{"class":92}," slugTool ",[82,358,359],{"class":88},"=",[82,361,362],{"class":134}," defineTool",[82,364,138],{"class":92},[82,366,367,369,372],{"class":84,"line":106},[82,368,144],{"class":92},[82,370,371],{"class":99},"\"text_slug\"",[82,373,150],{"class":92},[82,375,376],{"class":84,"line":121},[82,377,379],{"class":378},"scIB-","  \u002F\u002F …\n",[82,381,382,385,388,391,393],{"class":84,"line":128},[82,383,384],{"class":92},"  cli: { positional: [",[82,386,387],{"class":99},"\"text\"",[82,389,390],{"class":92},"], stdin: [",[82,392,387],{"class":99},[82,394,395],{"class":92},"] },\n",[82,397,398],{"class":84,"line":141},[82,399,190],{"class":92},[59,401,402,405,406,409,410,413],{},[63,403,404],{},"positional"," takes properties in order, as plain words. ",[63,407,408],{},"stdin"," lets a string property read standard input when you pass ",[63,411,412],{},"-",":",[72,415,418],{"className":416,"code":417,"filename":203,"language":204,"meta":78},[201],"$ slugs slug \"Hello, World\" --separator _\nhello_world\n$ echo \"Pipe me in\" | slugs slug -\npipe-me-in\n",[63,419,417],{"__ignoreMap":78},[59,421,422],{},"Stdin has to be valid UTF-8. A lenient decode would swap bad bytes for U+FFFD, and a hash tool would happily hash something you never sent. So invalid input is an error. Got real binary data? That's a job for a command of your own, see below.",[59,424,425,428],{},[63,426,427],{},"--help"," is built from the same fields. Descriptions come from the schema, the enum values too:",[72,430,433],{"className":431,"code":432,"language":204,"meta":78},[201],"Turn a title into a lowercase URL slug. (slugs slug v1.0.0)\n\nUSAGE slugs slug [OPTIONS] \u003CTEXT>\n\nARGUMENTS\n\n  TEXT  Title to slug (- reads stdin) (required)\n\nOPTIONS\n\n  --separator=\u003C-|_|.>  Joins the words. '-' when omitted\n  --json               Print the details as JSON instead of the text\n  -h, --help           Show this help\n",[63,434,432],{"__ignoreMap":78},[220,436,438],{"id":437},"short-flags-and-the-rest-of-the-line","Short flags and the rest of the line",[59,440,441,442,445],{},"Quotes around every title get old too. So does ",[63,443,444],{},"--separator",". Two hints bring back what every CLI written by hand had:",[72,447,449],{"className":74,"code":448,"filename":343,"language":77,"meta":78,"style":78},"cli: { rest: \"text\", stdin: [\"text\"], short: { separator: \"s\" } },\n",[63,450,451],{"__ignoreMap":78},[82,452,453,456,458,461,463,466,469],{"class":84,"line":85},[82,454,455],{"class":92},"cli: { rest: ",[82,457,387],{"class":99},[82,459,460],{"class":92},", stdin: [",[82,462,387],{"class":99},[82,464,465],{"class":92},"], short: { separator: ",[82,467,468],{"class":99},"\"s\"",[82,470,471],{"class":92}," } },\n",[59,473,474,477,478,481],{},[63,475,476],{},"rest"," takes every word the positionals leave over and joins them with single spaces. ",[63,479,480],{},"short"," gives an option an alias of one letter:",[72,483,486],{"className":484,"code":485,"filename":203,"language":204,"meta":78},[201],"$ slugs slug Hello big world -s _\nhello_big_world\n",[63,487,485],{"__ignoreMap":78},[59,489,490,491,494,495,498,499,501,502,505,506,509,510,512],{},"Only a whole word counts as a short flag, though. ",[63,492,493],{},"-s _"," works, ",[63,496,497],{},"-s_"," is an unknown option. Pedantic? A bit. But ",[63,500,69],{}," reads ",[63,503,504],{},"-separator _"," as ",[63,507,508],{},"-s eparator",", and next to a greedy ",[63,511,476],{}," one dash too few shouldn't turn into some other call. Here it fails and tells you why.",[220,514,516],{"id":515},"text-that-starts-with-a-dash","Text that starts with a dash",[59,518,519,520,523,524,526,527,213],{},"Is ",[63,521,522],{},"-.-."," an option? ",[63,525,69],{}," thinks so. In Morse it's a C. Every PGP armor line opens with five dashes, and a temperature can be ",[63,528,529],{},"-12",[59,531,532,533,234,535,234,537,540],{},"So a word counts as an option only when it spells one as a whole: ",[63,534,444],{},[63,536,318],{},[63,538,539],{},"-s",". Any other dashed word takes the next free positional:",[72,542,545],{"className":543,"code":544,"filename":203,"language":204,"meta":78},[201],"$ slugs slug \"-12 below zero\"\n12-below-zero\n",[63,546,544],{"__ignoreMap":78},[59,548,549],{},"Free means the plain words left room for it. Once they fill every positional, a dashed word is an unknown option again. A typo after the title fails, it doesn't end up in your slug:",[72,551,554],{"className":552,"code":553,"filename":203,"language":204,"meta":78},[201],"$ slugs slug Hello --seperator _\nInvalid arguments: unknown option \"--seperator\"; takes --separator (-s), --json\n",[63,555,553],{"__ignoreMap":78},[59,557,558,560,561,564,565,267,568,571,572,574,575,213],{},[63,559,476],{}," counts as one positional here. ",[63,562,563],{},"slugs slug -12 below zero"," fails, because ",[63,566,567],{},"below",[63,569,570],{},"zero"," already took the spot. Quote the title and it's one word again. Text that does spell a flag, like ",[63,573,539],{}," itself, goes after ",[63,576,577],{},"--",[220,579,581],{"id":580},"when-does-a-broken-hint-show-up","When does a broken hint show up?",[59,583,584,585,588,589,592,593,596,597,600],{},"At startup, before any command runs. A hint naming a property the tool doesn't have, two properties fighting over one flag, a boolean called ",[63,586,587],{},"noCache",", a required positional after an optional one, a short flag that tries to take ",[63,590,591],{},"-h",". Each one fails in ",[63,594,595],{},"createCli"," with a ",[63,598,599],{},"ToolDefinitionError",". Not on the first user who types the wrong thing.",[215,602,604],{"id":603},"what-comes-out","What comes out",[59,606,607,608,611,612,413],{},"The command prints the tool's text, the same text a model reads. Want data instead? ",[63,609,610],{},"--json"," prints ",[63,613,614],{},"details",[72,616,619],{"className":617,"code":618,"filename":203,"language":204,"meta":78},[201],"$ slugs slug \"Hello\" --json\n{\n  \"slug\": \"hello\",\n  \"words\": 1\n}\n",[63,620,618],{"__ignoreMap":78},[59,622,623,624,627,628,631],{},"A returned ",[63,625,626],{},"isError"," goes to stderr with exit code 1. So ",[63,629,630],{},"slugs slug \"!!!\""," fails like a proper command, scripts included.",[59,633,634,635,637,638,641],{},"A thrown error is a different story. Tell ",[63,636,65],{}," which errors you throw on purpose through ",[63,639,640],{},"expected",", and those print as one line with exit code 1. Anything else keeps its stack trace. A bug should look like a bug.",[59,643,644],{},"Errors get the same treatment as on the other hosts: one line each, so a newline in an echoed argument can't forge a second one. Escape codes and bidi tricks don't reach your terminal, not even through the tool's own text. And there are no colors at all, so nothing breaks in a pipe.",[215,646,648],{"id":647},"commands-only-the-cli-gets","Commands only the CLI gets",[59,650,651,652,655,656,659,660,413],{},"Some things belong on the command line and nowhere else. A ",[63,653,654],{},"version"," with build details, a ",[63,657,658],{},"doctor",", a reader for binary stdin. Write them as tool definitions and pass them as ",[63,661,662],{},"commands",[72,664,666],{"className":74,"code":665,"filename":76,"language":77,"meta":78,"style":78},"await runCli({\n  name: \"slugs\",\n  version: \"1.0.0\",\n  description: \"Slugs from titles\",\n  tools: [slugTool],\n  commands: [separatorsCommand],\n});\n",[63,667,668,676,684,692,700,704,709],{"__ignoreMap":78},[82,669,670,672,674],{"class":84,"line":85},[82,671,131],{"class":88},[82,673,135],{"class":134},[82,675,138],{"class":92},[82,677,678,680,682],{"class":84,"line":106},[82,679,144],{"class":92},[82,681,147],{"class":99},[82,683,150],{"class":92},[82,685,686,688,690],{"class":84,"line":121},[82,687,156],{"class":92},[82,689,159],{"class":99},[82,691,150],{"class":92},[82,693,694,696,698],{"class":84,"line":128},[82,695,167],{"class":92},[82,697,170],{"class":99},[82,699,150],{"class":92},[82,701,702],{"class":84,"line":141},[82,703,178],{"class":92},[82,705,706],{"class":84,"line":153},[82,707,708],{"class":92},"  commands: [separatorsCommand],\n",[82,710,711],{"class":84,"line":164},[82,712,190],{"class":92},[59,714,715,716,719],{},"MCP, Pi, OMP and the AI SDK never see them. They parse, validate and print exactly like the tools. One with the name of a generated command takes its place, ",[63,717,718],{},"mcp"," included.",[220,721,723],{"id":722},"can-it-guess-what-i-meant","Can it guess what I meant?",[59,725,726,727,730,731,734],{},"If you let it. ",[63,728,729],{},"default"," names the command for an empty line. ",[63,732,733],{},"fallback"," takes any first word that isn't a command:",[72,736,738],{"className":74,"code":737,"language":77,"meta":78,"style":78},"fallback: \"slug\",\n",[63,739,740],{"__ignoreMap":78},[82,741,742,745,748],{"class":84,"line":85},[82,743,744],{"class":92},"fallback: ",[82,746,747],{"class":99},"\"slug\"",[82,749,150],{"class":92},[72,751,754],{"className":752,"code":753,"filename":203,"language":204,"meta":78},[201],"$ slugs \"Hello, World\"\nhello-world\n",[63,755,753],{"__ignoreMap":78},[59,757,758,759,234,762,765],{},"Just remember it's greedy. Without ",[63,760,761],{},"mcp: true",[63,763,764],{},"slugs mcp"," slugs the word \"mcp\". Correct, and completely useless.",[215,767,769],{"id":768},"an-mcp-server-on-the-side","An MCP server on the side",[59,771,772,774,775,777,778,781],{},[63,773,761],{}," adds an ",[63,776,718],{}," command. It's ",[63,779,780],{},"createMcpServer"," over stdio, with the CLI's name and version as the server info:",[72,783,788],{"className":784,"code":785,"filename":786,"language":787,"meta":78,"style":78},"language-json shiki shiki-themes tools tools tools","{\n  \"mcpServers\": {\n    \"slugs\": { \"command\": \"slugs\", \"args\": [\"mcp\"] }\n  }\n}\n",".mcp.json","json",[63,789,790,795,803,833,838],{"__ignoreMap":78},[82,791,792],{"class":84,"line":85},[82,793,794],{"class":92},"{\n",[82,796,797,800],{"class":84,"line":106},[82,798,799],{"class":134},"  \"mcpServers\"",[82,801,802],{"class":92},": {\n",[82,804,805,808,811,814,817,819,821,824,827,830],{"class":84,"line":121},[82,806,807],{"class":134},"    \"slugs\"",[82,809,810],{"class":92},": { ",[82,812,813],{"class":134},"\"command\"",[82,815,816],{"class":92},": ",[82,818,147],{"class":99},[82,820,234],{"class":92},[82,822,823],{"class":134},"\"args\"",[82,825,826],{"class":92},": [",[82,828,829],{"class":99},"\"mcp\"",[82,831,832],{"class":92},"] }\n",[82,834,835],{"class":84,"line":128},[82,836,837],{"class":92},"  }\n",[82,839,840],{"class":84,"line":141},[82,841,842],{"class":92},"}\n",[59,844,845,846,849,850,852,853,856,857,213],{},"One binary, two jobs. The server serves ",[63,847,848],{},"tools"," only, your own ",[63,851,662],{}," stay on the command line. It needs ",[63,854,855],{},"@modelcontextprotocol\u002Fserver"," installed, and loads it only when someone actually runs ",[63,858,718],{},[59,860,861,862,865,866,869,870,873,874,876,877,880],{},"Progress is the one thing that differs. A plain command has nowhere to show it, so ",[63,863,864],{},"progress"," is ",[63,867,868],{},"undefined"," there and your ",[63,871,872],{},"progress?.()"," calls stay quiet. Through ",[63,875,764],{}," it's a real MCP server, so a client that sends a ",[63,878,879],{},"progressToken"," gets every line.",[882,883,884],"style",{},"html pre.shiki code .skH_V, html code.shiki .skH_V{--shiki-light:var(--shiki-token-keyword);--shiki-default:var(--shiki-token-keyword);--shiki-dark:var(--shiki-token-keyword)}html pre.shiki code .s38Sx, html code.shiki .s38Sx{--shiki-light:var(--ui-text-highlighted);--shiki-default:var(--ui-text-highlighted);--shiki-dark:var(--ui-text-highlighted)}html pre.shiki code .shU9J, html code.shiki .shU9J{--shiki-light:var(--shiki-token-string);--shiki-default:var(--shiki-token-string);--shiki-dark:var(--shiki-token-string)}html pre.shiki code .sK71F, html code.shiki .sK71F{--shiki-light:var(--shiki-token-function);--shiki-default:var(--shiki-token-function);--shiki-dark:var(--shiki-token-function)}html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .scIB-, html code.shiki .scIB-{--shiki-light:var(--shiki-token-comment);--shiki-default:var(--shiki-token-comment);--shiki-dark:var(--shiki-token-comment)}",{"title":78,"searchDepth":106,"depth":106,"links":886},[887,895,896,899],{"id":217,"depth":106,"text":218,"children":888},[889,890,891,892,893,894],{"id":222,"depth":121,"text":223},{"id":259,"depth":121,"text":260},{"id":331,"depth":121,"text":332},{"id":437,"depth":121,"text":438},{"id":515,"depth":121,"text":516},{"id":580,"depth":121,"text":581},{"id":603,"depth":106,"text":604},{"id":647,"depth":106,"text":648,"children":897},[898],{"id":722,"depth":121,"text":723},{"id":768,"depth":106,"text":769},"Turn the same tool list into a command line. Flags come from the schema and MCP rides along","md",null,{"icon":904},"i-lucide-square-terminal",{"title":20},{"title":907,"description":900},"A command line from the tools you already have","F0-PmQuPGX27Oe36wme1oi62UPHl13NN7O9GEfT5hf4",[910,912],{"title":16,"path":17,"stem":18,"description":911,"children":-1},"How the core checks every call and how each host reports a failure. And why error text never gets a second line",{"title":24,"path":25,"stem":26,"description":913,"children":-1},"Take a package that declares its tools four times and bring it down to one definition. Step by step with the traps marked",1791329826514]