@anastops/cli 2.0.1 → 2.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.
Files changed (35) hide show
  1. package/dist/commands/doctor.d.ts.map +1 -1
  2. package/dist/commands/doctor.js +10 -2
  3. package/dist/commands/doctor.js.map +1 -1
  4. package/dist/index.js +0 -0
  5. package/package.json +6 -3
  6. package/ranger-tui/ranger_tui/__pycache__/__init__.cpython-314.pyc +0 -0
  7. package/ranger-tui/ranger_tui/__pycache__/app.cpython-314.pyc +0 -0
  8. package/ranger-tui/ranger_tui/actions/__pycache__/__init__.cpython-314.pyc +0 -0
  9. package/ranger-tui/ranger_tui/actions/__pycache__/agent_actions.cpython-314.pyc +0 -0
  10. package/ranger-tui/ranger_tui/actions/__pycache__/session_actions.cpython-314.pyc +0 -0
  11. package/ranger-tui/ranger_tui/actions/__pycache__/task_actions.cpython-314.pyc +0 -0
  12. package/ranger-tui/ranger_tui/app.py +113 -22
  13. package/ranger-tui/ranger_tui/data/__init__.py +2 -1
  14. package/ranger-tui/ranger_tui/data/__pycache__/__init__.cpython-314.pyc +0 -0
  15. package/ranger-tui/ranger_tui/data/__pycache__/client.cpython-314.pyc +0 -0
  16. package/ranger-tui/ranger_tui/data/client.py +38 -10
  17. package/ranger-tui/ranger_tui/screens/__init__.py +2 -0
  18. package/ranger-tui/ranger_tui/screens/__pycache__/__init__.cpython-314.pyc +0 -0
  19. package/ranger-tui/ranger_tui/screens/__pycache__/command_palette.cpython-314.pyc +0 -0
  20. package/ranger-tui/ranger_tui/screens/__pycache__/dashboard.cpython-314.pyc +0 -0
  21. package/ranger-tui/ranger_tui/screens/__pycache__/help.cpython-314.pyc +0 -0
  22. package/ranger-tui/ranger_tui/screens/__pycache__/loading.cpython-314.pyc +0 -0
  23. package/ranger-tui/ranger_tui/screens/__pycache__/session.cpython-314.pyc +0 -0
  24. package/ranger-tui/ranger_tui/screens/__pycache__/task.cpython-314.pyc +0 -0
  25. package/ranger-tui/ranger_tui/screens/dashboard.py +18 -4
  26. package/ranger-tui/ranger_tui/screens/loading.py +214 -0
  27. package/ranger-tui/ranger_tui/screens/session.py +19 -5
  28. package/ranger-tui/ranger_tui/screens/task.py +18 -4
  29. package/ranger-tui/ranger_tui/widgets/__pycache__/agents_table.cpython-314.pyc +0 -0
  30. package/ranger-tui/ranger_tui/widgets/__pycache__/header.cpython-314.pyc +0 -0
  31. package/ranger-tui/ranger_tui/widgets/__pycache__/metrics_panel.cpython-314.pyc +0 -0
  32. package/ranger-tui/ranger_tui/widgets/__pycache__/sessions_table.cpython-314.pyc +0 -0
  33. package/ranger-tui/ranger_tui/widgets/__pycache__/tasks_table.cpython-314.pyc +0 -0
  34. package/ranger-tui/ranger_tui/widgets/__pycache__/topbar.cpython-314.pyc +0 -0
  35. package/ranger-tui/ranger_tui/widgets/topbar.py +14 -36
