@ouro.bot/cli 0.1.0-alpha.15 → 0.1.0-alpha.151

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (150) hide show
  1. package/AdoptionSpecialist.ouro/psyche/SOUL.md +2 -2
  2. package/AdoptionSpecialist.ouro/psyche/identities/monty.md +2 -2
  3. package/README.md +185 -191
  4. package/changelog.json +991 -0
  5. package/dist/heart/active-work.js +633 -0
  6. package/dist/heart/bridges/manager.js +358 -0
  7. package/dist/heart/bridges/state-machine.js +135 -0
  8. package/dist/heart/bridges/store.js +123 -0
  9. package/dist/heart/commitments.js +105 -0
  10. package/dist/heart/config.js +66 -21
  11. package/dist/heart/core.js +645 -112
  12. package/dist/heart/cross-chat-delivery.js +146 -0
  13. package/dist/heart/daemon/agent-discovery.js +81 -0
  14. package/dist/heart/daemon/agent-service.js +360 -0
  15. package/dist/heart/daemon/auth-flow.js +457 -0
  16. package/dist/heart/daemon/cadence.js +70 -0
  17. package/dist/heart/daemon/daemon-cli.js +2301 -205
  18. package/dist/heart/daemon/daemon-entry.js +131 -4
  19. package/dist/heart/daemon/daemon-runtime-sync.js +212 -0
  20. package/dist/heart/daemon/daemon-tombstone.js +106 -0
  21. package/dist/heart/daemon/daemon.js +372 -3
  22. package/dist/heart/daemon/habit-migration.js +181 -0
  23. package/dist/heart/daemon/habit-parser.js +119 -0
  24. package/dist/heart/daemon/habit-scheduler.js +252 -0
  25. package/dist/heart/daemon/hatch-animation.js +10 -3
  26. package/dist/heart/daemon/hatch-flow.js +28 -111
  27. package/dist/heart/daemon/hooks/bundle-meta.js +92 -0
  28. package/dist/heart/daemon/inner-status.js +89 -0
  29. package/dist/heart/daemon/launchd.js +159 -0
  30. package/dist/heart/daemon/log-tailer.js +4 -3
  31. package/dist/heart/daemon/mcp-server.js +591 -0
  32. package/dist/heart/daemon/message-router.js +17 -8
  33. package/dist/heart/daemon/os-cron-deps.js +134 -0
  34. package/dist/heart/daemon/ouro-bot-global-installer.js +128 -0
  35. package/dist/heart/daemon/ouro-path-installer.js +78 -34
  36. package/dist/heart/daemon/ouro-version-manager.js +171 -0
  37. package/dist/heart/daemon/process-manager.js +75 -0
  38. package/dist/heart/daemon/run-hooks.js +37 -0
  39. package/dist/heart/daemon/runtime-logging.js +58 -15
  40. package/dist/heart/daemon/runtime-metadata.js +219 -0
  41. package/dist/heart/daemon/runtime-mode.js +67 -0
  42. package/dist/heart/daemon/sense-manager.js +50 -2
  43. package/dist/heart/daemon/session-id-resolver.js +131 -0
  44. package/dist/heart/daemon/skill-management-installer.js +94 -0
  45. package/dist/heart/daemon/socket-client.js +202 -0
  46. package/dist/heart/daemon/specialist-orchestrator.js +2 -2
  47. package/dist/heart/daemon/specialist-prompt.js +10 -7
  48. package/dist/heart/daemon/specialist-tools.js +53 -4
  49. package/dist/heart/daemon/staged-restart.js +114 -0
  50. package/dist/heart/daemon/task-scheduler.js +3 -25
  51. package/dist/heart/daemon/thoughts.js +507 -0
  52. package/dist/heart/daemon/update-checker.js +111 -0
  53. package/dist/heart/daemon/update-hooks.js +138 -0
  54. package/dist/heart/daemon/wrapper-publish-guard.js +86 -0
  55. package/dist/heart/delegation.js +62 -0
  56. package/dist/heart/identity.js +64 -21
  57. package/dist/heart/kicks.js +2 -20
  58. package/dist/heart/model-capabilities.js +48 -0
  59. package/dist/heart/obligations.js +197 -0
  60. package/dist/heart/progress-story.js +42 -0
  61. package/dist/heart/provider-failover.js +88 -0
  62. package/dist/heart/provider-ping.js +159 -0
  63. package/dist/heart/providers/anthropic-token.js +163 -0
  64. package/dist/heart/providers/anthropic.js +195 -34
  65. package/dist/heart/providers/azure.js +115 -9
  66. package/dist/heart/providers/github-copilot.js +157 -0
  67. package/dist/heart/providers/minimax.js +33 -3
  68. package/dist/heart/providers/openai-codex.js +49 -14
  69. package/dist/heart/session-activity.js +173 -0
  70. package/dist/heart/session-recall.js +216 -0
  71. package/dist/heart/streaming.js +117 -33
  72. package/dist/heart/target-resolution.js +127 -0
  73. package/dist/heart/tool-loop.js +200 -0
  74. package/dist/heart/turn-coordinator.js +28 -0
  75. package/dist/mind/associative-recall.js +122 -32
  76. package/dist/mind/bundle-manifest.js +14 -0
  77. package/dist/mind/context.js +125 -14
  78. package/dist/mind/{memory.js → diary.js} +70 -56
  79. package/dist/mind/first-impressions.js +16 -2
  80. package/dist/mind/friends/channel.js +44 -0
  81. package/dist/mind/friends/group-context.js +144 -0
  82. package/dist/mind/friends/store-file.js +19 -0
  83. package/dist/mind/friends/trust-explanation.js +74 -0
  84. package/dist/mind/friends/types.js +8 -0
  85. package/dist/mind/journal-index.js +161 -0
  86. package/dist/mind/obligation-steering.js +221 -0
  87. package/dist/mind/obligations.js +134 -0
  88. package/dist/mind/pending.js +76 -9
  89. package/dist/mind/phrases.js +1 -0
  90. package/dist/mind/prompt.js +612 -88
  91. package/dist/mind/token-estimate.js +8 -12
  92. package/dist/nerves/cli-logging.js +15 -2
  93. package/dist/nerves/coverage/run-artifacts.js +1 -1
  94. package/dist/nerves/index.js +59 -2
  95. package/dist/nerves/runtime.js +5 -1
  96. package/dist/repertoire/ado-client.js +4 -2
  97. package/dist/repertoire/ado-semantic.js +6 -0
  98. package/dist/repertoire/coding/codex-jsonl.js +64 -0
  99. package/dist/repertoire/coding/context-pack.js +254 -0
  100. package/dist/repertoire/coding/feedback.js +301 -0
  101. package/dist/repertoire/coding/index.js +4 -1
  102. package/dist/repertoire/coding/manager.js +210 -4
  103. package/dist/repertoire/coding/spawner.js +58 -12
  104. package/dist/repertoire/coding/tools.js +176 -4
  105. package/dist/repertoire/data/ado-endpoints.json +188 -0
  106. package/dist/repertoire/guardrails.js +290 -0
  107. package/dist/repertoire/mcp-client.js +254 -0
  108. package/dist/repertoire/mcp-manager.js +198 -0
  109. package/dist/repertoire/skills.js +14 -23
  110. package/dist/repertoire/tasks/board.js +12 -0
  111. package/dist/repertoire/tasks/index.js +23 -9
  112. package/dist/repertoire/tasks/parser.js +0 -2
  113. package/dist/repertoire/tasks/transitions.js +1 -20
  114. package/dist/repertoire/tools-base.js +990 -273
  115. package/dist/repertoire/tools-bluebubbles.js +94 -0
  116. package/dist/repertoire/tools-github.js +1 -6
  117. package/dist/repertoire/tools-teams.js +65 -59
  118. package/dist/repertoire/tools.js +281 -91
  119. package/dist/scripts/claude-code-hook.js +41 -0
  120. package/dist/scripts/claude-code-stop-hook.js +47 -0
  121. package/dist/senses/attention-queue.js +97 -0
  122. package/dist/senses/bluebubbles-client.js +258 -5
  123. package/dist/senses/bluebubbles-entry.js +2 -0
  124. package/dist/senses/bluebubbles-inbound-log.js +109 -0
  125. package/dist/senses/bluebubbles-media.js +339 -0
  126. package/dist/senses/bluebubbles-model.js +12 -4
  127. package/dist/senses/bluebubbles-mutation-log.js +45 -5
  128. package/dist/senses/bluebubbles-runtime-state.js +109 -0
  129. package/dist/senses/bluebubbles-session-cleanup.js +72 -0
  130. package/dist/senses/bluebubbles.js +981 -45
  131. package/dist/senses/cli-entry.js +1 -1
  132. package/dist/senses/cli-layout.js +187 -0
  133. package/dist/senses/cli.js +374 -131
  134. package/dist/senses/contextual-heartbeat.js +106 -0
  135. package/dist/senses/continuity.js +94 -0
  136. package/dist/senses/debug-activity.js +154 -0
  137. package/dist/senses/inner-dialog-worker.js +112 -19
  138. package/dist/senses/inner-dialog.js +621 -84
  139. package/dist/senses/pipeline.js +463 -0
  140. package/dist/senses/shared-turn.js +196 -0
  141. package/dist/senses/surface-tool.js +95 -0
  142. package/dist/senses/teams.js +796 -141
  143. package/dist/senses/trust-gate.js +112 -2
  144. package/package.json +12 -4
  145. package/skills/configure-dev-tools.md +81 -0
  146. package/subagents/README.md +4 -70
  147. package/dist/heart/daemon/subagent-installer.js +0 -134
  148. package/subagents/work-doer.md +0 -233
  149. package/subagents/work-merger.md +0 -624
  150. package/subagents/work-planner.md +0 -373
