@paretools/docker 0.8.5 → 0.10.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 (68) hide show
  1. package/README.md +1 -1
  2. package/dist/index.js +1 -1
  3. package/dist/index.js.map +1 -1
  4. package/dist/lib/docker-runner.d.ts +1 -1
  5. package/dist/lib/docker-runner.d.ts.map +1 -1
  6. package/dist/lib/docker-runner.js +2 -2
  7. package/dist/lib/docker-runner.js.map +1 -1
  8. package/dist/lib/formatters.d.ts +62 -21
  9. package/dist/lib/formatters.d.ts.map +1 -1
  10. package/dist/lib/formatters.js +369 -52
  11. package/dist/lib/formatters.js.map +1 -1
  12. package/dist/lib/parsers.d.ts +50 -23
  13. package/dist/lib/parsers.d.ts.map +1 -1
  14. package/dist/lib/parsers.js +852 -129
  15. package/dist/lib/parsers.js.map +1 -1
  16. package/dist/schemas/index.d.ts +267 -9
  17. package/dist/schemas/index.d.ts.map +1 -1
  18. package/dist/schemas/index.js +186 -7
  19. package/dist/schemas/index.js.map +1 -1
  20. package/dist/tools/build.d.ts.map +1 -1
  21. package/dist/tools/build.js +122 -21
  22. package/dist/tools/build.js.map +1 -1
  23. package/dist/tools/compose-build.d.ts.map +1 -1
  24. package/dist/tools/compose-build.js +67 -8
  25. package/dist/tools/compose-build.js.map +1 -1
  26. package/dist/tools/compose-down.d.ts.map +1 -1
  27. package/dist/tools/compose-down.js +39 -7
  28. package/dist/tools/compose-down.js.map +1 -1
  29. package/dist/tools/compose-logs.d.ts.map +1 -1
  30. package/dist/tools/compose-logs.js +52 -7
  31. package/dist/tools/compose-logs.js.map +1 -1
  32. package/dist/tools/compose-ps.d.ts.map +1 -1
  33. package/dist/tools/compose-ps.js +65 -8
  34. package/dist/tools/compose-ps.js.map +1 -1
  35. package/dist/tools/compose-up.d.ts.map +1 -1
  36. package/dist/tools/compose-up.js +83 -6
  37. package/dist/tools/compose-up.js.map +1 -1
  38. package/dist/tools/exec.d.ts.map +1 -1
  39. package/dist/tools/exec.js +66 -17
  40. package/dist/tools/exec.js.map +1 -1
  41. package/dist/tools/images.d.ts.map +1 -1
  42. package/dist/tools/images.js +33 -11
  43. package/dist/tools/images.js.map +1 -1
  44. package/dist/tools/inspect.d.ts.map +1 -1
  45. package/dist/tools/inspect.js +43 -16
  46. package/dist/tools/inspect.js.map +1 -1
  47. package/dist/tools/logs.d.ts.map +1 -1
  48. package/dist/tools/logs.js +34 -11
  49. package/dist/tools/logs.js.map +1 -1
  50. package/dist/tools/network-ls.d.ts.map +1 -1
  51. package/dist/tools/network-ls.js +23 -10
  52. package/dist/tools/network-ls.js.map +1 -1
  53. package/dist/tools/ps.d.ts.map +1 -1
  54. package/dist/tools/ps.js +21 -6
  55. package/dist/tools/ps.js.map +1 -1
  56. package/dist/tools/pull.d.ts.map +1 -1
  57. package/dist/tools/pull.js +16 -14
  58. package/dist/tools/pull.js.map +1 -1
  59. package/dist/tools/run.d.ts.map +1 -1
  60. package/dist/tools/run.js +107 -10
  61. package/dist/tools/run.js.map +1 -1
  62. package/dist/tools/stats.d.ts.map +1 -1
  63. package/dist/tools/stats.js +21 -6
  64. package/dist/tools/stats.js.map +1 -1
  65. package/dist/tools/volume-ls.d.ts.map +1 -1
  66. package/dist/tools/volume-ls.js +23 -10
  67. package/dist/tools/volume-ls.js.map +1 -1
  68. package/package.json +2 -2
@@ -6,11 +6,15 @@ export function formatPs(data) {
6
6
  const ports = portsArr.length
7
7
  ? ` [${portsArr.map((p) => (p.host ? `${p.host}->${p.container}/${p.protocol}` : `${p.container}/${p.protocol}`)).join(", ")}]`
8
8
  : "";
9
- lines.push(` ${(c.state ?? "unknown").padEnd(10)} ${c.name} (${c.image})${ports}`);
9
+ // #117/#118: Show labels count and networks
10
+ const labelsInfo = c.labels && Object.keys(c.labels).length > 0 ? ` labels=${Object.keys(c.labels).length}` : "";
11
+ const networksInfo = c.networks && c.networks.length > 0 ? ` nets=${c.networks.join(",")}` : "";
12
+ lines.push(` ${(c.state ?? "unknown").padEnd(10)} ${c.name} (${c.image})${ports}${labelsInfo}${networksInfo}`);
10
13
  }
11
14
  return lines.join("\n");
12
15
  }
