@ouro.bot/cli 0.1.0-alpha.362 → 0.1.0-alpha.364

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 CHANGED
@@ -169,6 +169,7 @@ ouro auth --agent <name>
169
169
  ouro auth --agent <name> --provider <provider>
170
170
  ouro use --agent <name> --lane <outward|inner> --provider <provider> --model <model>
171
171
  ouro hatch
172
+ ouro clone <remote> [--agent <name>] # clone an existing agent from a git remote (see docs/cross-machine-setup.md)
172
173
  ouro chat <agent>
173
174
  ouro msg --to <agent> [--session <id>] [--task <ref>] <message>
174
175
  ouro poke <agent> --task <task-id>
@@ -183,6 +184,10 @@ ouro mcp-serve --agent <name> # start MCP server on stdin/stdout (us
183
184
  ouro hook <event> --agent <name> # fire a lifecycle hook (SessionStart, Stop, PostToolUse)
184
185
  ```
185
186
 
187
+ ## Setting Up On Another Machine
188
+
189
+ To clone an existing agent onto a new machine (macOS, Linux, or Windows via WSL2), see **[docs/cross-machine-setup.md](docs/cross-machine-setup.md)**. The short version: `npx ouro.bot`, pick "clone", enter the bundle's git remote URL, and follow the guided prompts (auth, daemon start, dev tool setup are all offered inline).
190
+
186
191
  ## The Agent's Inner Life
187
192
 
188
193
  Agents in Ouroboros aren't just responders — they have an autonomous inner life.
package/changelog.json CHANGED
@@ -1,6 +1,20 @@
1
1
  {
2
2
  "_note": "This changelog is maintained as part of the PR/version-bump workflow. Agent-curated, not auto-generated. Agents read this file directly via read_file to understand what changed between versions.",
3
3
  "versions": [
4
+ {
5
+ "version": "0.1.0-alpha.364",
6
+ "changes": [
7
+ "Cross-machine polish: bash PATH writes to .bashrc on Linux/WSL instead of .bash_profile (which non-login shells skip on Debian/Ubuntu). Shell hint message matches.",
8
+ "Agent prompt: never guess about harness behavior — consult docs first, investigate in code, fix stale docs via PR.",
9
+ "Agent prompt: harness docs pointer distinguishes dev mode (local read) vs production (fetch from GitHub)."
10
+ ]
11
+ },
12
+ {
13
+ "version": "0.1.0-alpha.363",
14
+ "changes": [
15
+ "Bootstrap first-install PATH hint is now shell-aware: shows correct source command for zsh, bash, fish, or generic fallback for unknown shells."
16
+ ]
17
+ },
4
18
  {
5
19
  "version": "0.1.0-alpha.362",
6
20
  "changes": [
@@ -492,8 +492,9 @@ async function checkManualCloneBundles(deps) {
492
492
  message: "bundle appears to be a manually cloned git repo",
493
493
  meta: { agent: agentDir, remote: remoteName },
494
494
  });
495
- const answer = await deps.promptInput(`Bundle ${agentDir} appears to be a git clone with a remote. Enable sync? (y/n): `);
496
- if (answer.trim().toLowerCase() === "y") {
495
+ /* v8 ignore next -- ?? fallback: promptInput always returns string in practice @preserve */
496
+ const answer = (await deps.promptInput(`Bundle ${agentDir} appears to be a git clone with a remote. Enable sync? (y/n): `)) ?? "";
497
+ if (answer.trim().toLowerCase() === "y" && fs.existsSync(agentJsonPath)) {
497
498
  const raw = fs.readFileSync(agentJsonPath, "utf-8");
498
499
  const config = JSON.parse(raw);
499
500
  config.sync = { enabled: true, remote: remoteName };
@@ -1353,10 +1354,15 @@ async function runOuroCli(args, deps = (0, cli_defaults_1.createDefaultOuroCliDe
1353
1354
  message: "user chose clone in first-run flow",
1354
1355
  meta: {},
1355
1356
  });
1356
- const remote = await deps.promptInput("Enter the git remote URL for the agent bundle: ");
1357
- // Run clone execution path
1358
- const cloneCommand = { kind: "clone", remote: remote.trim() };
1359
- return await runOuroCli(["clone", cloneCommand.remote], deps);
1357
+ /* v8 ignore next -- ?? fallback: promptInput always returns string in practice @preserve */
1358
+ const remote = (await deps.promptInput("Enter the git remote URL for the agent bundle: "))?.trim() ?? "";
1359
+ if (!remote) {
1360
+ deps.writeStdout("no remote URL provided — skipping clone");
1361
+ // Fall through to hatch flow
1362
+ }
1363
+ else {
1364
+ return await runOuroCli(["clone", remote], deps);
1365
+ }
1360
1366
  }
1361
1367
  (0, runtime_1.emitNervesEvent)({
1362
1368
  component: "daemon",
@@ -2787,10 +2793,18 @@ async function runOuroCli(args, deps = (0, cli_defaults_1.createDefaultOuroCliDe
2787
2793
  try {
2788
2794
  (0, child_process_1.execFileSync)("git", ["ls-remote", "--exit-code", command.remote], { stdio: "pipe", timeout: 15000 });
2789
2795
  }
2790
- catch {
2791
- const message = `could not reach remote: ${command.remote}\nCheck the URL and your network connection.`;
2792
- deps.writeStdout(message);
2793
- return message;
2796
+ catch (lsErr) {
2797
+ const stderr = lsErr?.stderr?.toString() ?? "";
2798
+ const isAuth = stderr.includes("Authentication failed")
2799
+ || stderr.includes("could not read Username")
2800
+ || stderr.includes("terminal prompts disabled")
2801
+ || stderr.includes("403")
2802
+ || stderr.includes("401");
2803
+ const hint = isAuth
2804
+ ? `authentication failed for: ${command.remote}\nSet up credentials first:\n gh auth login (GitHub repos)\n git config credential.helper store (other hosts)`
2805
+ : `could not reach remote: ${command.remote}\nCheck the URL and your network connection.`;
2806
+ deps.writeStdout(hint);
2807
+ return hint;
2794
2808
  }
2795
2809
  // 5. Clone
2796
2810
  (0, runtime_1.emitNervesEvent)({ component: "daemon", event: "daemon.clone_git_clone", message: "cloning agent bundle", meta: { remote: command.remote, targetPath } });
@@ -2800,18 +2814,79 @@ async function runOuroCli(args, deps = (0, cli_defaults_1.createDefaultOuroCliDe
2800
2814
  (0, runtime_1.emitNervesEvent)({ component: "daemon", event: "daemon.clone_identity_created", message: "machine identity created", meta: {} });
2801
2815
  // 7. Enable sync in agent.json
2802
2816
  const agentJsonPath = path.join(targetPath, "agent.json");
2817
+ let syncEnabled = false;
2803
2818
  if (fs.existsSync(agentJsonPath)) {
2804
2819
  const raw = fs.readFileSync(agentJsonPath, "utf-8");
2805
2820
  const config = JSON.parse(raw);
2806
2821
  config.sync = { enabled: true, remote: "origin" };
2807
2822
  fs.writeFileSync(agentJsonPath, JSON.stringify(config, null, 2) + "\n");
2823
+ syncEnabled = true;
2808
2824
  (0, runtime_1.emitNervesEvent)({ component: "daemon", event: "daemon.clone_sync_enabled", message: "sync enabled in agent.json", meta: { agentName } });
2809
2825
  }
2826
+ else {
2827
+ (0, runtime_1.emitNervesEvent)({ level: "warn", component: "daemon", event: "daemon.clone_no_agent_json", message: "cloned repo has no agent.json — may not be a valid bundle", meta: { agentName, targetPath } });
2828
+ }
2810
2829
  // 8. Output success message
2811
2830
  (0, runtime_1.emitNervesEvent)({ component: "daemon", event: "daemon.clone_complete", message: "clone complete", meta: { agentName, targetPath } });
2812
- const message = `cloned ${agentName} to ${targetPath}\nsync enabled (remote: origin)\nnext steps:\n ouro auth run --agent ${agentName}`;
2813
- deps.writeStdout(message);
2814
- return message;
2831
+ const syncMsg = syncEnabled ? "\nsync enabled (remote: origin)" : "\nwarning: no agent.json found — this may not be a valid agent bundle";
2832
+ deps.writeStdout(`cloned ${agentName} to ${targetPath}${syncMsg}`);
2833
+ // 9. Guided post-clone flow (when interactive)
2834
+ if (deps.promptInput) {
2835
+ // Auth
2836
+ const authAnswer = await deps.promptInput(`\nSet up provider auth now? (y/n): `) ?? "";
2837
+ if (authAnswer.trim().toLowerCase() === "y") {
2838
+ (0, runtime_1.emitNervesEvent)({ component: "daemon", event: "daemon.clone_chain_auth", message: "chaining auth from clone flow", meta: { agentName } });
2839
+ try {
2840
+ await runOuroCli(["auth", "--agent", agentName], deps);
2841
+ /* v8 ignore start -- chained command failures: tested via interactive clone test, catch branches are defensive @preserve */
2842
+ }
2843
+ catch (e) {
2844
+ deps.writeStdout(`auth setup failed: ${e instanceof Error ? e.message : String(e)}\nYou can retry later with: ouro auth run --agent ${agentName}`);
2845
+ }
2846
+ /* v8 ignore stop */
2847
+ }
2848
+ // Daemon
2849
+ const upAnswer = await deps.promptInput(`\nStart the daemon now? (y/n): `) ?? "";
2850
+ if (upAnswer.trim().toLowerCase() === "y") {
2851
+ (0, runtime_1.emitNervesEvent)({ component: "daemon", event: "daemon.clone_chain_up", message: "chaining daemon start from clone flow", meta: { agentName } });
2852
+ try {
2853
+ await runOuroCli(["up"], deps);
2854
+ /* v8 ignore start -- chained command failures: defensive catch @preserve */
2855
+ }
2856
+ catch (e) {
2857
+ deps.writeStdout(`daemon start failed: ${e instanceof Error ? e.message : String(e)}\nYou can retry later with: ouro up`);
2858
+ }
2859
+ /* v8 ignore stop */
2860
+ }
2861
+ // Dev tool setup
2862
+ const setupAnswer = await deps.promptInput(`\nSet up Claude Code integration? (y/n): `) ?? "";
2863
+ if (setupAnswer.trim().toLowerCase() === "y") {
2864
+ (0, runtime_1.emitNervesEvent)({ component: "daemon", event: "daemon.clone_chain_setup", message: "chaining dev tool setup from clone flow", meta: { agentName } });
2865
+ try {
2866
+ await runOuroCli(["setup", "--tool", "claude-code", "--agent", agentName], deps);
2867
+ /* v8 ignore start -- chained command failures: defensive catch @preserve */
2868
+ }
2869
+ catch (e) {
2870
+ deps.writeStdout(`dev tool setup failed: ${e instanceof Error ? e.message : String(e)}\nYou can retry later with: ouro setup --tool claude-code --agent ${agentName}`);
2871
+ }
2872
+ /* v8 ignore stop */
2873
+ }
2874
+ }
2875
+ else {
2876
+ deps.writeStdout(`\nnext steps:\n ouro auth run --agent ${agentName}\n ouro up\n ouro setup --tool claude-code --agent ${agentName}`);
2877
+ }
2878
+ /* v8 ignore start -- PATH hint: only fires inside npx, not testable in vitest @preserve */
2879
+ if (process.env.npm_execpath) {
2880
+ const shell = process.env.SHELL ? path.basename(process.env.SHELL) : "";
2881
+ const bashProfile = process.platform === "darwin" ? "~/.bash_profile" : "~/.bashrc";
2882
+ const sourceCmd = shell === "zsh" ? "source ~/.zshrc"
2883
+ : shell === "bash" ? `source ${bashProfile}`
2884
+ : shell === "fish" ? "source ~/.config/fish/config.fish"
2885
+ : "restart your shell";
2886
+ deps.writeStdout(`\ntip: if 'ouro' is not found, run: ${sourceCmd}`);
2887
+ }
2888
+ /* v8 ignore stop */
2889
+ return `clone complete: ${agentName}`;
2815
2890
  }
2816
2891
  const daemonCommand = toDaemonCommand(command);
2817
2892
  let response;
@@ -63,16 +63,21 @@ function writeWrapperScript(scriptPath, mkdirSync, writeFileSync, chmodSync) {
63
63
  writeFileSync(scriptPath, WRAPPER_SCRIPT, { mode: 0o755 });
64
64
  chmodSync(scriptPath, 0o755);
65
65
  }
66
- function detectShellProfile(homeDir, shell) {
66
+ function detectShellProfile(homeDir, shell, platform) {
67
67
  if (!shell)
68
68
  return null;
69
69
  const base = path.basename(shell);
70
70
  if (base === "zsh")
71
71
  return path.join(homeDir, ".zshrc");
72
72
  if (base === "bash") {
73
- // macOS uses .bash_profile, Linux uses .bashrc
74
- const profilePath = path.join(homeDir, ".bash_profile");
75
- return profilePath;
73
+ // macOS uses .bash_profile; Linux/WSL uses .bashrc (the default
74
+ // interactive shell config on Debian/Ubuntu). Writing to .bash_profile
75
+ // on Linux often has no effect because non-login shells skip it.
76
+ /* v8 ignore next -- ?? fallback: callers always pass platform from deps @preserve */
77
+ const effectivePlatform = platform ?? process.platform;
78
+ return effectivePlatform === "darwin"
79
+ ? path.join(homeDir, ".bash_profile")
80
+ : path.join(homeDir, ".bashrc");
76
81
  }
77
82
  if (base === "fish")
78
83
  return path.join(homeDir, ".config", "fish", "config.fish");
@@ -250,7 +255,7 @@ function installOuroCommand(deps = {}) {
250
255
  const pathReady = isBinDirInPath(binDir, envPath);
251
256
  let shellProfileUpdated = null;
252
257
  if (!pathReady) {
253
- const profilePath = detectShellProfile(homeDir, shell);
258
+ const profilePath = detectShellProfile(homeDir, shell, platform);
254
259
  if (profilePath) {
255
260
  try {
256
261
  let existing = "";
@@ -382,6 +382,7 @@ function runtimeInfoSection(channel, options) {
382
382
  lines.push(`process type: ${processTypeLabel(channel)}`);
383
383
  lines.push(`daemon: ${daemonStatus(options?.daemonRunning)}`);
384
384
  lines.push(`mcp serve: i can expose my tools to dev tools via \`ouro mcp-serve\`. see the configure-dev-tools skill for setup.`);