@@ -0,0 +1,134 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.createRealOsCronDeps = createRealOsCronDeps;
37
+ exports.createRealCrontabDeps = createRealCrontabDeps;
38
+ exports.resolveOuroBinaryPath = resolveOuroBinaryPath;
39
+ const child_process_1 = require("child_process");
40
+ const fs = __importStar(require("fs"));
41
+ const os = __importStar(require("os"));
42
+ const path = __importStar(require("path"));
43
+ const runtime_1 = require("../../nerves/runtime");
44
+ function createRealOsCronDeps() {
45
+ (0, runtime_1.emitNervesEvent)({
46
+ component: "daemon",
47
+ event: "daemon.os_cron_deps_created",
48
+ message: "created real OS cron deps",
49
+ meta: { platform: process.platform },
50
+ });
51
+ return {
52
+ exec: (cmd) => {
53
+ try {
54
+ (0, child_process_1.execSync)(cmd, { stdio: "ignore" });
55
+ }
56
+ catch {
57
+ /* best effort */
58
+ }
59
+ },
60
+ writeFile: (p, c) => fs.writeFileSync(p, c, "utf-8"),
61
+ removeFile: (p) => {
62
+ try {
63
+ fs.unlinkSync(p);
64
+ }
65
+ catch {
66
+ /* best effort — file may already be gone */
67
+ }
68
+ },
69
+ existsFile: (p) => fs.existsSync(p),
70
+ listDir: (dir) => {
71
+ try {
72
+ return fs.readdirSync(dir);
73
+ }
74
+ catch {
75
+ return [];
76
+ }
77
+ },
78
+ mkdirp: (dir) => fs.mkdirSync(dir, { recursive: true }),
79
+ homeDir: os.homedir(),
80
+ };
81
+ }
82
+ function createRealCrontabDeps() {
83
+ (0, runtime_1.emitNervesEvent)({
84
+ component: "daemon",
85
+ event: "daemon.crontab_deps_created",
86
+ message: "created real crontab deps",
87
+ meta: {},
88
+ });
89
+ /* v8 ignore start -- crontab exec closures: calling these in tests would modify the real system crontab @preserve */
90
+ return {
91
+ execOutput: (cmd) => (0, child_process_1.execSync)(cmd, { encoding: "utf-8" }),
92
+ execWrite: (cmd, stdin) => {
93
+ (0, child_process_1.execSync)(cmd, { input: stdin, stdio: ["pipe", "ignore", "ignore"] });
94
+ },
95
+ };
96
+ /* v8 ignore stop */
97
+ }
98
+ /* v8 ignore start -- ouro path resolution: probes process.argv, filesystem layout, and PATH; branches depend on install method and runtime environment @preserve */
99
+ function resolveOuroBinaryPath() {
100
+ // Try to resolve from process.argv[1] — the script being run
101
+ const scriptPath = process.argv[1];
102
+ if (scriptPath) {
103
+ // If running via node dist/heart/daemon/daemon-entry.js, resolve the ouro wrapper
104
+ // The ouro binary is typically at the package root's bin
105
+ const distDir = path.resolve(path.dirname(scriptPath));
106
+ const packageBin = path.resolve(distDir, "..", "..", "..", "node_modules", ".bin", "ouro");
107
+ if (fs.existsSync(packageBin)) {
108
+ return packageBin;
109
+ }
110
+ // Try the repo-local scripts/ouro.sh
111
+ const repoOuro = path.resolve(distDir, "..", "..", "..", "scripts", "ouro.sh");
112
+ if (fs.existsSync(repoOuro)) {
113
+ return repoOuro;
114
+ }
115
+ }
116
+ // Try which ouro
117
+ try {
118
+ const result = (0, child_process_1.execSync)("which ouro", { encoding: "utf-8" }).trim();
119
+ if (result.length > 0)
120
+ return result;
121
+ }
122
+ catch {
123
+ /* not on PATH */
124
+ }
125
+ // Fallback: use "ouro" and rely on PATH
126
+ (0, runtime_1.emitNervesEvent)({
127
+ component: "daemon",
128
+ event: "daemon.ouro_path_fallback",
129
+ message: "could not resolve full ouro binary path, falling back to 'ouro'",
130
+ meta: {},
131
+ });
132
+ return "ouro";
133
+ }
134
+ /* v8 ignore stop */
@@ -0,0 +1,128 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.syncGlobalOuroBotWrapper = syncGlobalOuroBotWrapper;
37
+ const child_process_1 = require("child_process");
38
+ const fs = __importStar(require("fs"));
39
+ const path = __importStar(require("path"));
40
+ const runtime_1 = require("../../nerves/runtime");
41
+ const runtime_metadata_1 = require("./runtime-metadata");
42
+ function normalizeOutput(output) {
43
+ return (typeof output === "string" ? output : output.toString("utf-8")).trim();
44
+ }
45
+ function resolveGlobalPrefix(execFileSyncImpl) {
46
+ return normalizeOutput(execFileSyncImpl("npm", ["prefix", "-g"], { encoding: "utf-8" }));
47
+ }
48
+ function resolveGlobalRoot(execFileSyncImpl) {
49
+ return normalizeOutput(execFileSyncImpl("npm", ["root", "-g"], { encoding: "utf-8" }));
50
+ }
51
+ function readInstalledWrapperVersion(globalRoot, existsSyncImpl, readFileSyncImpl) {
52
+ const packageJsonPath = path.join(globalRoot, "ouro.bot", "package.json");
53
+ if (!existsSyncImpl(packageJsonPath))
54
+ return null;
55
+ try {
56
+ const parsed = JSON.parse(readFileSyncImpl(packageJsonPath, "utf-8"));
57
+ return typeof parsed.version === "string" && parsed.version.trim().length > 0 ? parsed.version.trim() : null;
58
+ }
59
+ catch {
60
+ return null;
61
+ }
62
+ }
63
+ function resolveExecutableOwner(globalPrefix, platform, existsSyncImpl, realpathSyncImpl) {
64
+ const binName = platform === "win32" ? "ouro.bot.cmd" : "ouro.bot";
65
+ const binPath = platform === "win32"
66
+ ? path.join(globalPrefix, binName)
67
+ : path.join(globalPrefix, "bin", binName);
68
+ if (!existsSyncImpl(binPath))
69
+ return null;
70
+ try {
71
+ const resolved = realpathSyncImpl(binPath);
72
+ if (resolved.includes(`${path.sep}node_modules${path.sep}ouro.bot${path.sep}`))
73
+ return "wrapper";
74
+ if (resolved.includes(`${path.sep}node_modules${path.sep}@ouro.bot${path.sep}cli${path.sep}`))
75
+ return "cli";
76
+ return "other";
77
+ }
78
+ catch {
79
+ return "unknown";
80
+ }
81
+ }
82
+ function syncGlobalOuroBotWrapper(deps = {}) {
83
+ /* v8 ignore start -- dependency-injection defaults are only exercised in the live runtime */
84
+ const execFileSyncImpl = deps.execFileSync ?? child_process_1.execFileSync;
85
+ const existsSyncImpl = deps.existsSync ?? fs.existsSync;
86
+ const readFileSyncImpl = deps.readFileSync ?? fs.readFileSync;
87
+ const realpathSyncImpl = deps.realpathSync ?? fs.realpathSync;
88
+ const runtimeVersion = deps.runtimeVersion ?? (0, runtime_metadata_1.getRuntimeMetadata)().version;
89
+ const platform = deps.platform ?? process.platform;
90
+ /* v8 ignore stop */
91
+ (0, runtime_1.emitNervesEvent)({
92
+ component: "daemon",
93
+ event: "daemon.ouro_bot_global_sync_start",
94
+ message: "checking global ouro.bot wrapper",
95
+ meta: { version: runtimeVersion },
96
+ });
97
+ const globalPrefix = resolveGlobalPrefix(execFileSyncImpl);
98
+ const globalRoot = resolveGlobalRoot(execFileSyncImpl);
99
+ const installedVersion = readInstalledWrapperVersion(globalRoot, existsSyncImpl, readFileSyncImpl);
100
+ const executableOwner = resolveExecutableOwner(globalPrefix, platform, existsSyncImpl, realpathSyncImpl);
101
+ if (executableOwner === "wrapper") {
102
+ (0, runtime_1.emitNervesEvent)({
103
+ component: "daemon",
104
+ event: "daemon.ouro_bot_global_sync_end",
105
+ message: "global ouro.bot wrapper already current",
106
+ meta: { version: runtimeVersion, installedVersion, executableOwner, installed: false },
107
+ });
108
+ return {
109
+ installed: false,
110
+ version: runtimeVersion,
111
+ installedVersion,
112
+ executableOwner,
113
+ };
114
+ }
115
+ execFileSyncImpl("npm", ["install", "-g", "--force", "ouro.bot@latest"], { stdio: "pipe", encoding: "utf-8" });
116
+ (0, runtime_1.emitNervesEvent)({
117
+ component: "daemon",
118
+ event: "daemon.ouro_bot_global_sync_end",
119
+ message: "global ouro.bot wrapper synced",
120
+ meta: { version: runtimeVersion, installedVersion, executableOwner, installed: true },
121
+ });
122
+ return {
123
+ installed: true,
124
+ version: runtimeVersion,
125
+ installedVersion,
126
+ executableOwner,
127
+ };
128
+ }
@@ -39,8 +39,29 @@ const os = __importStar(require("os"));
39
39
  const path = __importStar(require("path"));