@@ -1 +1 @@
1
- {"version":3,"file":"doctor.d.ts","sourceRoot":"","sources":["../../src/commands/doctor.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAiBH,wBAAsB,aAAa,IAAI,OAAO,CAAC,IAAI,CAAC,CAsNnD"}
1
+ {"version":3,"file":"doctor.d.ts","sourceRoot":"","sources":["../../src/commands/doctor.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAiBH,wBAAsB,aAAa,IAAI,OAAO,CAAC,IAAI,CAAC,CAgOnD"}
@@ -133,10 +133,16 @@ export async function doctorCommand() {
133
133
  for (const adapter of registry.list()) {
134
134
  const spinner = ora(`Checking ${adapter.name}...`).start();
135
135
  try {
136
+ const { appendFileSync } = await import('fs');
137
+ appendFileSync('/tmp/doctor-debug.log', `Checking adapter: ${adapter.name} (${adapter.type})\n`);
136
138
  const [installed, authenticated] = await Promise.all([
137
139
  adapter.isInstalled(),
138
- adapter.isAuthenticated().catch(() => false),
140
+ adapter.isAuthenticated().catch((err) => {
141
+ appendFileSync('/tmp/doctor-debug.log', `Auth check failed for ${adapter.name}: ${String(err)}\n`);
142
+ return false;
143
+ }),
139
144
  ]);
145
+ appendFileSync('/tmp/doctor-debug.log', `${adapter.name} - installed: ${installed}, authenticated: ${authenticated}\n`);
140
146
  spinner.stop();
141
147
  if (!installed) {
142
148
  results.push({
@@ -164,7 +170,9 @@ export async function doctorCommand() {
164
170
  });
165
171
  }
166
172
  }
167
- catch {
173
+ catch (err) {
174
+ const { appendFileSync } = await import('fs');
175
+ appendFileSync('/tmp/doctor-debug.log', `Error checking ${adapter.name}: ${String(err)}\n`);
168
176
  spinner.stop();
169
177
  results.push({
170
178
  name: adapter.name,
@@ -1 +1 @@
1
- {"version":3,"file":"doctor.js","sourceRoot":"","sources":["../../src/commands/doctor.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,UAAU,EAAE,MAAM,IAAI,CAAC;AAChC,OAAO,EAAE,OAAO,EAAE,MAAM,IAAI,CAAC;AAC7B,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAE5B,OAAO,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AACrD,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,GAAG,MAAM,KAAK,CAAC;AAStB,MAAM,CAAC,KAAK,UAAU,aAAa;IACjC,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;IAChB,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,uBAAuB,CAAC,CAAC,CAAC;IACjD,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;IACvC,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;IAEhB,MAAM,OAAO,GAAkB,EAAE,CAAC;IAElC,wBAAwB;IACxB,MAAM,WAAW,GAAG,OAAO,CAAC,OAAO,CAAC;IACpC,MAAM,cAAc,GAAG,QAAQ,CAAC,WAAW,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,GAAG,EAAE,EAAE,CAAC,CAAC;IAC/E,OAAO,CAAC,IAAI,CAAC;QACX,IAAI,EAAE,SAAS;QACf,MAAM,EAAE,cAAc,IAAI,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM;QAC9C,OAAO,EAAE,WAAW;QACpB,OAAO,EAAE,cAAc,GAAG,EAAE,CAAC,CAAC,CAAC,sBAAsB,CAAC,CAAC,CAAC,SAAS;KAClE,CAAC,CAAC;IAEH,kEAAkE;IAClE,MAAM,aAAa,GAAG,GAAG,CAAC,2BAA2B,CAAC,CAAC,KAAK,EAAE,CAAC;IAC/D,IAAI,CAAC;QACH,MAAM,YAAY,GAAG,IAAI,CAAC,OAAO,EAAE,EAAE,WAAW,CAAC,CAAC;QAClD,MAAM,OAAO,GAAG,IAAI,CAAC,YAAY,EAAE,MAAM,CAAC,CAAC;QAC3C,MAAM,MAAM,GAAG,UAAU,CAAC,OAAO,CAAC,CAAC;QAEnC,aAAa,CAAC,IAAI,EAAE,CAAC;QACrB,OAAO,CAAC,IAAI,CAAC;YACX,IAAI,EAAE,eAAe;YACrB,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM;YAChC,OAAO,EAAE,MAAM,CAAC,CAAC,CAAC,wBAAwB,CAAC,CAAC,CAAC,iBAAiB;YAC9D,OAAO,EAAE,CAAC,MAAM,CAAC,CAAC,CAAC,oBAAoB,CAAC,CAAC,CAAC,SAAS;SACpD,CAAC,CAAC;IACL,CAAC;IAAC,MAAM,CAAC;QACP,aAAa,CAAC,IAAI,EAAE,CAAC;QACrB,OAAO,CAAC,IAAI,CAAC;YACX,IAAI,EAAE,eAAe;YACrB,MAAM,EAAE,MAAM;YACd,OAAO,EAAE,8BAA8B;YACvC,OAAO,EAAE,SAAS;SACnB,CAAC,CAAC;IACL,CAAC;IAED,eAAe;IACf,MAAM,aAAa,GAAG,GAAG,CAAC,oBAAoB,CAAC,CAAC,KAAK,EAAE,CAAC;IACxD,IAAI,CAAC;QACH,MAAM,EAAE,QAAQ,EAAE,GAAG,MAAM,MAAM,CAAC,eAAe,CAAC,CAAC;QACnD,QAAQ,CAAC,aAAa,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,CAAC,CAAC;QAC3C,aAAa,CAAC,IAAI,EAAE,CAAC;QACrB,OAAO,CAAC,IAAI,CAAC;YACX,IAAI,EAAE,QAAQ;YACd,MAAM,EAAE,MAAM;YACd,OAAO,EAAE,SAAS;YAClB,OAAO,EAAE,SAAS;SACnB,CAAC,CAAC;IACL,CAAC;IAAC,MAAM,CAAC;QACP,aAAa,CAAC,IAAI,EAAE,CAAC;QACrB,OAAO,CAAC,IAAI,CAAC;YACX,IAAI,EAAE,QAAQ;YACd,MAAM,EAAE,MAAM;YACd,OAAO,EAAE,8BAA8B;YACvC,OAAO,EAAE,4BAA4B;SACtC,CAAC,CAAC;IACL,CAAC;IAED,qEAAqE;IACrE,MAAM,YAAY,GAAG,GAAG,CAAC,mBAAmB,CAAC,CAAC,KAAK,EAAE,CAAC;IACtD,IAAI,CAAC;QACH,MAAM,EAAE,QAAQ,EAAE,GAAG,MAAM,MAAM,CAAC,eAAe,CAAC,CAAC;QACnD,iEAAiE;QACjE,MAAM,YAAY,GAAG,QAAQ,CAC3B,gEAAgE,EAChE;YACE,KAAK,EAAE,MAAM;YACb,QAAQ,EAAE,OAAO;SAClB,CACF,CAAC;QACF,MAAM,SAAS,GAAG,YAAY,CAAC,IAAI,EAAE,CAAC,QAAQ,CAAC,gBAAgB,CAAC,CAAC;QACjE,YAAY,CAAC,IAAI,EAAE,CAAC;QACpB,OAAO,CAAC,IAAI,CAAC;YACX,IAAI,EAAE,OAAO;YACb,MAAM,EAAE,SAAS,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM;YACnC,OAAO,EAAE,SAAS,CAAC,CAAC,CAAC,mBAAmB,CAAC,CAAC,CAAC,uBAAuB;YAClE,OAAO,EAAE,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,wBAAwB;SAC1D,CAAC,CAAC;IACL,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,YAAY,CAAC,IAAI,EAAE,CAAC;QACpB,OAAO,CAAC,KAAK,CAAC,kCAAkC,EAAE,KAAK,CAAC,CAAC;QACzD,OAAO,CAAC,IAAI,CAAC;YACX,IAAI,EAAE,OAAO;YACb,MAAM,EAAE,MAAM;YACd,OAAO,EAAE,uBAAuB;YAChC,OAAO,EAAE,wBAAwB;SAClC,CAAC,CAAC;IACL,CAAC;IAED,uEAAuE;IACvE,MAAM,YAAY,GAAG,GAAG,CAAC,qBAAqB,CAAC,CAAC,KAAK,EAAE,CAAC;IACxD,IAAI,CAAC;QACH,MAAM,EAAE,QAAQ,EAAE,GAAG,MAAM,MAAM,CAAC,eAAe,CAAC,CAAC;QACnD,iEAAiE;QACjE,MAAM,YAAY,GAAG,QAAQ,CAC3B,kEAAkE,EAClE;YACE,KAAK,EAAE,MAAM;YACb,QAAQ,EAAE,OAAO;SAClB,CACF,CAAC;QACF,MAAM,SAAS,GAAG,YAAY,CAAC,IAAI,EAAE,CAAC,QAAQ,CAAC,kBAAkB,CAAC,CAAC;QACnE,YAAY,CAAC,IAAI,EAAE,CAAC;QACpB,OAAO,CAAC,IAAI,CAAC;YACX,IAAI,EAAE,SAAS;YACf,MAAM,EAAE,SAAS,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM;YACnC,OAAO,EAAE,SAAS,CAAC,CAAC,CAAC,mBAAmB,CAAC,CAAC,CAAC,uBAAuB;YAClE,OAAO,EAAE,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,wBAAwB;SAC1D,CAAC,CAAC;IACL,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,YAAY,CAAC,IAAI,EAAE,CAAC;QACpB,OAAO,CAAC,KAAK,CAAC,oCAAoC,EAAE,KAAK,CAAC,CAAC;QAC3D,OAAO,CAAC,IAAI,CAAC;YACX,IAAI,EAAE,SAAS;YACf,MAAM,EAAE,MAAM;YACd,OAAO,EAAE,uBAAuB;YAChC,OAAO,EAAE,wBAAwB;SAClC,CAAC,CAAC;IACL,CAAC;IAED,qBAAqB;IACrB,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;IAChB,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,cAAc,CAAC,CAAC,CAAC;IACxC,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;IAEvC,MAAM,QAAQ,GAAG,eAAe,CAAC,WAAW,EAAE,CAAC;IAE/C,KAAK,MAAM,OAAO,IAAI,QAAQ,CAAC,IAAI,EAAE,EAAE,CAAC;QACtC,MAAM,OAAO,GAAG,GAAG,CAAC,YAAY,OAAO,CAAC,IAAI,KAAK,CAAC,CAAC,KAAK,EAAE,CAAC;QAE3D,IAAI,CAAC;YACH,MAAM,CAAC,SAAS,EAAE,aAAa,CAAC,GAAG,MAAM,OAAO,CAAC,GAAG,CAAC;gBACnD,OAAO,CAAC,WAAW,EAAE;gBACrB,OAAO,CAAC,eAAe,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,KAAK,CAAC;aAC7C,CAAC,CAAC;YAEH,OAAO,CAAC,IAAI,EAAE,CAAC;YAEf,IAAI,CAAC,SAAS,EAAE,CAAC;gBACf,OAAO,CAAC,IAAI,CAAC;oBACX,IAAI,EAAE,OAAO,CAAC,IAAI;oBAClB,MAAM,EAAE,MAAM;oBACd,OAAO,EAAE,mBAAmB;oBAC5B,OAAO,EAAE,SAAS;iBACnB,CAAC,CAAC;YACL,CAAC;iBAAM,IAAI,CAAC,aAAa,EAAE,CAAC;gBAC1B,OAAO,CAAC,IAAI,CAAC;oBACX,IAAI,EAAE,OAAO,CAAC,IAAI;oBAClB,MAAM,EAAE,MAAM;oBACd,OAAO,EAAE,iCAAiC;oBAC1C,OAAO,EAAE,SAAS;iBACnB,CAAC,CAAC;YACL,CAAC;iBAAM,CAAC;gBACN,MAAM,OAAO,GAAG,MAAM,OAAO,CAAC,UAAU,EAAE,CAAC;gBAC3C,OAAO,CAAC,IAAI,CAAC;oBACX,IAAI,EAAE,OAAO,CAAC,IAAI;oBAClB,MAAM,EAAE,MAAM;oBACd,OAAO,EAAE,WAAW,OAAO,IAAI,SAAS,GAAG;oBAC3C,OAAO,EAAE,SAAS;iBACnB,CAAC,CAAC;YACL,CAAC;QACH,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,CAAC,IAAI,EAAE,CAAC;YACf,OAAO,CAAC,IAAI,CAAC;gBACX,IAAI,EAAE,OAAO,CAAC,IAAI;gBAClB,MAAM,EAAE,MAAM;gBACd,OAAO,EAAE,yBAAyB;gBAClC,OAAO,EAAE,SAAS;aACnB,CAAC,CAAC;QACL,CAAC;IACH,CAAC;IAED,gBAAgB;IAChB,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;IAChB,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC;IACnC,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;IAEvC,KAAK,MAAM,MAAM,IAAI,OAAO,EAAE,CAAC;QAC7B,MAAM,IAAI,GACR,MAAM,CAAC,MAAM,KAAK,MAAM;YACtB,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC;YAClB,CAAC,CAAC,MAAM,CAAC,MAAM,KAAK,MAAM;gBACxB,CAAC,CAAC,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC;gBACnB,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QAEvB,OAAO,CAAC,GAAG,CAAC,KAAK,IAAI,IAAI,MAAM,CAAC,IAAI,KAAK,MAAM,CAAC,OAAO,EAAE,CAAC,CAAC;QAC3D,IAAI,MAAM,CAAC,OAAO,KAAK,SAAS,EAAE,CAAC;YACjC,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,SAAS,MAAM,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC;QACpD,CAAC;IACH,CAAC;IAED,UAAU;IACV,MAAM,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,KAAK,MAAM,CAAC,CAAC,MAAM,CAAC;IACjE,MAAM,QAAQ,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,KAAK,MAAM,CAAC,CAAC,MAAM,CAAC;IACnE,MAAM,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,KAAK,MAAM,CAAC,CAAC,MAAM,CAAC;IAEjE,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;IAChB,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;IACvC,OAAO,CAAC,GAAG,CACT,KAAK,KAAK,CAAC,KAAK,CAAC,MAAM,GAAG,SAAS,CAAC,KAAK;QACvC,GAAG,KAAK,CAAC,MAAM,CAAC,QAAQ,GAAG,WAAW,CAAC,KAAK;QAC5C,GAAG,KAAK,CAAC,GAAG,CAAC,MAAM,GAAG,SAAS,CAAC,EAAE,CACrC,CAAC;IACF,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;IAEhB,IAAI,MAAM,GAAG,CAAC,EAAE,CAAC;QACf,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;AACH,CAAC"}
1
+ {"version":3,"file":"doctor.js","sourceRoot":"","sources":["../../src/commands/doctor.ts"],"names":[],"mappings":"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"}
package/dist/index.js CHANGED
File without changes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@anastops/cli",
3
- "version": "2.0.1",
3
+ "version": "2.1.0",
4
4
  "description": "Infrastructure CLI for Anastops MCP Server setup and diagnostics",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -18,6 +18,8 @@
18
18
  "prebuild": "node scripts/copy-ranger.js",
19
19
  "build": "tsc",
20
20
  "dev": "tsc --watch",
21
+ "dev:tsx": "tsx src/index.ts",
22
+ "dev:tsx:watch": "tsx watch src/index.ts",
21
23
  "start": "node dist/index.js",
22
24
  "test": "vitest run",
23
25
  "test:watch": "vitest",
@@ -28,8 +30,8 @@
28
30
  },
29
31
  "dependencies": {
30
32
  "@anastops/core": "1.1.0",
31
- "@anastops/adapters": "1.1.0",
32
- "@anastops/mcp-server": "1.1.0",
33
+ "@anastops/adapters": "1.1.1",
34
+ "@anastops/mcp-server": "1.1.1",
33
35
  "commander": "^14.0.2",
34
36
  "enquirer": "^2.4.1",
35
37
  "chalk": "^5.6.2",
@@ -38,6 +40,7 @@
38
40
  },
39
41
  "devDependencies": {
40
42
  "@types/node": "^25.0.9",
43
+ "tsx": "^4.19.0",
41
44
  "typescript": "^5.9.3",
42
45
  "vitest": "^4.0.17"
43
46
  },
@@ -2,6 +2,9 @@
2
2
  Main Ranger TUI Application.
3
3
  """
4
4
 
5
+ import asyncio
6
+ import time
7
+
5
8
  from textual.app import App
6
9
  from textual.binding import Binding
7
10
  from textual.reactive import reactive
@@ -9,84 +12,172 @@ from textual.reactive import reactive
9
12
  from ranger_tui.config import config
10
13
  from ranger_tui.data import DataClient, get_client, Session
11
14
  from ranger_tui.theme import RANGER_THEME
12
- from ranger_tui.screens import DashboardScreen
15
+ from ranger_tui.screens import DashboardScreen, LoadingScreen
13
16
 
14
17
 
15
18
  class RangerApp(App):
16
19
  """
17
20
  Anastops Ranger TUI - Terminal dashboard for orchestration management.
18
-
21
+
19
22
  Navigation:
20
23
  - Arrow keys to move
21
24
  - Enter to select/open
22
25
  - Escape to go back
23
26
  - q to quit
24
27
  """
25
-
28
+
26
29
  TITLE = "Ranger"
27
30
  CSS_PATH = "styles/ranger.tcss"
28
-
31
+
29
32
  BINDINGS = [
30
33
  Binding("q", "quit", "Quit", show=True, priority=True),
31
34
  Binding("escape", "back", "Back", show=True),
32
35
  Binding("r", "refresh", "Refresh", show=True),
33
36
  ]
34
-
37
+
35
38
  # Reactive state
36
39
  sessions: reactive[list[Session]] = reactive(list, init=False)
37
40
  mongo_connected: reactive[bool] = reactive(False)
38
41
  redis_connected: reactive[bool] = reactive(False)
39
-
42
+
40
43
  def __init__(self):
41
44
  super().__init__()
42
45
  self.client: DataClient = get_client()
43
-
46
+ # Connection task tracking for non-blocking connection
47
+ self._connection_task: asyncio.Task | None = None
48
+ self._connection_start_time: float = 0.0
49
+ self._connection_timeout: float = 10.0 # seconds
50
+
44
51
  async def on_mount(self) -> None:
45
52
  """Called when app is mounted."""
46
53
  # Register and apply custom theme
47
54
  self.register_theme(RANGER_THEME)
48
55
  self.theme = "ranger"
49
-
50
- # Connect to databases
51
- await self._connect()
52
-
56
+
57
+ # Show loading screen immediately
58
+ await self.push_screen(LoadingScreen())
59
+
60
+ # Defer connection to allow loading screen to render
61
+ self.call_after_refresh(self._initialize_app)
62
+
63
+ async def _initialize_app(self) -> None:
64
+ """Initialize app after loading screen is rendered.
65
+
66
+ Uses non-blocking background task pattern to allow LoadingScreen
67
+ animation to run immediately while connection attempt proceeds.
68
+ """
69
+ # Get the loading screen instance
70
+ loading_screen = self.screen_stack[-1]
71
+
72
+ if isinstance(loading_screen, LoadingScreen):
73
+ loading_screen.set_status("Connecting to MongoDB...")
74
+
75
+ # Start connection in background WITHOUT awaiting it
76
+ # This allows the LoadingScreen animation to run immediately
77
+ self._connection_task = asyncio.create_task(self._connect())
78
+ self._connection_start_time = time.time()
79
+
80
+ # Schedule first check in 500ms - animation runs in parallel
81
+ self.set_timer(0.5, lambda: self._check_connection())
82
+
83
+ def _check_connection(self) -> None:
84
+ """Check connection status periodically (non-blocking).
85
+
86
+ Called by Textual's timer system, allowing animations to continue.
87
+ """
88
+ if self._connection_task is None:
89
+ return
90
+
91
+ loading_screen = self.screen_stack[-1] if self.screen_stack else None
92
+ elapsed = time.time() - self._connection_start_time
93
+
94
+ if self._connection_task.done():
95
+ # Connection completed - handle result asynchronously
96
+ asyncio.create_task(self._handle_connection_complete())
97
+ elif elapsed > self._connection_timeout:
98
+ # Timeout after configured seconds
99
+ self._connection_task.cancel()
100
+ if isinstance(loading_screen, LoadingScreen):
101
+ # Hide the spinner when connection fails
102
+ loading_screen.hide_spinner()
103
+ loading_screen.set_error(
104
+ "⚠ Connection timeout. Please start Docker with 'anastops init' and press 'q' to quit"
105
+ )
106
+ else:
107
+ # Not done yet, check again in 500ms
108
+ # This allows animation to continue running
109
+ self.set_timer(0.5, lambda: self._check_connection())
110
+
111
+ async def _handle_connection_complete(self) -> None:
112
+ """Handle completed connection task."""
113
+ loading_screen = self.screen_stack[-1] if self.screen_stack else None
114
+
115
+ # Get result and check for exceptions
116
+ if self._connection_task is not None:
117
+ try:
118
+ await self._connection_task
119
+ except asyncio.CancelledError:
120
+ return
121
+ except Exception as e:
122
+ if isinstance(loading_screen, LoadingScreen):
123
+ loading_screen.hide_spinner()
124
+ loading_screen.set_error(f"⚠ Connection error: {str(e)}")
125
+ return
126
+
127
+ # Check if connections succeeded
128
+ if not self.mongo_connected and not self.redis_connected:
129
+ if isinstance(loading_screen, LoadingScreen):
130
+ loading_screen.hide_spinner()
131
+ loading_screen.set_error(
132
+ "⚠ Connection failed. Please start Docker with 'anastops init' and press 'q' to quit"
133
+ )
134
+ return
135
+
136
+ # Success! Update status
137
+ if isinstance(loading_screen, LoadingScreen):
138
+ loading_screen.set_status("Connection established!")
139
+
140
+ # Small delay to show success message
141
+ await asyncio.sleep(0.5)
142
+
53
143
  # Start periodic refresh
54
144
  self.set_interval(config.refresh_interval, self._refresh_data)
55
145
  await self._refresh_data()
56
-
57
- # Push initial screen
146
+
147
+ # Transition to dashboard
148
+ self.pop_screen()
58
149
  await self.push_screen(DashboardScreen())
59
-
150
+
60
151
  async def _connect(self) -> None:
61
152
  """Connect to MongoDB and Redis."""
62
153
  await self.client.connect()
63
154
  health = await self.client.health_check()
64
155
  self.mongo_connected = health.get("mongodb", False)
65
156
  self.redis_connected = health.get("redis", False)
66
-
157
+
67
158
  async def _refresh_data(self) -> None:
68
159
  """Refresh data from databases."""
69
160
  if not self.client.is_connected:
70
161
  await self._connect()
71
-
162
+
72
163
  if self.client.is_connected:
73
164
  self.sessions = await self.client.get_sessions()
74
-
165
+
75
166
  # Update connection status
76
167
  health = await self.client.health_check()
77
168
  self.mongo_connected = health.get("mongodb", False)
78
169
  self.redis_connected = health.get("redis", False)
79
-
80
- def action_quit(self) -> None:
170
+
171
+ async def action_quit(self) -> None:
81
172
  """Quit the application."""
82
173
  self.exit()
83
-
174
+
84
175
  async def action_refresh(self) -> None:
85
176
  """Manually refresh data."""
86
177
  await self._refresh_data()
87
178
  self.notify("Refreshed")
88
-
89
- def action_back(self) -> None:
179
+
180
+ async def action_back(self) -> None:
90
181
  """Go back to previous screen (but not from dashboard)."""
91
182
  # Don't pop if we're on the dashboard (root screen)
92
183
  if len(self.screen_stack) > 2:
@@ -3,7 +3,7 @@ Data layer for Ranger TUI.
3
3
  """
4
4
 
5
5
  from .client import DataClient, get_client
6
- from .models import Session, Task, Agent, Artifact, SessionReport
6
+ from .models import Session, Task, Agent, Artifact, SessionReport, SessionMetadata
7
7
 
8
8
  __all__ = [
9
9
  "DataClient",
@@ -13,4 +13,5 @@ __all__ = [
13
13
  "Agent",
14
14
  "Artifact",
15
15
  "SessionReport",
16
+ "SessionMetadata",
16
17
  ]
@@ -124,16 +124,17 @@ class DataClient:
124
124
 
125
125
  cursor = self._db.sessions.find(query).sort("updated_at", -1).limit(limit)
126
126
  sessions = []
127
-
127
+
128
128
  async for doc in cursor:
129
129
  sessions.append(self._doc_to_session(doc))
130
-
130
+
131
131
  # Fetch task counts for all sessions in one aggregation
132
132
  if sessions:
133
133
  session_ids = [s.id for s in sessions]
134
134
  task_counts = await self._get_task_counts_by_session(session_ids)
135
-
136
- # Update session metadata with task counts
135
+ task_metrics = await self._get_task_metrics_by_session(session_ids)
136
+
137
+ # Update session metadata with task counts and metrics
137
138
  for session in sessions:
138
139
  counts = task_counts.get(session.id, {})
139
140
  session.metadata.tasks_total = counts.get("total", 0)
@@ -142,7 +143,11 @@ class DataClient:
142
143
  session.metadata.tasks_running = counts.get("running", 0)
143
144
  session.metadata.tasks_pending = counts.get("pending", 0)
144
145
  session.metadata.tasks_queued = counts.get("queued", 0)
145
-
146
+
147
+ metrics = task_metrics.get(session.id, {"total_tokens": 0, "total_cost": 0.0})
148
+ session.metadata.total_tokens = metrics["total_tokens"]
149
+ session.metadata.total_cost = metrics["total_cost"]
150
+
146
151
  return sessions
147
152
 
148
153
  async def _get_task_counts_by_session(
@@ -152,7 +157,7 @@ class DataClient:
152
157
  """Get task counts grouped by session and status."""
153
158
  if self._db is None:
154
159
  return {}
155
-
160
+
156
161
  pipeline = [
157
162
  {"$match": {"session_id": {"$in": session_ids}}},
158
163
  {"$group": {
@@ -160,20 +165,43 @@ class DataClient:
160
165
  "count": {"$sum": 1}
161
166
  }},
162
167
  ]
163
-
168
+
164
169
  result: dict[str, dict[str, int]] = {}
165
170
  async for doc in self._db.tasks.aggregate(pipeline):
166
171
  session_id = doc["_id"]["session_id"]
167
172
  status = doc["_id"]["status"]
168
173
  count = doc["count"]
169
-
174
+
170
175
  if session_id not in result:
171
176
  result[session_id] = {"total": 0, "completed": 0, "failed": 0, "running": 0, "pending": 0, "queued": 0}
172
-
177
+
173
178
  result[session_id][status] = count
174
179
  result[session_id]["total"] += count
175
-
180
+
176
181
  return result
182
+
183
+ async def _get_task_metrics_by_session(self, session_ids: list[str]) -> dict[str, dict]:
184
+ """Calculate total tokens and cost from tasks for each session."""
185
+ if self._db is None:
186
+ return {}
187
+
188
+ pipeline = [
189
+ {"$match": {"session_id": {"$in": session_ids}}},
190
+ {"$group": {
191
+ "_id": "$session_id",
192
+ "total_tokens": {"$sum": "$token_usage.total_tokens"},
193
+ "total_cost": {"$sum": "$token_usage.cost"}
194
+ }},
195
+ ]
196
+
197
+ cursor = self._db.tasks.aggregate(pipeline)
198
+ results = {}
199
+ async for doc in cursor:
200
+ results[doc["_id"]] = {
201
+ "total_tokens": doc["total_tokens"],
202
+ "total_cost": doc["total_cost"]
203
+ }
204
+ return results
177
205
 
178
206
  async def get_session(self, session_id: str) -> Optional[Session]:
179
207
  """Get a single session by ID."""
@@ -6,6 +6,7 @@ from .dashboard import DashboardScreen
6
6
  from .session import SessionScreen
7
7
  from .task import TaskScreen
8
8
  from .modals import NewSessionModal, NewTaskModal
9
+ from .loading import LoadingScreen
9
10
 
10
11
  __all__ = [
11
12
  "DashboardScreen",
@@ -13,4 +14,5 @@ __all__ = [
13
14
  "TaskScreen",
14
15
  "NewSessionModal",
15
16
  "NewTaskModal",
17
+ "LoadingScreen",
16
18
  ]
@@ -6,8 +6,8 @@ from datetime import datetime, UTC
6
6
  from textual.app import ComposeResult
7
7
  from textual.screen import Screen
8
8
  from textual.binding import Binding
9
- from textual.containers import Horizontal
10
- from textual.widgets import DataTable, Footer
9
+ from textual.containers import Horizontal, Vertical
10
+ from textual.widgets import DataTable, Footer, Static
11
11
 
12
12
  from ranger_tui.widgets import TopBar, Sidebar
13
13
  from ranger_tui.theme import format_status
@@ -15,16 +15,30 @@ from ranger_tui.theme import format_status
15
15
 
16
16
  class DashboardScreen(Screen):
17
17
  """Main dashboard screen showing all sessions."""
18
-
18
+
19
+ DEFAULT_CSS = """
20
+ .view-title {
21
+ height: 4;
22
+ content-align: center middle;
23
+ text-style: bold;
24
+ background: $surface;
25
+ border: tall $primary;
26
+ color: $primary;
27
+ text-align: center;
28
+ padding: 0 2;
29
+ }
30
+ """
31
+
19
32
  BINDINGS = [
20
33
  Binding("enter", "open_session", "Open", show=True),
21
34
  Binding("n", "new_session", "New", show=True),
22
35
  Binding("escape", "noop", show=False), # Disable escape on dashboard
23
36
  ]
24
-
37
+
25
38
  def compose(self) -> ComposeResult:
26
39
  """Compose the dashboard layout."""
27
40
  yield TopBar()
41
+ yield Static("[bold]S E S S I O N S[/]", classes="view-title", markup=True)
28
42
  with Horizontal(id="main"):
29
43
  yield DataTable(id="sessions-table", cursor_type="row")
30
44
  yield Sidebar(title="Summary")
@@ -0,0 +1,214 @@
1
+ """
2
+ Loading screen displayed during database connection.
3
+
4
+ Uses a wave animation (dots highlighting left to right) inside the loading box.
5
+ """
6
+
7
+ import logging
8
+ from typing import Optional
9
+
10
+ from textual.screen import Screen
11
+ from textual.app import ComposeResult
12
+ from textual.containers import Vertical, Horizontal, Container
13
+ from textual.widgets import Static
14
+ from textual.reactive import reactive
15
+ from textual.timer import Timer
16
+
17
+ from ranger_tui.widgets.ranger_image import RangerImage
18
+
19
+
20
+ logger = logging.getLogger(__name__)
21
+
22
+
23
+ class LoadingScreen(Screen):
24
+ """Loading screen with wave animation inside the box.
25
+
26
+ Uses a wave animation (dots highlighting left to right) inside the loading box.
27
+ """
28
+
29
+ CSS = """
30
+ LoadingScreen {
31
+ background: #000000;
32
+ align: center middle;
33
+ }
34
+
35
+ #loading-box {
36
+ width: auto;
37
+ min-width: 150;
38
+ height: auto;
39
+ border: round #3d5c8c;
40
+ padding: 2 4;
41
+ align: center middle;
42
+ }
43
+
44
+ #loading-inner {
45
+ width: 100%;
46
+ height: auto;
47
+ align: center middle;
48
+ }
49
+
50
+ #loading-title-row {
51
+ width: auto;
52
+ height: auto;
53
+ align: center middle;
54
+ }
55
+
56
+ #loading-title-anastops {
57
+ width: auto;
58
+ height: auto;
59
+ content-align: right middle;
60
+ padding-right: 3;
61
+ }
62
+
63
+ #loading-image-container {
64
+ width: auto;
65
+ height: auto;
66
+ align: center middle;
67
+ }
68
+
69
+ #loading-title-ranger {
70
+ width: auto;
71
+ height: auto;
72
+ content-align: left middle;
73
+ padding-left: 3;
74
+ }
75
+
76
+ #loading-progress-container {
77
+ width: 100%;
78
+ height: auto;
79
+ align: center middle;
80
+ margin-top: 6;
81
+ margin-bottom: 4;
82
+ }
83
+
84
+ #loading-animation {
85
+ width: 100%;
86
+ height: 1;
87
+ text-align: center;
88
+ content-align: center middle;
89
+ }
90
+
91
+ #loading-status-text {
92
+ width: 100%;
93
+ height: auto;
94
+ text-align: center;
95
+ content-align: center middle;
96
+ color: #7aa2f7;
97
+ margin-top: 1;
98
+ }
99
+ """
100
+
101
+ # Reactive variables
102
+ status_text: reactive[str] = reactive("Waiting for connection...")
103
+ loading_animation: reactive[str] = reactive("")
104
+
105
+ # Animation state
106
+ animation_timer: Optional[Timer] = None
107
+ animation_frame: int = 0
108
+
109
+ def _update_loading_animation(self) -> None:
110
+ """Update loading animation with wave pattern (dots highlighting left to right)."""
111
+ num_dots = 25
112
+
113
+ # Current position of the wave
114
+ current_pos = self.animation_frame % num_dots
115
+
116
+ # Build dots WITHOUT padding spaces - let CSS center them
117
+ dots = []
118
+ for i in range(num_dots):
119
+ # Calculate distance from current wave position
120
+ distance = abs(i - current_pos)
121
+
122
+ if distance == 0:
123
+ dots.append('[bold #7aa2f7]\u25cf[/]') # Full circle (bright blue)
124
+ elif distance == 1:
125
+ dots.append('[#5a7fa8]\u25cb[/]') # Empty circle (medium blue)
126
+ else:
127
+ dots.append('[dim #3d5c8c]\u00b7[/]') # Middle dot (dim blue)
128
+
129
+ # Join with single space between dots - CSS text-align: center will center this
130
+ self.loading_animation = ' '.join(dots)
131
+ self.animation_frame += 1
132
+
133
+ def watch_loading_animation(self, value: str) -> None:
134
+ """Update loading animation widget when it changes."""
135
+ try:
136
+ animation = self.query_one("#loading-animation", Static)
137
+ animation.update(value)
138
+ except Exception:
139
+ pass
140
+
141
+ def compose(self) -> ComposeResult:
142
+ with Container(id="loading-box"):
143
+ with Vertical(id="loading-inner"):
144
+ # Horizontal title layout: ANASTOPS [image] RANGER
145
+ with Horizontal(id="loading-title-row"):
146
+ # ANASTOPS title (blue) - large box-drawing ASCII art
147
+ anastops_title = (
148
+ "[bold #7aa2f7]"
149
+ "\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2557 \u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\n"
150
+ "\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255d\u255a\u2550\u2550\u2588\u2588\u2554\u2550\u2550\u255d\u2588\u2588\u2554\u2550\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255d\n"
151
+ "\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2551\u2588\u2588\u2554\u2588\u2588\u2557 \u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255d\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\n"
152
+ "\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2551\u2588\u2588\u2551\u255a\u2588\u2588\u2557\u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2551\u255a\u2550\u2550\u2550\u2550\u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2550\u255d \u255a\u2550\u2550\u2550\u2550\u2588\u2588\u2551\n"
153
+ "\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2551 \u255a\u2588\u2588\u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2551 \u2588\u2588\u2551 \u255a\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255d\u2588\u2588\u2551 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2551\n"
154
+ "\u255a\u2550\u255d \u255a\u2550\u255d\u255a\u2550\u255d \u255a\u2550\u2550\u2550\u255d\u255a\u2550\u255d \u255a\u2550\u255d\u255a\u2550\u2550\u2550\u2550\u2550\u2550\u255d \u255a\u2550\u255d \u255a\u2550\u2550\u2550\u2550\u2550\u255d \u255a\u2550\u255d \u255a\u2550\u2550\u2550\u2550\u2550\u2550\u255d[/]"
155
+ )
156
+ yield Static(anastops_title, id="loading-title-anastops", markup=True)
157
+
158
+ # Ranger image in center
159
+ with Container(id="loading-image-container"):
160
+ yield RangerImage()
161
+
162
+ # RANGER title (purple) - large box-drawing ASCII art
163
+ ranger_title = (
164
+ "[bold #bb9af7]"
165
+ "\u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2557 \u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2557 \n"
166
+ "\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255d \u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255d\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\n"
167
+ "\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255d\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2551\u2588\u2588\u2554\u2588\u2588\u2557 \u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255d\n"
168
+ "\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2551\u2588\u2588\u2551\u255a\u2588\u2588\u2557\u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u255d \u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\n"
169
+ "\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2551 \u255a\u2588\u2588\u2588\u2588\u2551\u255a\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255d\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2551 \u2588\u2588\u2551\n"
170
+ "\u255a\u2550\u255d \u255a\u2550\u255d\u255a\u2550\u255d \u255a\u2550\u255d\u255a\u2550\u255d \u255a\u2550\u2550\u2550\u255d \u255a\u2550\u2550\u2550\u2550\u2550\u255d \u255a\u2550\u2550\u2550\u2550\u2550\u2550\u255d\u255a\u2550\u255d \u255a\u2550\u255d[/]"
171
+ )
172
+ yield Static(ranger_title, id="loading-title-ranger", markup=True)
173
+
174
+ # Animation and status text
175
+ with Vertical(id="loading-progress-container"):
176
+ yield Static("", id="loading-animation", markup=True)
177
+ yield Static(self.status_text, id="loading-status-text", markup=True)
178
+
179
+ def on_mount(self) -> None:
180
+ """Start the loading animation."""
181
+ logger.debug("LoadingScreen mounted - starting wave loading animation")
182
+ self.animation_frame = 0
183
+ # Use 0.1 seconds (10 FPS) for smooth animation
184
+ self.animation_timer = self.set_interval(0.1, self._update_loading_animation)
185
+
186
+ def watch_status_text(self, value: str) -> None:
187
+ """Update status text when it changes."""
188
+ try:
189
+ status = self.query_one("#loading-status-text", Static)
190
+ status.update(value)
191
+ status.refresh(layout=True)
192
+ self.refresh(layout=True)
193
+ except Exception:
194
+ pass
195
+
196
+ def set_status(self, text: str) -> None:
197
+ """Set the status text."""
198
+ self.status_text = text
199
+
200
+ def set_error(self, error_text: str) -> None:
201
+ """Display error message."""
202
+ self.status_text = f"[bold #f7768e]{error_text}[/]"
203
+
204
+ def hide_spinner(self) -> None:
205
+ """Hide the loading animation."""
206
+ try:
207
+ # Stop the animation timer
208
+ if self.animation_timer is not None:
209
+ self.animation_timer.stop()
210
+ self.animation_timer = None
211
+ # Clear the animation widget
212
+ self.query_one("#loading-animation", Static).update("")
213
+ except Exception:
214
+ pass
@@ -7,7 +7,7 @@ from textual.app import ComposeResult
7
7
  from textual.screen import Screen
8
8
  from textual.binding import Binding
9
9
  from textual.containers import Horizontal
10
- from textual.widgets import DataTable, Footer, TabbedContent, TabPane, RichLog
10
+ from textual.widgets import DataTable, Footer, TabbedContent, TabPane, RichLog, Static
11
11
 
12
12
  from ranger_tui.data import SessionReport
13
13
  from ranger_tui.widgets import TopBar, Sidebar
@@ -16,22 +16,36 @@ from ranger_tui.theme import format_status, format_model, STATUS_SYMBOLS
16
16
 
17
17
  class SessionScreen(Screen):
18
18
  """Session detail screen with tasks and agents."""
19
-
19
+
20
+ DEFAULT_CSS = """
21
+ .view-title {
22
+ height: 4;
23
+ content-align: center middle;
24
+ text-style: bold;
25
+ background: $surface;
26
+ border: tall $primary;
27
+ color: $primary;
28
+ text-align: center;
29
+ padding: 0 2;
30
+ }
31
+ """
32
+
20
33
  BINDINGS = [
21
34
  Binding("enter", "open_task", "Open", show=True),
22
35
  Binding("t", "new_task", "New Task", show=True),
23
36
  ]
24
-
37
+
25
38
  session_id: str = ""
26
39
  report: SessionReport | None = None
27
-
40
+
28
41
  def __init__(self, session_id: str):
29
42
  super().__init__()
30
43
  self.session_id = session_id
31
-
44
+
32
45
  def compose(self) -> ComposeResult:
33
46
  """Compose the session detail layout."""
34
47
  yield TopBar(title="Loading...")
48
+ yield Static("[bold]S E S S I O N D E T A I L S[/]", classes="view-title", markup=True)
35
49
  with Horizontal(id="main"):
36
50
  with TabbedContent(id="tabs"):
37
51
  with TabPane("Tasks", id="tasks-tab"):
@@ -5,7 +5,7 @@ Task detail screen - View task details, input, output, and errors.
5
5
  from textual.app import ComposeResult
6
6
  from textual.screen import Screen
7
7
  from textual.containers import Horizontal
8
- from textual.widgets import Footer, TabbedContent, TabPane, RichLog, Markdown
8
+ from textual.widgets import Footer, TabbedContent, TabPane, RichLog, Markdown, Static
9
9
 
10
10
  from ranger_tui.data import Task
11
11
  from ranger_tui.widgets import TopBar, Sidebar
@@ -14,17 +14,31 @@ from ranger_tui.theme import STATUS_SYMBOLS
14
14
 
15
15
  class TaskScreen(Screen):
16
16
  """Task detail screen."""
17
-
17
+
18
+ DEFAULT_CSS = """
19
+ .view-title {
20
+ height: 4;
21
+ content-align: center middle;
22
+ text-style: bold;
23
+ background: $surface;
24
+ border: tall $primary;
25
+ color: $primary;
26
+ text-align: center;
27
+ padding: 0 2;
28
+ }
29
+ """
30
+
18
31
  task_id: str = ""
19
32
  task: Task | None = None
20
-
33
+
21
34
  def __init__(self, task_id: str):
22
35
  super().__init__()
23
36
  self.task_id = task_id
24
-
37
+
25
38
  def compose(self) -> ComposeResult:
26
39
  """Compose the task detail layout."""
27
40
  yield TopBar(title="Loading...")
41
+ yield Static("[bold]T A S K D E T A I L S[/]", classes="view-title", markup=True)
28
42
  with Horizontal(id="main"):
29
43
  with TabbedContent(id="tabs"):
30
44
  with TabPane("Output", id="output-tab"):
@@ -37,23 +37,19 @@ class TopBar(Widget):
37
37
  margin: 0 1;
38
38
  }
39
39
 
40
- #topbar-view-title {
41
- width: auto;
42
- height: 100%;
43
- content-align: center middle;
44
- margin-left: 2;
45
- }
46
-
47
40
  .topbar-spacer {
48
41
  width: 1fr;
49
42
  }
50
43
 
51
44
  #topbar-status-frame {
52
- width: auto;
45
+ width: 28;
46
+ min-width: 26;
47
+ max-width: 32;
53
48
  height: auto;
54
- align: center middle;
49
+ background: $panel;
55
50
  border: round $secondary-darken-2;
56
- padding: 0 1;
51
+ padding: 1 2;
52
+ content-align: center middle;
57
53
  }
58
54
 
59
55
  TopBar > .topbar-status, #topbar-status-frame .topbar-status {
@@ -74,60 +70,42 @@ class TopBar(Widget):
74
70
 
75
71
  mongo_ok: reactive[bool] = reactive(False)
76
72
  redis_ok: reactive[bool] = reactive(False)
77
- view_title: reactive[str] = reactive("")
78
-
79
- def __init__(self, title: str | None = None, view_title: str = "") -> None:
73
+
74
+ def __init__(self, title: str | None = None) -> None:
80
75
  """
81
76
  Initialize TopBar.
82
-
77
+
83
78
  Args:
84
79
  title: Optional custom title. If None, uses the standard logo.
85
- view_title: Current view name (e.g., "Sessions", "Tasks")
86
80
  """
87
81
  super().__init__()
88
82
  self._custom_title = title
89
- self.view_title = view_title
90
83
 
91
84
  def compose(self) -> ComposeResult:
92
85
  # Left spacer (for centering)
93
86
  yield Static("", classes="topbar-spacer")
94
-
87
+
95
88
  # ANASTOPS title
96
89
  line1_left = "[bold #00d4ff]▄▀█[/] [bold #00c8ff]█▄[/][bold #00c8ff]█[/] [bold #00bcff]▄▀█[/] [bold #00b0ff]█▀[/] [bold #00a4ff]▀█▀[/] [bold #0098ff]█▀█[/] [bold #008cff]█▀█[/] [bold #0080ff]█▀[/]"
97
90
  line2_left = "[bold #00d4ff]█▀█[/] [bold #00c8ff]█[/] [bold #00c8ff]█[/] [bold #00bcff]█▀█[/] [bold #00b0ff]▄█[/] [bold #00a4ff]█[/] [bold #0098ff]█▄█[/] [bold #008cff]█▀▀[/] [bold #0080ff]▄█[/]"
98
91
  yield Static(line1_left + "\n" + line2_left, id="topbar-title-left", classes="topbar-title-text", markup=True)
99
-
92
+
100
93
  # Ranger image in center with circle frame
101
94
  with Horizontal(id="topbar-brand"):
102
95
  yield RangerImage()
103
-
96
+
104
97
  # RANGER title
105
98
  line1_right = "[bold #ff00ff]█▀█[/] [bold #e600ff]▄▀█[/] [bold #cc00ff]█▄[/][bold #cc00ff]█[/] [bold #b300ff]█▀▀[/] [bold #9900ff]█▀▀[/] [bold #8000ff]█▀█[/]"
106
99
  line2_right = "[bold #ff00ff]█▀▄[/] [bold #e600ff]█▀█[/] [bold #cc00ff]█[/] [bold #cc00ff]█[/] [bold #b300ff]█▄█[/] [bold #9900ff]██▄[/] [bold #8000ff]█▀▄[/]"
107
100
  yield Static(line1_right + "\n" + line2_right, id="topbar-title-right", classes="topbar-title-text", markup=True)
108
-
101
+
109
102
  # Right spacer (for centering)
110
103
  yield Static("", classes="topbar-spacer")
111
-
104
+
112
105
  # Status frame on far right
113
106
  with Horizontal(id="topbar-status-frame"):
114
107
  yield Static("", classes="topbar-status", id="topbar-status", markup=True)
115
108
 
116
- def watch_view_title(self, value: str) -> None:
117
- """Update view title display."""
118
- try:
119
- view_widget = self.query_one("#topbar-view-title", Static)
120
- if value:
121
- view_widget.update(f"[bold]{value}[/]")
122
- else:
123
- view_widget.update("")
124
- except Exception:
125
- pass
126
-
127
- def set_view_title(self, title: str) -> None:
128
- """Set the current view title."""
129
- self.view_title = title
130
-
131
109
  def watch_mongo_ok(self, value: bool) -> None:
132
110
  """Update status when mongo connection changes."""
133
111
  self._update_status()