@ouro.bot/cli 0.1.0-alpha.247 → 0.1.0-alpha.249

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/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.249",
6
+ "changes": [
7
+ "feat(cli): `ouro chat` without agent name shows agent selection (auto-selects if 1 agent, numbered list if multiple).",
8
+ "fix(tui): session history user messages get `) ` prefix, separator line after history, reduced to last 2 exchanges."
9
+ ]
10
+ },
11
+ {
12
+ "version": "0.1.0-alpha.248",
13
+ "changes": [
14
+ "feat(daemon): `ouro status` Git Sync now resolves and shows the actual remote URL via `git remote get-url`. Three states: `origin → git@github.com:me/foo.git` when the remote resolves, `local only` when sync is enabled but no remote is configured, `disabled` when sync is off. Previously you had to `cd` into the bundle and run `git remote -v` to find out where it pushes.",
15
+ "fix(heart/sync): `preTurnPull` now skips the pull when no git remote is configured, mirroring the existing `postTurnPush` behavior. Closes the half-implemented 'no-remote sync' (local-only commit log) story \u2014 previously, enabling sync without a remote produced a `syncFailure` on every turn from the failing `git pull` call."
16
+ ]
17
+ },
4
18
  {
5
19
  "version": "0.1.0-alpha.247",
6
20
  "changes": [
@@ -37,6 +37,7 @@ exports.listEnabledBundleAgents = listEnabledBundleAgents;
37
37
  exports.listBundleSyncRows = listBundleSyncRows;
38
38
  const fs = __importStar(require("fs"));
39
39
  const path = __importStar(require("path"));
40
+ const child_process_1 = require("child_process");
40
41
  const identity_1 = require("../identity");
41
42
  const runtime_1 = require("../../nerves/runtime");
42
43
  function listEnabledBundleAgents(options = {}) {
@@ -85,14 +86,21 @@ function listEnabledBundleAgents(options = {}) {
85
86
  * Used by the daemon (and stopped-state status renderer) to build per-agent
86
87
  * sync rows without depending on argv-derived global identity. Bundles that
87
88
  * cannot be read are skipped silently.
89
+ *
90
+ * For rows with sync enabled, also resolves the remote URL by shelling out to
91
+ * `git remote get-url <remote>` in the bundle directory. On any error (no
92
+ * remote, not a git repo, git missing) the URL is left undefined and the
93
+ * status renderer treats the bundle as "local only".
88
94
  */
89
95
  function listBundleSyncRows(options = {}) {
90
96
  const bundlesRoot = options.bundlesRoot ?? (0, identity_1.getAgentBundlesRoot)();
91
97
  const readFileSync = options.readFileSync ?? fs.readFileSync;
98
+ const execFileSync = options.execFileSync ?? child_process_1.execFileSync;
92
99
  const agents = listEnabledBundleAgents(options);
93
100
  const rows = [];
94
101
  for (const agent of agents) {
95
- const configPath = path.join(bundlesRoot, `${agent}.ouro`, "agent.json");
102
+ const bundleRoot = path.join(bundlesRoot, `${agent}.ouro`);
103
+ const configPath = path.join(bundleRoot, "agent.json");
96
104
  let enabled = false;
97
105
  let remote = "origin";
98
106
  try {
@@ -109,7 +117,22 @@ function listBundleSyncRows(options = {}) {
109
117
  catch {
110
118
  // Best-effort: bundle without readable config still gets a row with defaults
111
119
  }
112
- rows.push({ agent, enabled, remote });
120
+ let remoteUrl;
121
+ if (enabled) {
122
+ try {
123
+ const out = execFileSync("git", ["remote", "get-url", remote], {
124
+ cwd: bundleRoot,
125
+ stdio: "pipe",
126
+ timeout: 5000,
127
+ }).toString().trim();
128
+ if (out.length > 0)
129
+ remoteUrl = out;
130
+ }
131
+ catch {
132
+ // No remote configured, not a git repo, or git missing — leave undefined.
133
+ }
134
+ }
135
+ rows.push(remoteUrl !== undefined ? { agent, enabled, remote, remoteUrl } : { agent, enabled, remote });
113
136
  }
114
137
  return rows;
115
138
  }
@@ -1775,8 +1775,36 @@ async function runOuroCli(args, deps = (0, cli_defaults_1.createDefaultOuroCliDe
1775
1775
  return message;
1776
1776
  }
1777
1777
  if (command.kind === "chat.connect" && deps.startChat) {
1778
+ let agent = command.agent;
1779
+ // No agent specified — show selection
1780
+ /* v8 ignore start -- interactive agent selection: requires real promptInput + discovered agents @preserve */
1781
+ if (!agent) {
1782
+ const discovered = await Promise.resolve(deps.listDiscoveredAgents ? deps.listDiscoveredAgents() : (0, cli_defaults_1.defaultListDiscoveredAgents)());
1783
+ if (discovered.length === 0) {
1784
+ deps.writeStdout("no agents found — run `ouro` to hatch one");
1785
+ return "no agents found";
1786
+ }
1787
+ if (discovered.length === 1) {
1788
+ agent = discovered[0];
1789
+ }
1790
+ else if (deps.promptInput) {
1791
+ const prompt = `who do you want to talk to?\n${discovered.map((a, i) => `${i + 1}. ${a}`).join("\n")}\n`;
1792
+ const answer = await deps.promptInput(prompt);
1793
+ agent = discovered.includes(answer) ? answer : discovered[parseInt(answer, 10) - 1];
1794
+ if (!agent) {
1795
+ deps.writeStdout("invalid selection");
1796
+ return "invalid selection";
1797
+ }
1798
+ }
1799
+ else {
1800
+ const message = `who do you want to talk to? ${discovered.join(", ")} (use: ouro chat <agent>)`;
1801
+ deps.writeStdout(message);
1802
+ return message;
1803
+ }
1804
+ }
1805
+ /* v8 ignore stop */
1778
1806
  await ensureDaemonRunning(deps);
1779
- await deps.startChat(command.agent);
1807
+ await deps.startChat(agent);
1780
1808
  return "";
1781
1809
  }
1782
1810
  if (command.kind === "hatch.start") {
@@ -742,7 +742,7 @@ function parseOuroCommand(args) {
742
742
  }
743
743
  if (head === "chat") {
744
744
  if (!second)
745
- throw new Error(`Usage\n${usage()}`);
745
+ return { kind: "chat.connect", agent: "" };
746
746
  return { kind: "chat.connect", agent: second };
747
747
  }
748
748
  if (head === "msg")
@@ -147,7 +147,10 @@ function parseStatusPayload(data) {
147
147
  const remote = stringField(row.remote);
148
148
  if (!agent || enabled === null || !remote)
149
149
  return null;
150
- return { agent, enabled, remote };
150
+ const remoteUrl = stringField(row.remoteUrl) ?? undefined;
151
+ return remoteUrl !== undefined
152
+ ? { agent, enabled, remote, remoteUrl }
153
+ : { agent, enabled, remote };
151
154
  });
152
155
  if (parsedSenses.some((row) => row === null) ||
153
156
  parsedWorkers.some((row) => row === null) ||
@@ -317,7 +320,10 @@ function formatDaemonStatusOutput(response, fallback) {
317
320
  const name = row.agent.padEnd(agentNameWidth);
318
321
  const dot = row.enabled ? green("●") : dim("○");
319
322
  const stateText = (row.enabled ? "enabled " : "disabled").padEnd(10);
320
- const detail = row.enabled ? `remote: ${row.remote}` : "";
323
+ let detail = "";
324
+ if (row.enabled) {
325
+ detail = row.remoteUrl !== undefined ? `${row.remote} → ${row.remoteUrl}` : "local only";
326
+ }
321
327
  lines.push(` ${name} ${dot} ${stateText} ${dim(detail)}`);
322
328
  }
323
329
  lines.push("");
@@ -41,6 +41,11 @@ const path = __importStar(require("path"));
41
41
  const runtime_1 = require("../nerves/runtime");
42
42
  /**
43
43
  * Pre-turn pull: sync the agent bundle from remote before assembling the start-of-turn packet.
44
+ *
45
+ * If the bundle has no git remote configured, the pull is skipped and the function
46
+ * returns ok — matching the no-remote behavior of postTurnPush. This supports the
47
+ * "local-only sync" mode where the bundle accumulates a commit log without ever
48
+ * pushing or pulling from a remote.
44
49
  */
45
50
  function preTurnPull(agentRoot, config) {
46
51
  (0, runtime_1.emitNervesEvent)({
@@ -49,6 +54,33 @@ function preTurnPull(agentRoot, config) {
49
54
  message: "pre-turn pull starting",
50
55
  meta: { agentRoot, remote: config.remote },
51
56
  });
57
+ // Check if any remote is configured. If not, skip the pull (local-only mode).
58
+ try {
59
+ const remoteOutput = (0, child_process_1.execFileSync)("git", ["remote"], {
60
+ cwd: agentRoot,
61
+ stdio: "pipe",
62
+ timeout: 5000,
63
+ }).toString().trim();
64
+ if (remoteOutput.length === 0) {
65
+ (0, runtime_1.emitNervesEvent)({
66
+ component: "heart",
67
+ event: "heart.sync_pull_end",
68
+ message: "pre-turn pull skipped: no remote configured",
69
+ meta: { agentRoot },
70
+ });
71
+ return { ok: true };
72
+ }
73
+ }
74
+ catch (err) {
75
+ const error = err instanceof Error ? err.message : String(err);
76
+ (0, runtime_1.emitNervesEvent)({
77
+ component: "heart",
78
+ event: "heart.sync_pull_error",
79
+ message: "pre-turn pull failed: git remote check failed",
80
+ meta: { agentRoot, error },
81
+ });
82
+ return { ok: false, error };
83
+ }
52
84
  try {
53
85
  (0, child_process_1.execFileSync)("git", ["pull", config.remote], {
54
86
  cwd: agentRoot,
@@ -86,10 +86,13 @@ function MessageBlock({ msg }) {
86
86
  return ((0, jsx_runtime_1.jsx)(ink_1.Box, { flexDirection: "column", marginTop: 1, children: (0, jsx_runtime_1.jsx)(ink_1.Text, { dimColor: true, children: msg.content }) }));
87
87
  }
88
88
  if (msg.role === "history-user") {
89
- return ((0, jsx_runtime_1.jsx)(ink_1.Box, { flexDirection: "column", marginTop: 1, children: (0, jsx_runtime_1.jsx)(ink_1.Text, { dimColor: true, bold: true, children: msg.content }) }));
89
+ return ((0, jsx_runtime_1.jsx)(ink_1.Box, { flexDirection: "column", marginTop: 1, children: (0, jsx_runtime_1.jsxs)(ink_1.Box, { children: [(0, jsx_runtime_1.jsx)(ink_1.Text, { dimColor: true, bold: true, children: ") " }), (0, jsx_runtime_1.jsx)(ink_1.Text, { dimColor: true, bold: true, children: msg.content })] }) }));
90
90
  }
91
91
  if (msg.role === "history-assistant") {
92
- return ((0, jsx_runtime_1.jsx)(ink_1.Box, { flexDirection: "column", marginBottom: 1, children: msg.content ? (0, jsx_runtime_1.jsx)(ink_1.Text, { dimColor: true, children: msg.content }) : null }));
92
+ return ((0, jsx_runtime_1.jsx)(ink_1.Box, { flexDirection: "column", marginBottom: 1, children: msg.content ? (0, jsx_runtime_1.jsx)(streaming_markdown_1.StreamingMarkdown, { text: msg.content, maxWidth: safeWidth() }) : null }));
93
+ }
94
+ if (msg.role === "history-end") {
95
+ return ((0, jsx_runtime_1.jsx)(ink_1.Box, { flexDirection: "column", children: (0, jsx_runtime_1.jsx)(ink_1.Text, { dimColor: true, children: "─".repeat(safeWidth() + 2) }) }));
93
96
  }
94
97
  if (msg.role === "user") {
95
98
  return ((0, jsx_runtime_1.jsxs)(ink_1.Box, { flexDirection: "column", marginTop: 1, children: [msg.content ? (0, jsx_runtime_1.jsx)(ink_1.Text, { color: OURO.bone, bold: true, children: msg.content }) : null, (0, jsx_runtime_1.jsx)(ink_1.Box, { marginBottom: 1, children: (0, jsx_runtime_1.jsx)(ink_1.Text, { children: "" }) })] }));
@@ -173,7 +173,7 @@ class TuiStore {
173
173
  seedHistory(texts) {
174
174
  this._inputHistory.push(...texts);
175
175
  }
176
- /** Show session resume context: summary line + last N exchanges, all dimmed */
176
+ /** Show session resume context: summary line + last N exchanges (dimmed), then separator */
177
177
  addSessionHistory(summary, exchanges) {
178
178
  const msgs = [
179
179
  { id: "history-summary", role: "history-summary", content: summary },
@@ -182,6 +182,7 @@ class TuiStore {
182
182
  role: (ex.role === "user" ? "history-user" : "history-assistant"),
183
183
  content: ex.content,
184
184
  })),
185
+ { id: "history-end", role: "history-end", content: "" },
185
186
  ];
186
187
  this._completed = [...msgs, ...this._completed];
187
188
  this.notify();
@@ -539,9 +539,9 @@ async function runCliSession(options) {
539
539
  /* v8 ignore start -- session resume display: integration-only, tested visually @preserve */
540
540
  if (messages.length > 1) {
541
541
  const userAssistantMsgs = messages.filter((m) => (m.role === "user" || m.role === "assistant") && typeof m.content === "string" && m.content.trim().length > 0);
542
- // Extract last 3 exchanges (pairs of user+assistant)
542
+ // Extract last 2 exchanges (up to 4 messages)
543
543
  const lastExchanges = [];
544
- for (let i = userAssistantMsgs.length - 1; i >= 0 && lastExchanges.length < 6; i--) {
544
+ for (let i = userAssistantMsgs.length - 1; i >= 0 && lastExchanges.length < 4; i--) {
545
545
  lastExchanges.unshift({ role: userAssistantMsgs[i].role, content: userAssistantMsgs[i].content });
546
546
  }
547
547
  const msgCount = messages.filter(m => m.role === "user" || m.role === "assistant").length;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ouro.bot/cli",
3
- "version": "0.1.0-alpha.247",
3
+ "version": "0.1.0-alpha.249",
4
4
  "main": "dist/heart/daemon/ouro-entry.js",
5
5
  "bin": {
6
6
  "cli": "dist/heart/daemon/ouro-bot-entry.js",