sidekick-docker 0.1.0 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -6,7 +6,9 @@
6
6
 
7
7
  A terminal dashboard for Docker. Manage containers, Compose projects, images, volumes, and networks — all from a single, keyboard-driven TUI.
8
8
 
9
- <!-- ![Sidekick Docker CLI Screenshot](../docs/screenshot-cli.png) -->
9
+ <p align="center">
10
+ <img src="../assets/sidekick_docker_cli.gif" alt="Sidekick Docker CLI Demo" width="800">
11
+ </p>
10
12
 
11
13
  ## Install
12
14
 
@@ -127,6 +129,10 @@ sidekick-docker --socket tcp://192.168.1.100:2375
127
129
 
128
130
  Full documentation is available at the [docs site](https://cesarandreslopez.github.io/sidekick-docker/).
129
131
 
132
+ ## See Also
133
+
134
+ **[Sidekick Agent Hub](https://github.com/cesarandreslopez/sidekick-agent-hub)** — Multi-provider AI coding agent monitor. Real-time visibility into Claude Code, OpenCode, and Codex CLI sessions with token tracking, context management, and session intelligence. Available as a [TUI on npm](https://www.npmjs.com/package/sidekick-agent-hub) and a [VS Code extension](https://marketplace.visualstudio.com/items?itemName=CesarAndresLopez.sidekick-for-max).
135
+
130
136
  ## Contributing
131
137
 
132
138
  Contributions are welcome! See [CONTRIBUTING.md](../CONTRIBUTING.md) for setup instructions and guidelines.
@@ -39925,6 +39925,89 @@ var require_ComposeClient = __commonJS({
39925
39925
  async ps(project) {
39926
39926
  return this.exec(["-p", project, "ps", "--format", "json"]);
39927
39927
  }
39928
+ async *streamLogs(project, service, tail = 100) {
39929
+ const args = ["-p", project, "logs", "--follow", "--tail", String(tail), "--timestamps"];
39930
+ if (service)
39931
+ args.push(service);
39932
+ const proc = (0, child_process_1.spawn)("docker", ["compose", ...args], {
39933
+ stdio: ["ignore", "pipe", "pipe"]
39934
+ });
39935
+ const parseLine = (line, stream) => {
39936
+ if (!line.trim())
39937
+ return null;
39938
+ const pipeIdx = line.indexOf("|");
39939
+ let message = line;
39940
+ let timestamp = null;
39941
+ if (pipeIdx > 0) {
39942
+ const after = line.substring(pipeIdx + 1).trimStart();
39943
+ const tsMatch = after.match(/^(\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}[\d.]*Z?)\s*(.*)/);
39944
+ if (tsMatch) {
39945
+ const parsed = new Date(tsMatch[1]);
39946
+ if (!isNaN(parsed.getTime())) {
39947
+ timestamp = parsed;
39948
+ message = `${line.substring(0, pipeIdx).trim()} | ${tsMatch[2]}`;
39949
+ } else {
39950
+ message = `${line.substring(0, pipeIdx).trim()} | ${after}`;
39951
+ }
39952
+ } else {
39953
+ message = `${line.substring(0, pipeIdx).trim()} | ${after}`;
39954
+ }
39955
+ }
39956
+ return { timestamp, stream, message };
39957
+ };
39958
+ let stdoutBuffer = "";
39959
+ let stderrBuffer = "";
39960
+ let done = false;
39961
+ const entries = [];
39962
+ let resolve = null;
39963
+ const push = (entry) => {
39964
+ entries.push(entry);
39965
+ resolve?.();
39966
+ };
39967
+ proc.stdout.on("data", (data) => {
39968
+ stdoutBuffer += data.toString();
39969
+ const lines = stdoutBuffer.split("\n");
39970
+ stdoutBuffer = lines.pop();
39971
+ for (const line of lines) {
39972
+ const entry = parseLine(line, "stdout");
39973
+ if (entry)
39974
+ push(entry);
39975
+ }
39976
+ });
39977
+ proc.stderr.on("data", (data) => {
39978
+ stderrBuffer += data.toString();
39979
+ const lines = stderrBuffer.split("\n");
39980
+ stderrBuffer = lines.pop();
39981
+ for (const line of lines) {
39982
+ const entry = parseLine(line, "stderr");
39983
+ if (entry)
39984
+ push(entry);
39985
+ }
39986
+ });
39987
+ proc.on("close", () => {
39988
+ done = true;
39989
+ resolve?.();
39990
+ });
39991
+ proc.on("error", () => {
39992
+ done = true;
39993
+ resolve?.();
39994
+ });
39995
+ try {
39996
+ while (!done) {
39997
+ if (entries.length === 0) {
39998
+ await new Promise((r) => {
39999
+ resolve = r;
40000
+ });
40001
+ resolve = null;
40002
+ }
40003
+ while (entries.length > 0) {
40004
+ yield entries.shift();
40005
+ }
40006
+ }
40007
+ } finally {
40008
+ proc.kill();
40009
+ }
40010
+ }
39928
40011
  };
39929
40012
  exports2.ComposeClient = ComposeClient2;
39930
40013
  }
@@ -77304,6 +77387,7 @@ var DockerState = class {
77304
77387
  networks = [];
77305
77388
  composeProjects = [];
77306
77389
  selectedLogs = [];
77390
+ selectedComposeLogs = [];
77307
77391
  inspectedEnv = /* @__PURE__ */ new Map();
77308
77392
  lastRefresh = null;
77309
77393
  daemonConnected = false;
@@ -77407,6 +77491,15 @@ var DockerState = class {
77407
77491
  clearLogs() {
77408
77492
  this.selectedLogs = [];
77409
77493
  }
77494
+ appendComposeLog(entry) {
77495
+ this.selectedComposeLogs.push(entry);
77496
+ if (this.selectedComposeLogs.length > 1e3) {
77497
+ this.selectedComposeLogs.shift();
77498
+ }
77499
+ }
77500
+ clearComposeLogs() {
77501
+ this.selectedComposeLogs = [];
77502
+ }
77410
77503
  getStatsCollector() {
77411
77504
  return this.statsCollector;
77412
77505
  }
@@ -77426,6 +77519,7 @@ var DockerState = class {
77426
77519
  statsCollector: this.statsCollector,
77427
77520
  inspectedEnv: this.inspectedEnv,
77428
77521
  selectedContainerLogs: [...this.selectedLogs],
77522
+ selectedComposeLogs: [...this.selectedComposeLogs],
77429
77523
  lastRefresh: this.lastRefresh,
77430
77524
  daemonConnected: this.daemonConnected
77431
77525
  };
@@ -77767,9 +77861,12 @@ var ServicesPanel = class {
77767
77861
  },
77768
77862
  {
77769
77863
  label: "Logs",
77770
- render: () => {
77771
- return "Compose service logs will stream here when selected.";
77772
- }
77864
+ render: (_item, metrics) => {
77865
+ const logs = metrics.selectedComposeLogs;
77866
+ if (logs.length === 0) return "No compose logs. Logs will appear when a service produces output.";
77867
+ return logs.map((e) => colorizeLogEntry(e)).join("\n");
77868
+ },
77869
+ autoScrollBottom: true
77773
77870
  }
77774
77871
  ];
77775
77872
  getItems(metrics) {
@@ -78218,6 +78315,55 @@ var StatsStreamManager = class {
78218
78315
  }
78219
78316
  };
78220
78317
 
78318
+ // src/dashboard/ComposeLogStreamManager.ts
78319
+ var MAX_LOG_LINES2 = 1e3;
78320
+ var ComposeLogStreamManager = class {
78321
+ composeClient;
78322
+ currentProject = null;
78323
+ currentService = null;
78324
+ logs = [];
78325
+ aborted = false;
78326
+ onChange;
78327
+ constructor(composeClient, onChange) {
78328
+ this.composeClient = composeClient;
78329
+ this.onChange = onChange;
78330
+ }
78331
+ async select(project, service) {
78332
+ if (project === this.currentProject && service === this.currentService) return;
78333
+ this.stop();
78334
+ this.currentProject = project;
78335
+ this.currentService = service;
78336
+ this.logs = [];
78337
+ if (!project) return;
78338
+ this.aborted = false;
78339
+ this.streamLogs(project, service);
78340
+ }
78341
+ async streamLogs(project, service) {
78342
+ try {
78343
+ for await (const entry of this.composeClient.streamLogs(project, service ?? void 0)) {
78344
+ if (this.aborted || this.currentProject !== project) break;
78345
+ this.logs.push(entry);
78346
+ if (this.logs.length > MAX_LOG_LINES2) {
78347
+ this.logs.shift();
78348
+ }
78349
+ this.onChange();
78350
+ }
78351
+ } catch {
78352
+ }
78353
+ }
78354
+ stop() {
78355
+ this.aborted = true;
78356
+ this.currentProject = null;
78357
+ this.currentService = null;
78358
+ }
78359
+ getLogs() {
78360
+ return this.logs;
78361
+ }
78362
+ dispose() {
78363
+ this.stop();
78364
+ }
78365
+ };
78366
+
78221
78367
  // src/dashboard/ink/Dashboard.tsx
78222
78368
  var import_react35 = __toESM(require_react(), 1);
78223
78369
  await init_build2();
@@ -79415,8 +79561,8 @@ function Dashboard({ panels, metrics, onSelectionChange, execTriggerRef, onExecF
79415
79561
  )
79416
79562
  ] })