385
+ lines.push(`harness docs: the harness repo has docs/ and skills/ with guides for setup, operations, and capabilities. docs/ does NOT ship in the npm package — in production, fetch from https://github.com/ouroborosbot/ouroboros/tree/main/docs instead. in dev mode, read from ${sourceRoot}/docs/. when someone asks about setup, installation, cross-machine cloning, deployment, testing, auth, or how i work — consult the docs first. NEVER guess about how the harness works. if the docs don't answer the question, investigate in code. if i discover the docs are stale or missing coverage, open a PR to fix them — stale docs cause the same damage as wrong answers.`);
385
386
  if (channel === "cli") {
386
387
  lines.push("i introduce myself on boot with a fun random greeting.");
387
388
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ouro.bot/cli",
3
- "version": "0.1.0-alpha.362",
3
+ "version": "0.1.0-alpha.364",
4
4
  "main": "dist/heart/daemon/ouro-entry.js",
5
5
  "bin": {
6
6
  "cli": "dist/heart/daemon/ouro-bot-entry.js",
@@ -1,6 +1,6 @@
1
1
  # Configure Dev Tools for MCP Agent Bridge
2
2
 
3
- Set up your development tools (Claude Code, Codex) to communicate with Ouroboros agents via MCP. One command does everything.
3
+ Set up your development tools (Claude Code, Codex) to communicate with Ouroboros agents via MCP. One command does everything — including cross-platform WSL2 bridging on Windows.
4
4
 
5
5
  ## Setup
6
6
 
@@ -15,6 +15,16 @@ This command:
15
15
  2. Configures lifecycle hooks (SessionStart, Stop, PostToolUse) for passive awareness
16
16
  3. Detects dev vs installed mode automatically and uses the correct command path
17
17
 
18
+ **On WSL2 (Windows):** The command automatically detects the WSL environment and:
19
+ - Calls `claude.exe` (the Windows binary) instead of `claude`
20
+ - Prefixes MCP serve and hook commands with `wsl` so Windows-side Claude Code spawns them through WSL
21
+ - Resolves the Windows-side home directory and writes config to the Windows-side `~/.claude/`
22
+ - After setup, open Claude Code in PowerShell — the agent is there
23
+
24
+ **On native Windows (no WSL):** Not yet supported. The command prints a message directing you to install WSL2.
25
+
26
+ For the full cross-machine setup flow (including cloning an agent to a new machine), see `docs/cross-machine-setup.md` in the harness repo.
27
+
18
28
  ### Codex
19
29
 
20
30
  ```bash
@@ -73,6 +83,16 @@ Most read-only tools work without the daemon (reads filesystem directly). For wr
73
83
  - Ensure `dist/` is built: `npm run build`
74
84
  - Check that the entry point path is correct (setup auto-detects this)
75
85
 
86
+ ### WSL2-specific issues
87
+
88
+ **`claude.exe` not found** — Windows executables must be accessible from WSL. This is the default, but enterprise environments may disable it via `/etc/wsl.conf` setting `appendWindowsPath = false`. Check with `which claude.exe`. If missing, add Claude Code's install directory to WSL's PATH manually or update `wsl.conf`.
89
+
90
+ **`cmd.exe` or `wslpath` fails** — The setup command resolves the Windows home directory using `cmd.exe /C echo %USERPROFILE%` piped through `wslpath`. If either is unavailable, the setup will fail. `wslpath` ships with all standard WSL distributions. `cmd.exe` requires Windows executables to be on PATH (see above).
91
+
92
+ **MCP server hangs or returns empty** — The MCP server runs inside WSL via `wsl ouro mcp-serve --agent <name>`. If stdio piping between Windows and WSL is broken, check that the WSL distribution is running (`wsl --status`) and that no other process has claimed stdin.
93
+
94
+ **Hooks not firing** — Claude Code hooks use `wsl ouro hook <event> --agent <name>`. If hooks fail silently, check that `ouro` is on PATH inside WSL (run `wsl ouro --version` from PowerShell to verify).
95
+
76
96
  ### Removing
77
97
  ```bash
78
98
  claude mcp remove ouro-<agent-name>