13
- /** Formats structured Docker build results into a human-readable success/failure summary. */
16
+ /** Formats structured Docker build results into a human-readable success/failure summary.
17
+ * #97: Includes structured error details with line numbers. */
14
18
  export function formatBuild(data) {
15
19
  if (data.success) {
16
20
  const parts = [`Build succeeded in ${data.duration}s`];
@@ -18,68 +22,158 @@ export function formatBuild(data) {
18
22
  parts[0] += ` → ${data.imageId}`;
19
23
  if (data.steps)
20
24
  parts.push(`${data.steps} steps`);
25
+ if (data.cacheHits != null || data.cacheMisses != null) {
26
+ parts.push(`cache hits=${data.cacheHits ?? 0}, misses=${data.cacheMisses ?? 0}`);
27
+ }
21
28
  return parts.join(", ");
22
29
  }
23
30
  const lines = [`Build failed (${data.duration}s)`];
31
+ if (data.cacheHits != null || data.cacheMisses != null) {
32
+ lines.push(` cache hits=${data.cacheHits ?? 0}, misses=${data.cacheMisses ?? 0}`);
33
+ }
24
34
  for (const err of data.errors ?? []) {
25
- lines.push(` ${err}`);
35
+ if (typeof err === "string") {
36
+ lines.push(` ${err}`);
37
+ }
38
+ else {
39
+ const lineInfo = err.line != null ? `:${err.line}` : "";
40
+ const fileInfo = err.dockerfile
41
+ ? `${err.dockerfile}${lineInfo}`
42
+ : lineInfo
43
+ ? `line${lineInfo}`
44
+ : "";
45
+ const prefix = fileInfo ? `${fileInfo}: ` : "";
46
+ lines.push(` ${prefix}${err.message}`);
47
+ }
26
48
  }
27
49
  return lines.join("\n");
28
50
  }
29
- /** Formats structured Docker logs data into a human-readable output with container name and line count. */
51
+ /** Formats structured Docker logs data into a human-readable output with container name and line count.
52
+ * #113: Notes when separate stdout/stderr streams are available. */
30
53
  export function formatLogs(data) {
31
54
  const header = data.isTruncated
32
55
  ? `${data.container} (${data.total} of ${data.totalLines} lines, truncated)`
33
56
  : `${data.container} (${data.total} lines)`;
34
- return `${header}\n${(data.lines ?? []).join("\n")}`;
35
- }
36
- /** Formats structured Docker image data into a human-readable listing with repository, tag, and size. */
57
+ const streamInfo = data.stdoutLines || data.stderrLines
58
+ ? ` [stdout=${(data.stdoutLines ?? []).length}, stderr=${(data.stderrLines ?? []).length}]`
59
+ : "";
60
+ const body = data.entries
61
+ ? data.entries.map((e) => `${e.timestamp ? `${e.timestamp} ` : ""}${e.message}`).join("\n")
62
+ : (data.lines ?? []).join("\n");
63
+ return `${header}${streamInfo}\n${body}`;
64
+ }
65
+ /** Formats structured Docker image data into a human-readable listing with repository, tag, and size.
66
+ * #110: Shows ISO creation timestamp when available. */
37
67
  export function formatImages(data) {
38
68
  if (data.total === 0)
39
69
  return "No images found.";
40
70
  const lines = [`${data.total} images:`];
41
71
  for (const img of data.images) {
42
72
  const tag = img.tag && img.tag !== "<none>" ? `:${img.tag}` : "";
43
- lines.push(` ${img.repository}${tag} (${img.size}, ${img.created})`);
73
+ const digest = img.digest ? ` [${img.digest.slice(0, 19)}...]` : "";
74
+ const createdAt = img.createdAt ? ` (${img.createdAt})` : ` (${img.created})`;
75
+ const bytes = img.sizeBytes != null ? ` ~${img.sizeBytes}B` : "";
76
+ const labelCount = img.labels && Object.keys(img.labels).length > 0
77
+ ? ` labels=${Object.keys(img.labels).length}`
78
+ : "";
79
+ lines.push(` ${img.repository}${tag} (${img.size}${bytes})${createdAt}${digest}${labelCount}`);
44
80
  }
45
81
  return lines.join("\n");
46
82
  }
47
- /** Formats structured Docker run output into a human-readable summary. */
83
+ /** Formats structured Docker run output into a human-readable summary.
84
+ * #121/#122: Shows exitCode, errorCategory, and stdout/stderr for non-detached runs. */
48
85
  export function formatRun(data) {
49
86
  const name = data.name ? ` (${data.name})` : "";
50
87
  const mode = data.detached ? "detached" : "attached";
51
- return `Container ${data.containerId}${name} started from ${data.image} [${mode}]`;
88
+ const lines = [`Container ${data.containerId}${name} started from ${data.image} [${mode}]`];
89
+ if (data.exitCode != null && data.exitCode !== 0) {
90
+ lines.push(` Exit code: ${data.exitCode}`);
91
+ if (data.errorCategory)
92
+ lines.push(` Error: ${data.errorCategory}`);
93
+ if (data.stderr)
94
+ lines.push(` stderr: ${data.stderr.slice(0, 200)}`);
95
+ }
96
+ else if (!data.detached) {
97
+ if (data.stdout)
98
+ lines.push(data.stdout);
99
+ if (data.stderr)
100
+ lines.push(`stderr: ${data.stderr}`);
101
+ }
102
+ return lines.join("\n");
52
103
  }