40
40
  const runtime_1 = require("../../nerves/runtime");
41
41
  const WRAPPER_SCRIPT = `#!/bin/sh
42
- exec npx --yes ouro.bot "$@"
42
+ # Check for dev mode — if dev-config.json exists, dispatch to the dev repo
43
+ # Skip dev dispatch for "up" command (explicitly returns to production)
44
+ DEV_CONFIG="$HOME/.ouro-cli/dev-config.json"
45
+ if [ -f "$DEV_CONFIG" ] && [ "$1" != "up" ]; then
46
+ DEV_REPO=$(node -e "try{console.log(JSON.parse(require('fs').readFileSync('$DEV_CONFIG','utf-8')).repoPath)}catch{}" 2>/dev/null)
47
+ DEV_ENTRY="$DEV_REPO/dist/heart/daemon/ouro-entry.js"
48
+ if [ -n "$DEV_REPO" ] && [ -e "$DEV_ENTRY" ]; then
49
+ exec node "$DEV_ENTRY" "$@"
50
+ fi
51
+ fi
52
+ # Fall back to installed version
53
+ ENTRY="$HOME/.ouro-cli/CurrentVersion/node_modules/@ouro.bot/cli/dist/heart/daemon/ouro-entry.js"
54
+ if [ ! -e "$ENTRY" ]; then
55
+ echo "ouro not installed. Run: npx ouro.bot" >&2
56
+ exit 1
57
+ fi
58
+ exec node "$ENTRY" "$@"
43
59
  `;