79417
79563
  ] }),
79418
- state.overlay === "help" && /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(HelpOverlay, { panels, activePanelIndex: state.activePanelIndex, version: "0.1.0" }),
79419
- state.overlay === "version" && /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(VersionOverlay, { version: "0.1.0" }),
79564
+ state.overlay === "help" && /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(HelpOverlay, { panels, activePanelIndex: state.activePanelIndex, version: "0.1.1" }),
79565
+ state.overlay === "version" && /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(VersionOverlay, { version: "0.1.1" }),
79420
79566
  state.overlay === "exec" && /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
79421
79567
  ExecOverlay,
79422
79568
  {
@@ -79434,7 +79580,7 @@ function Dashboard({ panels, metrics, onSelectionChange, execTriggerRef, onExecF
79434
79580
  filterString: state.filterString,
79435
79581
  containerCount: metrics.containers.length,
79436
79582
  runningCount,
79437
- version: "0.1.0",
79583
+ version: "0.1.1",
79438
79584
  matchCount: state.filterString ? currentItems.length : void 0,
79439
79585
  totalCount: state.filterString ? totalItemCount : void 0,
79440
79586
  lastRefresh: metrics.lastRefresh
@@ -79490,10 +79636,18 @@ async function dashboardAction(_opts, cmd) {
79490
79636
  const statsManager = new StatsStreamManager(client, state.getStatsCollector(), () => {
79491
79637
  scheduleRender();
79492
79638
  });
79639
+ const composeLogManager = new ComposeLogStreamManager(composeClient, () => {
79640
+ state.clearComposeLogs();
79641
+ for (const entry of composeLogManager.getLogs()) {
79642
+ state.appendComposeLog(entry);
79643
+ }
79644
+ scheduleRender();
79645
+ });
79493
79646
  const onSelectionChange = (panelId, itemId) => {
79494
79647
  if (panelId === "containers") {
79495
79648
  logManager.select(itemId);
79496
79649
  statsManager.select(itemId);
79650
+ composeLogManager.select(null, null);
79497
79651
  if (itemId && !state.getInspectedEnv(itemId)) {
79498
79652
  client.inspectContainer(itemId).then((info) => {
79499
79653
  state.setInspectedEnv(itemId, info.Config.Env || []);
@@ -79501,9 +79655,19 @@ async function dashboardAction(_opts, cmd) {
79501
79655
  }).catch(() => {
79502
79656
  });
79503
79657
  }
79658
+ } else if (panelId === "services" && itemId) {
79659
+ logManager.select(null);
79660
+ statsManager.select(null);
79661
+ const parts = itemId.split(":");
79662
+ if (parts[0] === "project") {
79663
+ composeLogManager.select(parts.slice(1).join(":"), null);
79664
+ } else if (parts[0] === "service") {
79665
+ composeLogManager.select(parts[1], parts.slice(2).join(":"));
79666
+ }
79504
79667
  } else {
79505
79668
  logManager.select(null);
79506
79669
  statsManager.select(null);
79670
+ composeLogManager.select(null, null);
79507
79671
  }
79508
79672
  };
79509
79673
  const panels = [
@@ -79593,6 +79757,10 @@ async function dashboardAction(_opts, cmd) {
79593
79757
  statsManager.dispose();
79594
79758
  } catch {
79595
79759
  }
79760
+ try {
79761
+ composeLogManager.dispose();
79762
+ } catch {
79763
+ }
79596
79764
  try {
79597
79765
  clearInterval(refreshInterval);
79598
79766
  } catch {
@@ -79677,7 +79845,7 @@ async function logsAction(container, opts) {
79677
79845
 
79678
79846
  // src/cli.ts
79679
79847
  var program2 = new Command();
79680
- program2.name("sidekick-docker").description("Docker management TUI dashboard").version("0.1.0").option("--socket <path>", "Docker socket path").action(async (_opts, cmd) => {
79848
+ program2.name("sidekick-docker").description("Docker management TUI dashboard").version("0.1.1").option("--socket <path>", "Docker socket path").action(async (_opts, cmd) => {
79681
79849
  await dashboardAction(_opts, cmd);
79682
79850
  });
79683
79851
  program2.command("ps").description("List containers").option("-a, --all", "Show all containers (default: running only)", false).action(async (opts) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sidekick-docker",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "Docker management TUI dashboard",
5
5
  "author": "Cesar Andres Lopez <cesarandreslopez@gmail.com>",
6
6
  "repository": {