delimit-cli 4.0.6 → 4.1.0

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.
@@ -560,37 +560,3 @@ def _save_scan(result: Dict[str, Any], scan_time: datetime) -> Path:
560
560
  path.write_text(json.dumps(result, indent=2, default=str))
561
561
  logger.info("Scan saved to %s", path)
562
562
  return path
563
-
564
-
565
- def fetch_thread(thread_id: str, *, proxy_url: str = PROXY_URL) -> Optional[Dict[str, Any]]:
566
- """Fetch a single Reddit thread by ID via the residential proxy."""
567
- import urllib.parse
568
- import urllib.request
569
- reddit_url = f"https://www.reddit.com/comments/{thread_id}.json?raw_json=1"
570
- fetch_url = f"{proxy_url}?url={urllib.parse.quote(reddit_url, safe='')}"
571
-
572
- req = urllib.request.Request(
573
- fetch_url,
574
- headers={"User-Agent": "delimit-scanner/1.0", "Accept": "application/json"},
575
- )
576
-
577
- try:
578
- with urllib.request.urlopen(req, timeout=15) as resp:
579
- data = json.loads(resp.read().decode())
580
- if isinstance(data, list) and len(data) > 0:
581
- post_data = data[0].get("data", {}).get("children", [{}])[0].get("data", {})
582
- if post_data:
583
- return {
584
- "id": post_data.get("id", ""),
585
- "title": post_data.get("title", ""),
586
- "author": post_data.get("author", ""),
587
- "score": post_data.get("score", 0),
588
- "num_comments": post_data.get("num_comments", 0),
589
- "subreddit": post_data.get("subreddit", ""),
590
- "permalink": post_data.get("permalink", ""),
591
- "selftext": post_data.get("selftext", ""),
592
- "created_utc": post_data.get("created_utc", 0),
593
- }
594
- except Exception as exc:
595
- logger.warning("Failed to fetch thread %s: %s", thread_id, exc)
596
- return None
@@ -4906,9 +4906,10 @@ def delimit_deliberate(
4906
4906
  ) -> Dict[str, Any]:
4907
4907
  """Run multi-model consensus via real AI-to-AI deliberation (Pro).
4908
4908
 
4909
- Models (Grok 4, Gemini, Codex) debate each other directly until unanimous agreement.
4910
- Free tier: 3 deliberations using hosted keys, no setup required.
4911
- BYOK: configure your own API keys in ~/.delimit/models.json for unlimited use.
4909
+ Models debate each other directly until unanimous agreement.
4910
+ Free tier: 3 deliberations using Gemini Flash + GPT-4o-mini, no setup required.
4911
+ BYOK: configure your own API keys in ~/.delimit/models.json for unlimited use
4912
+ with any models (Grok, Claude, Gemini Pro, GPT-4o, etc).
4912
4913
 
4913
4914
  Args:
4914
4915
  question: The question to reach consensus on.
package/gateway/ai/tui.py CHANGED
@@ -34,18 +34,21 @@ SESSIONS_DIR = Path.home() / ".delimit" / "sessions"
34
34
 
35
35
 
36
36
  def _load_ledger_items(status: str = "open", limit: int = 20) -> List[Dict]:
37
- items = []
37
+ # Deduplicate by ID — last entry wins (append-only JSONL)
38
+ by_id: Dict[str, Dict] = {}
38
39
  for fname in ("operations.jsonl", "strategy.jsonl"):
39
40
  path = LEDGER_DIR / fname
40
41
  if not path.exists():
41
42
  continue
42
- for line in path.read_text().strip().split("\n")[-200:]:
43
+ for line in path.read_text().strip().split("\n"):
43
44
  try:
44
45
  d = json.loads(line)
45
- if d.get("status") == status:
46
- items.append(d)
46
+ item_id = d.get("id", "")
47
+ if item_id:
48
+ by_id[item_id] = d
47
49
  except json.JSONDecodeError:
48
50
  continue
51
+ items = [d for d in by_id.values() if d.get("status") == status]
49
52
  items.sort(key=lambda x: (0 if x.get("priority") == "P0" else 1 if x.get("priority") == "P1" else 2))
50
53
  return items[:limit]
51
54
 
@@ -154,6 +157,48 @@ class SessionPanel(Static):
154
157
  content.update("\n".join(lines))
155
158
 
156
159
 
160
+ class VenturesPanel(Static):
161
+ """Ventures as app tiles — each venture is an 'app' in the OS."""
162
+
163
+ def compose(self) -> ComposeResult:
164
+ yield Static(id="ventures-content")
165
+
166
+ def on_mount(self) -> None:
167
+ self._refresh_data()
168
+ self.set_interval(30, self._refresh_data)
169
+
170
+ def _refresh_data(self) -> None:
171
+ content = self.query_one("#ventures-content", Static)
172
+ swarm = _load_swarm_status()
173
+ by_venture = swarm.get("by_venture", {})
174
+
175
+ if not by_venture:
176
+ content.update("[dim]No ventures registered. Run delimit_swarm(action='register').[/]")
177
+ return
178
+
179
+ # Count open items per venture
180
+ all_items = _load_ledger_items("open", 999)
181
+ venture_items = {}
182
+ for item in all_items:
183
+ v = item.get("venture", "root")
184
+ venture_items[v] = venture_items.get(v, 0) + 1
185
+
186
+ lines = [
187
+ "[bold]Ventures[/] — each venture is an app in Delimit OS\n",
188
+ ]
189
+ for venture, agent_count in sorted(by_venture.items()):
190
+ open_count = venture_items.get(venture, venture_items.get(f"{venture}-mcp", 0))
191
+ status_icon = "[green]●[/]" if agent_count > 0 else "[red]○[/]"
192
+ lines.append(
193
+ f" {status_icon} [bold cyan]{venture}[/]"
194
+ f" | {agent_count} agents"
195
+ f" | {open_count} open items"
196
+ )
197
+
198
+ lines.append(f"\n[dim]Total: {len(by_venture)} ventures, {swarm['agents']} agents[/]")
199
+ content.update("\n".join(lines))
200
+
201
+
157
202
  class GovernanceBar(Static):
158
203
  """Top status bar — governance health at a glance."""
159
204
 
@@ -213,6 +258,7 @@ class DelimitOS(App):
213
258
  ("q", "quit", "Quit"),
214
259
  ("l", "focus_ledger", "Ledger"),
215
260
  ("s", "focus_swarm", "Swarm"),
261
+ ("v", "focus_ventures", "Ventures"),
216
262
  ("h", "focus_sessions", "History"),
217
263
  ("r", "refresh", "Refresh"),
218
264
  ("t", "think", "Think"),
@@ -226,6 +272,8 @@ class DelimitOS(App):
226
272
  yield LedgerPanel()
227
273
  with TabPane("Swarm", id="tab-swarm"):
228
274
  yield SwarmPanel()
275
+ with TabPane("Ventures", id="tab-ventures"):
276
+ yield VenturesPanel()
229
277
  with TabPane("Sessions", id="tab-sessions"):
230
278
  yield SessionPanel()
231
279
  yield Footer()
@@ -236,6 +284,9 @@ class DelimitOS(App):
236
284
  def action_focus_swarm(self) -> None:
237
285
  self.query_one(TabbedContent).active = "tab-swarm"
238
286
 
287
+ def action_focus_ventures(self) -> None:
288
+ self.query_one(TabbedContent).active = "tab-ventures"
289
+
239
290
  def action_focus_sessions(self) -> None:
240
291
  self.query_one(TabbedContent).active = "tab-sessions"
241
292
 
@@ -248,11 +299,41 @@ class DelimitOS(App):
248
299
  panel._refresh_data()
249
300
  self.query_one(GovernanceBar)._refresh()
250
301
 
302
+ @work(thread=True)
251
303
  def action_think(self) -> None:
252
- self.notify("Deliberation triggered check ledger for results", title="Think")
304
+ """Trigger deliberation in background thread."""
305
+ self.notify("Deliberation starting...", title="Think")
306
+ try:
307
+ from ai.deliberation import deliberate
308
+ result = deliberate(
309
+ "Based on the current ledger and recent signals, what should the swarm build next?",
310
+ mode="dialogue",
311
+ max_rounds=2,
312
+ )
313
+ if result.get("mode") == "single_model_reflection":
314
+ verdict = result.get("synthesis", "No synthesis")[:200]
315
+ else:
316
+ verdict = result.get("final_verdict", "No consensus")
317
+ if isinstance(verdict, str):
318
+ verdict = verdict[:200]
319
+ else:
320
+ verdict = str(verdict)[:200]
321
+ self.notify(verdict, title="Think Result", timeout=15)
322
+ except Exception as e:
323
+ self.notify(f"Deliberation failed: {e}", title="Think Error", severity="error")
253
324
 
254
325
  def action_build(self) -> None:
255
- self.notify("Build loop started swarm dispatching tasks", title="Build")
326
+ """Show next buildable item from ledger."""
327
+ items = _load_ledger_items("open", 5)
328
+ if items:
329
+ top = items[0]
330
+ self.notify(
331
+ f"{top.get('id', '?')} [{top.get('priority', '?')}]: {top.get('title', '?')[:60]}",
332
+ title="Next Build Item",
333
+ timeout=10,
334
+ )
335
+ else:
336
+ self.notify("Ledger is clear — nothing to build!", title="Build")
256
337
 
257
338
 
258
339
  def main():
@@ -31,11 +31,6 @@ The founder reviews and approves via email. The AI's job is to draft, send, and
31
31
  - **Session end or idle**: call \`delimit_session_handoff\` to preserve context for next session
32
32
  - If returning user, summarize what's pending. If new project, call \`delimit_scan\`.
33
33
 
34
- ### Natural Language Triggers
35
- - \`think and build\`, \`keep building\`, \`resume building\`, \`run the swarm\`: route through native Delimit session/build behavior, equivalent to \`delimit session --build\`
36
- - \`ask delimit\`, \`what's next\`, \`check the ledger\`: route through native Delimit inspect behavior, equivalent to \`delimit session --inspect\` or \`delimit ask\`
37
- - Bootstrap behavior must be identical across Codex, Claude Code, Gemini, Grok, and Cursor: resolve user + venture + repo, load continuity/session state, check ledger, check daemon/swarm status, then either inspect or resume/launch the governed loop
38
-
39
34
  ### Code Development (debounced per edit batch)
40
35
  - After editing UI/CSS: call \`delimit_design_validate_responsive\`
41
36
  - After editing API specs: call \`delimit_lint\` + \`delimit_drift_check\`
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "delimit-cli",
3
3
  "mcpName": "io.github.delimit-ai/delimit-mcp-server",
4
- "version": "4.0.6",
4
+ "version": "4.1.0",
5
5
  "description": "Unify Claude Code, Codex, Cursor, and Gemini CLI with persistent context, governance, and multi-model debate.",
6
6
  "main": "index.js",
7
7
  "files": [
@@ -68,7 +68,7 @@
68
68
  "url": "https://github.com/delimit-ai/delimit-mcp-server.git"
69
69
  },
70
70
  "dependencies": {
71
- "axios": "^1.0.0",
71
+ "axios": "1.13.6",
72
72
  "chalk": "^4.1.2",
73
73
  "commander": "^12.1.0",
74
74
  "express": "^4.18.0",
@@ -52,18 +52,6 @@ else
52
52
  echo "✅ clean"
53
53
  fi
54
54
 
55
- # 5. Continuity state must never ship
56
- echo -n " Continuity state... "
57
- if find "$TMPDIR/package/" \( -path "*/.delimit/*" -o -path "*/continuity/*" -o -name "*.jsonl" -o -name "session_*.json" -o -name "handoff_*.json" \) | grep -v "package-lock.json" 2>/dev/null; then
58
- echo "❌ CONTINUITY ARTIFACTS IN PACKAGE"
59
- FAIL=1
60
- elif grep -rEi "/root/\.delimit|/home/[^/]+/\.delimit|/Users/[^/]+/\.delimit|C:\\Users\\[^\]+\\.delimit" "$TMPDIR/package/" --include="*.js" --include="*.json" 2>/dev/null | grep -v "security-scan-ignore"; then
61
- echo "❌ CONTINUITY PATHS LEAKED INTO PACKAGE"
62
- FAIL=1
63
- else
64
- echo "✅ clean"
65
- fi
66
-
67
55
  # Cleanup
68
56
  rm -rf "$TMPDIR"
69
57