53
- /** Formats structured Docker exec output into a human-readable summary. */
104
+ /** Formats structured Docker exec output into a human-readable summary.
105
+ * #108: Shows truncation indicator. */
54
106
  export function formatExec(data) {
55
- const status = data.success ? "succeeded" : `failed (exit code ${data.exitCode})`;
56
- const lines = [`Exec ${status}`];
107
+ const status = data.timedOut
108
+ ? "timed out"
109
+ : data.success
110
+ ? "succeeded"
111
+ : `failed (exit code ${data.exitCode})`;
112
+ const dur = data.duration != null ? ` in ${data.duration}s` : "";
113
+ const trunc = data.isTruncated ? " [truncated]" : "";
114
+ const lines = [`Exec ${status}${dur}${trunc}`];
57
115
  if (data.stdout)
58
116
  lines.push(data.stdout);
59
117
  if (data.stderr)
60
118
  lines.push(`stderr: ${data.stderr}`);
119
+ if (data.json !== undefined)
120
+ lines.push(`json: ${JSON.stringify(data.json)}`);
121
+ if (data.parseJsonError)
122
+ lines.push(`json-parse-error: ${data.parseJsonError}`);
61
123
  return lines.join("\n");
62
124
  }
63
- /** Formats structured Docker Compose up output into a human-readable summary. */
125
+ /** Formats structured Docker Compose up output into a human-readable summary.
126
+ * #107: Shows per-service state details when available. */
64
127
  export function formatComposeUp(data) {
65
128
  if (!data.success)
66
129
  return "Compose up failed";
67
130
  if (data.started === 0)
68
131
  return "Compose up succeeded (no new services started)";
69
- return `Compose up: ${data.started} services started (${(data.services ?? []).join(", ")})`;
132
+ const lines = [
133
+ `Compose up: ${data.started} services started (${(data.services ?? []).join(", ")})`,
134
+ ];
135
+ if (data.serviceStates && data.serviceStates.length > 0) {
136
+ for (const ss of data.serviceStates) {
137
+ lines.push(` ${ss.name}: ${ss.action}`);
138
+ }
139
+ }
140
+ if (data.networksCreated != null)
141
+ lines.push(` networks created: ${data.networksCreated}`);
142
+ if (data.volumesCreated != null)
143
+ lines.push(` volumes created: ${data.volumesCreated}`);
144
+ return lines.join("\n");
70
145
  }
71
- /** Formats structured Docker Compose down output into a human-readable summary. */
146
+ /** Formats structured Docker Compose down output into a human-readable summary.
147
+ * #100: Shows per-container details.
148
+ * #101: Shows separate volume and network counts. */
72
149
  export function formatComposeDown(data) {
73
150
  if (!data.success)
74
151
  return "Compose down failed";
75
- return `Compose down: ${data.stopped} stopped, ${data.removed} removed`;
152
+ const parts = [`Compose down: ${data.stopped} stopped, ${data.removed} removed`];
153
+ if (data.volumesRemoved)
154
+ parts[0] += `, ${data.volumesRemoved} volumes removed`;
155
+ if (data.networksRemoved)
156
+ parts[0] += `, ${data.networksRemoved} networks removed`;
157
+ if (data.containers && data.containers.length > 0) {
158
+ for (const c of data.containers) {
159
+ parts.push(` ${c.name}: ${c.action}`);
160
+ }
161
+ }
162
+ return parts.join("\n");
76
163
  }
77
- /** Formats structured Docker pull output into a human-readable summary. */
164
+ /** Formats structured Docker pull output into a human-readable summary.
165
+ * #120: Shows size when available. */
78
166
  export function formatPull(data) {
79
- if (!data.success)
80
- return `Pull failed for ${data.image}:${data.tag}`;
167
+ if (!data.success) {
168
+ const errType = data.errorType ? ` (${data.errorType})` : "";
169
+ const msg = data.errorMessage ? `: ${data.errorMessage}` : "";
170
+ return `Pull failed for ${data.image}:${data.tag}${errType}${msg}`;
171
+ }
81
172
  const digest = data.digest ? ` (${data.digest.slice(0, 19)}...)` : "";
82
- return `Pulled ${data.image}:${data.tag}${digest}`;
173
+ const size = data.size ? ` [${data.size}]` : "";
174
+ if (data.status === "up-to-date")
175
+ return `${data.image}:${data.tag} is up to date${digest}`;
176
+ return `Pulled ${data.image}:${data.tag}${digest}${size}`;
83
177
  }