60
+ function writeWrapperScript(scriptPath, mkdirSync, writeFileSync, chmodSync) {
61
+ mkdirSync(path.dirname(scriptPath), { recursive: true });
62
+ writeFileSync(scriptPath, WRAPPER_SCRIPT, { mode: 0o755 });
63
+ chmodSync(scriptPath, 0o755);
64
+ }
44
65
  function detectShellProfile(homeDir, shell) {
45
66
  if (!shell)
46
67
  return null;
@@ -66,6 +87,16 @@ function buildPathExportLine(binDir, shell) {
66
87
  }
67
88
  return `\n# Added by ouro\nexport PATH="${binDir}:$PATH"\n`;
68
89
  }
90
+ function isWrapperCurrent(scriptPath, existsSync, readFileSync) {
91
+ if (!existsSync(scriptPath))
92
+ return false;
93
+ try {
94
+ return readFileSync(scriptPath, "utf-8") === WRAPPER_SCRIPT;
95
+ }
96
+ catch {
97
+ return false;
98
+ }
99
+ }
69
100
  function installOuroCommand(deps = {}) {
70
101
  /* v8 ignore start -- dep defaults: only used in real runtime, tests always inject @preserve */
71
102
  const platform = deps.platform ?? process.platform;
@@ -86,46 +117,59 @@ function installOuroCommand(deps = {}) {
86
117
  message: "skipped ouro PATH install on Windows",
87
118
  meta: { platform },
88
119
  });