84
178
  export function compactPsMap(data) {
85
179
  return {
@@ -128,14 +222,22 @@ export function compactBuildMap(data) {
128
222
  ...(data.imageId ? { imageId: data.imageId } : {}),
129
223
  duration: data.duration,
130
224
  errorCount: (data.errors ?? []).length,
225
+ ...(data.cacheHits != null ? { cacheHits: data.cacheHits } : {}),
226
+ ...(data.cacheMisses != null ? { cacheMisses: data.cacheMisses } : {}),
131
227
  };
132
228
  }
133
229
  export function formatBuildCompact(data) {
134
230
  if (data.success) {
135
231
  const id = data.imageId ? ` → ${data.imageId}` : "";
136
- return `Build succeeded in ${data.duration}s${id}`;
232
+ const cache = data.cacheHits != null || data.cacheMisses != null
233
+ ? ` [cache h=${data.cacheHits ?? 0} m=${data.cacheMisses ?? 0}]`
234
+ : "";
235
+ return `Build succeeded in ${data.duration}s${id}${cache}`;
137
236
  }
138
- return `Build failed (${data.duration}s, ${data.errorCount} errors)`;
237
+ const cache = data.cacheHits != null || data.cacheMisses != null
238
+ ? `, cache h=${data.cacheHits ?? 0} m=${data.cacheMisses ?? 0}`
239
+ : "";
240
+ return `Build failed (${data.duration}s, ${data.errorCount} errors${cache})`;
139
241
  }
140
242
  export function compactLogsMap(data) {
141
243
  const HEAD_SIZE = 5;
@@ -160,13 +262,21 @@ export function compactPullMap(data) {
160
262
  return {
161
263
  image: data.image,
162
264
  tag: data.tag,
265
+ ...(data.digest ? { digest: data.digest } : {}),
266
+ status: data.status,
163
267
  success: data.success,
268
+ ...(data.errorType ? { errorType: data.errorType } : {}),
164
269
  };
165
270
  }
166
271
  export function formatPullCompact(data) {
167
- if (!data.success)
168
- return `Pull failed for ${data.image}:${data.tag}`;
169
- return `Pulled ${data.image}:${data.tag}`;
272
+ if (!data.success) {
273
+ const err = data.errorType ? ` (${data.errorType})` : "";
274
+ return `Pull failed for ${data.image}:${data.tag}${err}`;
275
+ }
276
+ const digest = data.digest ? ` (${data.digest.slice(0, 19)}...)` : "";
277
+ if (data.status === "up-to-date")
278
+ return `${data.image}:${data.tag} is up to date${digest}`;
279
+ return `Pulled ${data.image}:${data.tag}${digest}`;
170
280
  }
171
281
  export function compactRunMap(data) {
172
282
  return {
@@ -183,21 +293,43 @@ export function compactExecMap(data) {
183
293
  return {
184
294
  exitCode: data.exitCode,
185
295
  success: data.success,
296
+ ...(data.duration != null ? { duration: data.duration } : {}),
297
+ ...(data.timedOut ? { timedOut: true } : {}),
298
+ ...(data.isTruncated ? { isTruncated: true } : {}),
299
+ ...(data.stdout ? { stdoutPreview: data.stdout.slice(0, 200) } : {}),
300
+ ...(data.stderr ? { stderrPreview: data.stderr.slice(0, 200) } : {}),
186
301
  };
187
302
  }
188
303
  export function formatExecCompact(data) {
189
- return data.success ? "Exec succeeded" : `Exec failed (exit code ${data.exitCode})`;
304
+ const dur = data.duration != null ? ` in ${data.duration}s` : "";
305
+ const status = data.timedOut
306
+ ? "timed out"
307
+ : data.success
308
+ ? "succeeded"
309
+ : `failed (exit code ${data.exitCode})`;
310
+ const trunc = data.isTruncated ? " [truncated]" : "";
311
+ const lines = [`Exec ${status}${dur}${trunc}`];
312
+ if (data.stdoutPreview)
313
+ lines.push(data.stdoutPreview);
314
+ if (data.stderrPreview)
315
+ lines.push(`stderr: ${data.stderrPreview}`);
316
+ return lines.join("\n");
190
317
  }
191
318
  export function compactComposeUpMap(data) {
192
319
  return {
193
320
  success: data.success,
194
321
  started: data.started,
322
+ ...(data.networksCreated != null ? { networksCreated: data.networksCreated } : {}),
323
+ ...(data.volumesCreated != null ? { volumesCreated: data.volumesCreated } : {}),
195
324
  };
196
325
  }
197
326
  export function formatComposeUpCompact(data) {
198
327
  if (!data.success)
199
328
  return "Compose up failed";
200
- return `Compose up: ${data.started} services started`;
329
+ const infra = data.networksCreated != null || data.volumesCreated != null
330
+ ? ` (networks=${data.networksCreated ?? 0}, volumes=${data.volumesCreated ?? 0})`
331
+ : "";
332
+ return `Compose up: ${data.started} services started${infra}`;
201
333
  }
202
334
  export function compactComposeDownMap(data) {
203
335
  return {
@@ -212,8 +344,24 @@ export function formatComposeDownCompact(data) {
212
344
  return `Compose down: ${data.stopped} stopped, ${data.removed} removed`;
213
345
  }
214
346
  // ── Inspect ──────────────────────────────────────────────────────────
215
- /** Formats structured Docker inspect data into a human-readable summary. */
347
+ /** Formats structured Docker inspect data into a human-readable summary.
348
+ * Handles both container and image inspect results. */
216
349
  export function formatInspect(data) {
350
+ if (data.inspectType === "image") {
351
+ return formatImageInspect(data);
352
+ }
353
+ if (data.inspectType === "volume") {
354
+ return formatVolumeInspect(data);
355
+ }
356
+ if (data.inspectType === "network") {
357
+ return formatNetworkInspect(data);
358
+ }
359
+ return formatContainerInspect(data);
360
+ }
361
+ /** Formats a container inspect result.
362
+ * #111: Shows network settings.
363
+ * #112: Shows mount information. */
364
+ function formatContainerInspect(data) {
217
365
  const lines = [`${data.name} (${data.id})`];
218
366
  lines.push(` Image: ${data.image}`);
219
367
  const state = data.state;
@@ -222,38 +370,166 @@ export function formatInspect(data) {
222
370
  if (state.startedAt)
223
371
  lines.push(` Started: ${state.startedAt}`);
224
372
  }
373
+ if (data.healthStatus)
374
+ lines.push(` Health: ${data.healthStatus}`);
375
+ if (data.restartPolicy)
376
+ lines.push(` Restart: ${data.restartPolicy}`);
225
377
  if (data.platform)
226
378
  lines.push(` Platform: ${data.platform}`);
227
379
  if (data.created)
228
380
  lines.push(` Created: ${data.created}`);
381
+ if (data.env && data.env.length > 0) {
382
+ lines.push(` Env: ${data.env.length} variables`);
383
+ }
384
+ // #111: Show network settings
385
+ if (data.networkSettings) {
386
+ lines.push(` Network: IP=${data.networkSettings.ipAddress}`);
387
+ }
388
+ // #112: Show mounts
389
+ if (data.mounts && data.mounts.length > 0) {
390
+ lines.push(` Mounts: ${data.mounts.length} mount(s)`);
391
+ for (const m of data.mounts) {
392
+ const mode = m.mode ? ` [${m.mode}]` : "";
393
+ lines.push(` ${m.source} → ${m.destination}${mode}`);
394
+ }
395
+ }
396
+ if (data.relatedTargets && data.relatedTargets.length > 1) {
397
+ lines.push(` Related targets: ${data.relatedTargets.length}`);
398
+ }
399
+ return lines.join("\n");
400
+ }
401
+ /** Formats an image inspect result. */
402
+ function formatImageInspect(data) {
403
+ const lines = [`Image: ${data.name} (${data.id})`];
404
+ if (data.repoTags && data.repoTags.length > 0) {
405
+ lines.push(` Tags: ${data.repoTags.join(", ")}`);
406
+ }
407
+ if (data.repoDigests && data.repoDigests.length > 0) {
408
+ lines.push(` Digests: ${data.repoDigests.map((d) => d.slice(0, 40) + "...").join(", ")}`);
409
+ }
410
+ if (data.size != null) {
411
+ const sizeMB = (data.size / 1024 / 1024).toFixed(1);
412
+ lines.push(` Size: ${sizeMB} MB`);
413
+ }
414
+ if (data.platform)
415
+ lines.push(` Platform: ${data.platform}`);
416
+ if (data.created)
417
+ lines.push(` Created: ${data.created}`);
418
+ if (data.entrypoint && data.entrypoint.length > 0) {
419
+ lines.push(` Entrypoint: ${data.entrypoint.join(" ")}`);
420
+ }
421
+ if (data.cmd && data.cmd.length > 0) {
422
+ lines.push(` Cmd: ${data.cmd.join(" ")}`);
423
+ }
424
+ if (data.env && data.env.length > 0) {
425
+ lines.push(` Env: ${data.env.length} variables`);
426
+ }
427
+ if (data.relatedTargets && data.relatedTargets.length > 1) {
428
+ lines.push(` Related targets: ${data.relatedTargets.length}`);
429
+ }
430
+ return lines.join("\n");
431
+ }
432
+ function formatVolumeInspect(data) {
433
+ const lines = [`Volume: ${data.name} (${data.id})`];
434
+ if (data.driver)
435
+ lines.push(` Driver: ${data.driver}`);
436
+ if (data.scope)
437
+ lines.push(` Scope: ${data.scope}`);
438
+ if (data.mountpoint)
439
+ lines.push(` Mountpoint: ${data.mountpoint}`);
440
+ if (data.created)
441
+ lines.push(` Created: ${data.created}`);
442
+ if (data.labels && Object.keys(data.labels).length > 0) {
443
+ lines.push(` Labels: ${Object.keys(data.labels).length}`);
444
+ }
445
+ return lines.join("\n");
446
+ }
447
+ function formatNetworkInspect(data) {
448
+ const lines = [`Network: ${data.name} (${data.id})`];
449
+ if (data.driver)
450
+ lines.push(` Driver: ${data.driver}`);
451
+ if (data.scope)
452
+ lines.push(` Scope: ${data.scope}`);
453
+ if (data.created)
454
+ lines.push(` Created: ${data.created}`);
455
+ if (data.labels && Object.keys(data.labels).length > 0) {
456
+ lines.push(` Labels: ${Object.keys(data.labels).length}`);
457
+ }
229
458
  return lines.join("\n");
230
459
  }
231
460
  export function compactInspectMap(data) {
461
+ if (data.inspectType === "image") {
462
+ return {
463
+ id: data.id,
464
+ name: data.name,
465
+ inspectType: "image",
466
+ status: "n/a",
467
+ running: false,
468
+ image: data.image,
469
+ ...(data.repoTags ? { repoTags: data.repoTags } : {}),
470
+ };
471
+ }
472
+ if (data.inspectType === "volume" || data.inspectType === "network") {
473
+ return {
474
+ id: data.id,
475
+ name: data.name,
476
+ inspectType: data.inspectType,
477
+ status: data.status ?? "n/a",
478
+ running: false,
479
+ image: data.image,
480
+ };
481
+ }
232
482
  return {
233
483
  id: data.id,
234
484
  name: data.name,
485
+ inspectType: "container",
235
486
  status: data.state?.status ?? data.status ?? "unknown",
236
487
  running: data.state?.running ?? data.running ?? false,
237
488
  image: data.image,
489
+ ...(data.healthStatus ? { healthStatus: data.healthStatus } : {}),
238
490
  };
239
491
  }
240
492
  export function formatInspectCompact(data) {
241
- return `${data.name} (${data.id}) ${data.status} [${data.running ? "running" : "stopped"}] image=${data.image}`;
493
+ if (data.inspectType === "image") {
494
+ const tags = data.repoTags ? ` tags=${data.repoTags.join(",")}` : "";
495
+ return `Image ${data.name} (${data.id})${tags}`;
496
+ }
497
+ if (data.inspectType === "volume") {
498
+ return `Volume ${data.name} (${data.id})`;
499
+ }
500
+ if (data.inspectType === "network") {
501
+ return `Network ${data.name} (${data.id})`;
502
+ }
503
+ const health = data.healthStatus ? ` health=${data.healthStatus}` : "";
504
+ return `${data.name} (${data.id}) ${data.status} [${data.running ? "running" : "stopped"}] image=${data.image}${health}`;
242
505
  }
243
506
  // ── Network LS ───────────────────────────────────────────────────────
244
- /** Formats structured Docker network list into a human-readable listing. */
507
+ /** Formats structured Docker network list into a human-readable listing.
508
+ * #115: Shows labels when present.
509
+ * #116: Shows boolean flags. */
245
510
  export function formatNetworkLs(data) {
246
511
  if (data.total === 0)
247
512
  return "No networks found.";
248
513
  const lines = [`${data.total} networks:`];
249
514
  for (const n of data.networks) {
250
- lines.push(` ${n.name} (${n.driver}, ${n.scope})`);
515
+ const created = n.createdAt ? ` (${n.createdAt})` : "";
516
+ const flags = [];
517
+ if (n.ipv6)
518
+ flags.push("ipv6");
519
+ if (n.internal)
520
+ flags.push("internal");
521
+ if (n.attachable)
522
+ flags.push("attachable");
523
+ const flagStr = flags.length > 0 ? ` [${flags.join(", ")}]` : "";
524
+ const labelCount = n.labels && Object.keys(n.labels).length > 0 ? ` labels=${Object.keys(n.labels).length}` : "";
525
+ lines.push(` ${n.name} (${n.driver}, ${n.scope})${created}${flagStr}${labelCount}`);
251
526
  }
252
527
  return lines.join("\n");
253
528
  }
254
529
  export function compactNetworkLsMap(data) {
255
530
  return {
256
531
  networks: data.networks.map((n) => ({
532
+ ...(n.id ? { id: n.id } : {}),
257
533
  name: n.name,
258
534
  driver: n.driver,
259
535
  })),
@@ -265,18 +541,23 @@ export function formatNetworkLsCompact(data) {
265
541
  return "No networks found.";
266
542
  const lines = [`${data.total} networks:`];
267
543
  for (const n of data.networks) {
268
- lines.push(` ${n.name} (${n.driver})`);
544
+ const id = n.id ? ` [${n.id}]` : "";
545
+ lines.push(` ${n.name} (${n.driver})${id}`);
269
546
  }
270
547
  return lines.join("\n");
271
548
  }
272
549
  // ── Volume LS ────────────────────────────────────────────────────────
273
- /** Formats structured Docker volume list into a human-readable listing. */
550
+ /** Formats structured Docker volume list into a human-readable listing.
551
+ * #125: Shows labels when present. */
274
552
  export function formatVolumeLs(data) {
275
553
  if (data.total === 0)
276
554
  return "No volumes found.";
277
555
  const lines = [`${data.total} volumes:`];
278
556
  for (const v of data.volumes) {
279
- lines.push(` ${v.name} (${v.driver}, ${v.scope})`);
557
+ const created = v.createdAt ? ` (${v.createdAt})` : "";
558
+ const status = v.status ? ` status=${v.status}` : "";
559
+ const labelCount = v.labels && Object.keys(v.labels).length > 0 ? ` labels=${Object.keys(v.labels).length}` : "";
560
+ lines.push(` ${v.name} (${v.driver}, ${v.scope})${created}${status}${labelCount}`);
280
561
  }
281
562
  return lines.join("\n");
282
563
  }
@@ -285,6 +566,7 @@ export function compactVolumeLsMap(data) {
285
566
  volumes: data.volumes.map((v) => ({
286
567
  name: v.name,
287
568
  driver: v.driver,
569
+ ...(v.mountpoint ? { mountpoint: v.mountpoint } : {}),
288
570
  })),
289
571
  total: data.total,
290
572
  };
@@ -294,12 +576,14 @@ export function formatVolumeLsCompact(data) {
294
576
  return "No volumes found.";
295
577
  const lines = [`${data.total} volumes:`];
296
578
  for (const v of data.volumes) {
297
- lines.push(` ${v.name} (${v.driver})`);
579
+ const mp = v.mountpoint ? ` @ ${v.mountpoint}` : "";
580
+ lines.push(` ${v.name} (${v.driver})${mp}`);
298
581
  }
299
582
  return lines.join("\n");
300
583
  }
301
584
  // ── Compose Build ────────────────────────────────────────────────────
302
- /** Formats structured Docker Compose build output into a human-readable summary. */
585
+ /** Formats structured Docker Compose build output into a human-readable summary.
586
+ * #99: Shows per-service duration. */
303
587
  export function formatComposeBuild(data) {
304
588
  if (!data.success && data.built === 0) {
305
589
  const lines = [`Compose build failed (${data.duration}s)`];
@@ -313,18 +597,23 @@ export function formatComposeBuild(data) {
313
597
  for (const s of data.services ?? []) {
314
598
  const status = s.success ? "built" : "failed";
315
599
  const error = s.error ? ` — ${s.error}` : "";
316
- lines.push(` ${s.service}: ${status}${error}`);
600
+ const dur = s.duration != null ? ` (${s.duration}s)` : "";
601
+ const imageId = s.imageId ? ` id=${s.imageId}` : "";
602
+ const image = s.image ? ` image=${s.image}` : "";
603
+ lines.push(` ${s.service}: ${status}${dur}${imageId}${image}${error}`);
317
604
  }
318
605
  return lines.join("\n");
319
606
  }
320
607
  // ── Stats ────────────────────────────────────────────────────────────
321
- /** Formats structured Docker stats data into a human-readable listing with CPU, memory, and I/O. */
608
+ /** Formats structured Docker stats data into a human-readable listing with CPU, memory, and I/O.
609
+ * #123/#124: Shows numeric memory and I/O values. */
322
610
  export function formatStats(data) {
323
611
  if (data.total === 0)
324
612
  return "No container stats available.";
325
613
  const lines = [`${data.total} containers:`];
326
614
  for (const c of data.containers) {
327
- lines.push(` ${c.name} (${c.id}) CPU: ${c.cpuPercent.toFixed(2)}% Mem: ${c.memoryUsage}/${c.memoryLimit} (${c.memoryPercent.toFixed(2)}%) Net: ${c.netIO} Block: ${c.blockIO} PIDs: ${c.pids}`);
615
+ const state = c.state ? ` [${c.state}]` : "";
616
+ lines.push(` ${c.name}${state} (${c.id}) CPU: ${c.cpuPercent.toFixed(2)}% Mem: ${c.memoryUsage}/${c.memoryLimit} (${c.memoryPercent.toFixed(2)}%) Net: ${c.netIO} Block: ${c.blockIO} PIDs: ${c.pids}`);
328
617
  }
329
618
  return lines.join("\n");
330
619
  }
@@ -347,7 +636,9 @@ export function compactStatsMap(data) {
347
636
  containers: data.containers.map((c) => ({
348
637
  id: c.id,
349
638
  name: c.name,
639
+ ...(c.state ? { state: c.state } : {}),
350
640
  cpuPercent: c.cpuPercent,
641
+ memoryUsage: c.memoryUsage,
351
642
  memoryPercent: c.memoryPercent,
352
643
  pids: c.pids,
353
644
  })),
@@ -359,19 +650,31 @@ export function formatStatsCompact(data) {
359
650
  return "No container stats available.";
360
651
  const lines = [`${data.total} containers:`];
361
652
  for (const c of data.containers) {
362
- lines.push(` ${c.name} (${c.id}) CPU: ${c.cpuPercent.toFixed(2)}% Mem: ${c.memoryPercent.toFixed(2)}% PIDs: ${c.pids}`);
653
+ const mem = c.memoryUsage ? ` ${c.memoryUsage}` : "";
654
+ const state = c.state ? ` [${c.state}]` : "";
655
+ lines.push(` ${c.name}${state} (${c.id}) CPU: ${c.cpuPercent.toFixed(2)}% Mem:${mem} (${c.memoryPercent.toFixed(2)}%) PIDs: ${c.pids}`);
363
656
  }
364
657
  return lines.join("\n");
365
658
  }
366
659
  // ── Compose PS ───────────────────────────────────────────────────────
367
- /** Formats structured Docker Compose ps data into a human-readable listing. */
660
+ /** Formats structured Docker Compose ps data into a human-readable listing.
661
+ * #105: Shows health status.
662
+ * #106: Shows running/stopped counts. */
368
663
  export function formatComposePs(data) {
369
664
  if (data.total === 0)
370
665
  return "No compose services found.";
371
- const lines = [`${data.total} services:`];
666
+ const countInfo = data.running != null && data.stopped != null
667
+ ? ` (${data.running} running, ${data.stopped} stopped)`
668
+ : "";
669
+ const lines = [`${data.total} services${countInfo}:`];
372
670
  for (const s of data.services) {
373
- const ports = s.ports ? ` [${s.ports}]` : "";
374
- lines.push(` ${s.state.padEnd(10)} ${s.name} (${s.service}) ${s.status}${ports}`);
671
+ const portsArr = s.ports ?? [];
672
+ const ports = portsArr.length
673
+ ? ` [${portsArr.map((p) => (p.host ? `${p.host}->${p.container}/${p.protocol}` : `${p.container}/${p.protocol}`)).join(", ")}]`
674
+ : "";
675
+ const health = s.health ? ` health=${s.health}` : "";
676
+ const exit = s.exitCode != null ? ` exit=${s.exitCode}` : "";
677
+ lines.push(` ${s.state.padEnd(10)} ${s.name} (${s.service}) ${s.status}${ports}${health}${exit}`);
375
678
  }
376
679
  return lines.join("\n");
377
680
  }
@@ -381,6 +684,7 @@ export function compactComposePsMap(data) {
381
684
  name: s.name,
382
685
  service: s.service,
383
686
  state: s.state,
687
+ ...(s.exitCode != null ? { exitCode: s.exitCode } : {}),
384
688
  })),
385
689
  total: data.total,
386
690
  };
@@ -390,12 +694,14 @@ export function formatComposePsCompact(data) {
390
694
  return "No compose services found.";
391
695
  const lines = [`${data.total} services:`];
392
696
  for (const s of data.services) {
393
- lines.push(` ${s.state.padEnd(10)} ${s.name} (${s.service})`);
697
+ const exit = s.exitCode != null ? ` exit=${s.exitCode}` : "";
698
+ lines.push(` ${s.state.padEnd(10)} ${s.name} (${s.service})${exit}`);
394
699
  }
395
700
  return lines.join("\n");
396
701
  }
397
702
  // ── Compose Logs ─────────────────────────────────────────────────────
398
- /** Formats structured Docker Compose logs into a human-readable output grouped by service. */
703
+ /** Formats structured Docker Compose logs into a human-readable output grouped by service.
704
+ * #104: Shows log level when available. */
399
705
  export function formatComposeLogs(data) {
400
706
  const header = data.isTruncated
401
707
  ? `Compose logs: ${data.services.length} services, ${data.total} of ${data.totalEntries} entries (truncated)`
@@ -403,7 +709,8 @@ export function formatComposeLogs(data) {
403
709
  const lines = [header];
404
710
  for (const entry of data.entries ?? []) {
405
711
  const ts = entry.timestamp ? `${entry.timestamp} ` : "";
406
- lines.push(` ${entry.service} | ${ts}${entry.message}`);
712
+ const level = entry.level ? `[${entry.level.toUpperCase()}] ` : "";
713
+ lines.push(` ${entry.service} | ${ts}${level}${entry.message}`);
407
714
  }
408
715
  return lines.join("\n");
409
716
  }
@@ -414,21 +721,31 @@ export function compactComposeLogsMap(data) {
414
721
  return {
415
722
  services: data.services,
416
723
  total: data.total,
417
- head: entries.slice(0, HEAD_SIZE).map((e) => ({ service: e.service, message: e.message })),
724
+ head: entries.slice(0, HEAD_SIZE).map((e) => ({
725
+ service: e.service,
726
+ message: e.message,
727
+ ...(e.timestamp ? { timestamp: e.timestamp } : {}),
728
+ })),
418
729
  tail: data.total > HEAD_SIZE + TAIL_SIZE
419
- ? entries.slice(-TAIL_SIZE).map((e) => ({ service: e.service, message: e.message }))
730
+ ? entries.slice(-TAIL_SIZE).map((e) => ({
731
+ service: e.service,
732
+ message: e.message,
733
+ ...(e.timestamp ? { timestamp: e.timestamp } : {}),
734
+ }))
420
735
  : [],
421
736
  };
422
737
  }
423
738
  export function formatComposeLogsCompact(data) {
424
739
  const parts = [`Compose logs: ${data.services.length} services, ${data.total} entries`];
425
740
  for (const e of data.head) {
426
- parts.push(` ${e.service} | ${e.message}`);
741
+ const ts = e.timestamp ? `${e.timestamp} ` : "";
742
+ parts.push(` ${e.service} | ${ts}${e.message}`);
427
743
  }
428
744
  if (data.tail.length) {
429
745
  parts.push(` ... ${data.total - data.head.length - data.tail.length} entries omitted ...`);
430
746
  for (const e of data.tail) {
431
- parts.push(` ${e.service} | ${e.message}`);
747
+ const ts = e.timestamp ? `${e.timestamp} ` : "";
748
+ parts.push(` ${e.service} | ${ts}${e.message}`);
432
749
  }
433
750
  }
434
751
  return parts.join("\n");