89
- return { installed: false, scriptPath: null, pathReady: false, shellProfileUpdated: null, skippedReason: "windows" };
120
+ return { installed: false, scriptPath: null, pathReady: false, shellProfileUpdated: null, skippedReason: "windows", repairedOldLauncher: false };
121
+ }
122
+ // Ensure ~/.ouro-cli/ directory layout exists
123
+ if (deps.ensureCliLayout) {
124
+ deps.ensureCliLayout();
90
125
  }
91
- const binDir = path.join(homeDir, ".local", "bin");
126
+ const binDir = path.join(homeDir, ".ouro-cli", "bin");
92
127
  const scriptPath = path.join(binDir, "ouro");
93
- (0, runtime_1.emitNervesEvent)({
94
- component: "daemon",
95
- event: "daemon.ouro_path_install_start",
96
- message: "installing ouro command to PATH",
97
- meta: { scriptPath, binDir },
98
- });
99
- // If ouro already exists, check content and repair if stale
100
- if (existsSync(scriptPath)) {
101
- let existingContent = "";
128
+ const oldScriptPath = path.join(homeDir, ".local", "bin", "ouro");
129
+ const modernCurrent = isWrapperCurrent(scriptPath, existsSync, readFileSync);
130
+ const oldExists = existsSync(oldScriptPath);
131
+ const oldCurrent = oldExists && isWrapperCurrent(oldScriptPath, existsSync, readFileSync);
132
+ // ── Repair old ~/.local/bin/ouro launcher ──
133
+ // If the old launcher exists with stale content it can shadow the modern
134
+ // path and cause the wrong CLI version to run. Overwrite it with the
135
+ // current wrapper so both paths resolve to ~/.ouro-cli/CurrentVersion.
136
+ let repairedOldLauncher = false;
137
+ if (oldExists && !oldCurrent) {
138
+ (0, runtime_1.emitNervesEvent)({
139
+ component: "daemon",
140
+ event: "daemon.ouro_path_repair_old",
141
+ message: "repairing stale old launcher at ~/.local/bin/ouro",
142
+ meta: { oldScriptPath },
143
+ });
102
144
  try {
103
- existingContent = readFileSync(scriptPath, "utf-8");
145
+ writeFileSync(oldScriptPath, WRAPPER_SCRIPT, { mode: 0o755 });
146
+ chmodSync(oldScriptPath, 0o755);
147
+ repairedOldLauncher = true;
104
148
  }
105
149
  catch {
106
- // Can't read — treat as stale, will overwrite below
107
- }
108
- if (existingContent === WRAPPER_SCRIPT) {
109
- (0, runtime_1.emitNervesEvent)({
110
- component: "daemon",
111
- event: "daemon.ouro_path_install_skip",
112
- message: "ouro command already installed",
113
- meta: { scriptPath },
114
- });
115
- return { installed: false, scriptPath, pathReady: isBinDirInPath(binDir, envPath), shellProfileUpdated: null, skippedReason: "already-installed" };
150
+ // Best effort — old launcher repair failure must not block modern install
116
151
  }
117
- // Content is stale — repair by overwriting
152
+ }
153
+ // ── Fast-path: modern wrapper already current ──
154
+ if (modernCurrent) {
118
155
  (0, runtime_1.emitNervesEvent)({
119
156
  component: "daemon",
120
- event: "daemon.ouro_path_install_repair",
121
- message: "repairing stale ouro wrapper script",
157
+ event: "daemon.ouro_path_install_skip",
158
+ message: "ouro command already installed",
122
159
  meta: { scriptPath },
123
160
  });
161
+ return { installed: false, scriptPath, pathReady: isBinDirInPath(binDir, envPath), shellProfileUpdated: null, skippedReason: "already-installed", repairedOldLauncher };
124
162
  }
163
+ (0, runtime_1.emitNervesEvent)({
164
+ component: "daemon",
165
+ event: "daemon.ouro_path_install_start",
166
+ message: existsSync(scriptPath) ? "repairing stale ouro wrapper script" : "installing ouro command to PATH",
167
+ meta: { scriptPath, binDir },
168
+ });
125
169
  try {
126
- mkdirSync(binDir, { recursive: true });
127
- writeFileSync(scriptPath, WRAPPER_SCRIPT, { mode: 0o755 });
128
- chmodSync(scriptPath, 0o755);
170
+ if (!modernCurrent) {
171
+ writeWrapperScript(scriptPath, mkdirSync, writeFileSync, chmodSync);
172
+ }
129
173
  }
130
174
  catch (error) {
131
175
  (0, runtime_1.emitNervesEvent)({
@@ -135,11 +179,11 @@ function installOuroCommand(deps = {}) {
135
179
  message: "failed to install ouro command",
136
180
  meta: { error: error instanceof Error ? error.message : /* v8 ignore next -- defensive: non-Error catch branch @preserve */ String(error) },
137
181
  });
138
- return { installed: false, scriptPath: null, pathReady: false, shellProfileUpdated: null, skippedReason: error instanceof Error ? error.message : /* v8 ignore next -- defensive @preserve */ String(error) };
182
+ return { installed: false, scriptPath: null, pathReady: false, shellProfileUpdated: null, skippedReason: error instanceof Error ? error.message : /* v8 ignore next -- defensive @preserve */ String(error), repairedOldLauncher };
139
183
  }
140
- // Check if ~/.local/bin is already in PATH
141
- let shellProfileUpdated = null;
184
+ // Check if ~/.ouro-cli/bin is already in PATH
142
185
  const pathReady = isBinDirInPath(binDir, envPath);
186
+ let shellProfileUpdated = null;
143
187
  if (!pathReady) {
144
188
  const profilePath = detectShellProfile(homeDir, shell);
145
189
  if (profilePath) {
@@ -171,7 +215,7 @@ function installOuroCommand(deps = {}) {
171
215
  component: "daemon",
172
216
  event: "daemon.ouro_path_install_end",
173
217
  message: "ouro command installed",
174
- meta: { scriptPath, pathReady, shellProfileUpdated },
218
+ meta: { scriptPath, pathReady, shellProfileUpdated, oldScriptPath: oldExists ? oldScriptPath : null },
175
219
  });
176
- return { installed: true, scriptPath, pathReady, shellProfileUpdated };
220
+ return { installed: true, scriptPath, pathReady, shellProfileUpdated, repairedOldLauncher };
177
221
  }
@@ -0,0 +1,171 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.getOuroCliHome = getOuroCliHome;
37
+ exports.getCurrentVersion = getCurrentVersion;
38
+ exports.getPreviousVersion = getPreviousVersion;
39
+ exports.buildChangelogCommand = buildChangelogCommand;
40
+ exports.listInstalledVersions = listInstalledVersions;
41
+ exports.installVersion = installVersion;
42
+ exports.activateVersion = activateVersion;
43
+ exports.ensureLayout = ensureLayout;
44
+ const fs = __importStar(require("fs"));
45
+ const os = __importStar(require("os"));
46
+ const path = __importStar(require("path"));
47
+ const runtime_1 = require("../../nerves/runtime");
48
+ function getOuroCliHome(homeDir) {
49
+ /* v8 ignore next -- dep default: tests always inject @preserve */
50
+ const home = homeDir ?? os.homedir();
51
+ return path.join(home, ".ouro-cli");
52
+ }
53
+ function getCurrentVersion(deps) {
54
+ const cliHome = getOuroCliHome(deps.homeDir);
55
+ /* v8 ignore next -- dep default: tests always inject @preserve */
56
+ const readlinkSync = deps.readlinkSync ?? fs.readlinkSync;
57
+ try {
58
+ const target = readlinkSync(path.join(cliHome, "CurrentVersion"));
59
+ return path.basename(target);
60
+ }
61
+ catch {
62
+ return null;
63
+ }
64
+ }
65
+ function getPreviousVersion(deps) {
66
+ const cliHome = getOuroCliHome(deps.homeDir);
67
+ /* v8 ignore next -- dep default: tests always inject @preserve */
68
+ const readlinkSync = deps.readlinkSync ?? fs.readlinkSync;
69
+ try {
70
+ const target = readlinkSync(path.join(cliHome, "previous"));
71
+ return path.basename(target);
72
+ }
73
+ catch {
74
+ return null;
75
+ }
76
+ }
77
+ function buildChangelogCommand(previousVersion, currentVersion) {
78
+ if (!previousVersion || !currentVersion || previousVersion === currentVersion) {
79
+ return null;
80
+ }
81
+ return `ouro changelog --from ${previousVersion}`;
82
+ }
83
+ function listInstalledVersions(deps) {
84
+ const cliHome = getOuroCliHome(deps.homeDir);
85
+ /* v8 ignore next -- dep default: tests always inject @preserve */
86
+ const readdirSync = deps.readdirSync ?? ((p, opts) => fs.readdirSync(p, opts));
87
+ try {
88
+ const entries = readdirSync(path.join(cliHome, "versions"), { withFileTypes: true });
89
+ return entries.filter((e) => e.isDirectory()).map((e) => e.name);
90
+ }
91
+ catch {
92
+ return [];
93
+ }
94
+ }
95
+ function installVersion(version, deps) {
96
+ const cliHome = getOuroCliHome(deps.homeDir);
97
+ /* v8 ignore start -- dep defaults: tests always inject @preserve */
98
+ const mkdirSync = deps.mkdirSync ?? fs.mkdirSync;
99
+ const execSync = deps.execSync ?? ((cmd, opts) => require("child_process").execSync(cmd, opts));
100
+ /* v8 ignore stop */
101
+ const versionDir = path.join(cliHome, "versions", version);
102
+ (0, runtime_1.emitNervesEvent)({
103
+ component: "daemon",
104
+ event: "daemon.cli_version_install_start",
105
+ message: "installing CLI version",
106
+ meta: { version, versionDir },
107
+ });
108
+ mkdirSync(versionDir, { recursive: true });
109
+ execSync(`npm install --prefix ${versionDir} @ouro.bot/cli@${version}`, { stdio: "pipe" });
110
+ (0, runtime_1.emitNervesEvent)({
111
+ component: "daemon",
112
+ event: "daemon.cli_version_install_end",
113
+ message: "CLI version installed",
114
+ meta: { version, versionDir },
115
+ });
116
+ }
117
+ function activateVersion(version, deps) {
118
+ const cliHome = getOuroCliHome(deps.homeDir);
119
+ /* v8 ignore start -- dep defaults: tests always inject @preserve */
120
+ const readlinkSync = deps.readlinkSync ?? fs.readlinkSync;
121
+ const unlinkSync = deps.unlinkSync ?? fs.unlinkSync;
122
+ const symlinkSync = deps.symlinkSync ?? fs.symlinkSync;
123
+ const existsSync = deps.existsSync ?? fs.existsSync;
124
+ /* v8 ignore stop */
125
+ const currentVersionPath = path.join(cliHome, "CurrentVersion");
126
+ const previousPath = path.join(cliHome, "previous");
127
+ const newTarget = path.join(cliHome, "versions", version);
128
+ (0, runtime_1.emitNervesEvent)({
129
+ component: "daemon",
130
+ event: "daemon.cli_version_activate",
131
+ message: "activating CLI version",
132
+ meta: { version },
133
+ });
134
+ // Read old CurrentVersion target (may not exist)
135
+ let oldTarget = null;
136
+ try {
137
+ oldTarget = readlinkSync(currentVersionPath);
138
+ }
139
+ catch {
140
+ // No current version — first install
141
+ }
142
+ // Update previous symlink to point to old current
143
+ if (oldTarget) {
144
+ try {
145
+ unlinkSync(previousPath);
146
+ }
147
+ catch {
148
+ // previous symlink may not exist yet
149
+ }
150
+ symlinkSync(oldTarget, previousPath);
151
+ }
152
+ // Update CurrentVersion symlink
153
+ if (existsSync(currentVersionPath)) {
154
+ unlinkSync(currentVersionPath);
155
+ }
156
+ symlinkSync(newTarget, currentVersionPath);
157
+ }
158
+ function ensureLayout(deps) {
159
+ const cliHome = getOuroCliHome(deps.homeDir);
160
+ /* v8 ignore next -- dep default: tests always inject @preserve */
161
+ const mkdirSync = deps.mkdirSync ?? fs.mkdirSync;
162
+ mkdirSync(cliHome, { recursive: true });
163
+ mkdirSync(path.join(cliHome, "bin"), { recursive: true });
164
+ mkdirSync(path.join(cliHome, "versions"), { recursive: true });
165
+ (0, runtime_1.emitNervesEvent)({
166
+ component: "daemon",
167
+ event: "daemon.cli_layout_ensured",
168
+ message: "CLI directory layout ensured",
169
+ meta: { cliHome },
170
+ });
171
+ }