Skip to content

Commit b3479fe

Browse files
authored
feat(snapshot): snapshot prune command (#104)
1 parent 6e7e8c4 commit b3479fe

12 files changed

Lines changed: 531 additions & 135 deletions

File tree

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,7 @@ rli snapshot list # List all snapshots
109109
rli snapshot create <devbox-id> # Create a snapshot of a devbox
110110
rli snapshot delete <id> # Delete a snapshot
111111
rli snapshot get <id> # Get snapshot details
112+
rli snapshot prune <devbox-id> # Delete old snapshots for a devbox, ke...
112113
rli snapshot status <snapshot-id> # Get snapshot operation status
113114
```
114115

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@
6767
"access": "public"
6868
},
6969
"dependencies": {
70+
"@js-temporal/polyfill": "^0.5.1",
7071
"@modelcontextprotocol/sdk": "^1.26.0",
7172
"@runloop/api-client": "1.6.0",
7273
"@types/express": "^5.0.6",

pnpm-lock.yaml

Lines changed: 16 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/commands/blueprint/prune.ts

Lines changed: 4 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import * as readline from "readline";
66
import { getClient } from "../../utils/client.js";
77
import { output, outputError } from "../../utils/output.js";
8+
import { formatRelativeTime } from "../../utils/time.js";
89
import type { Blueprint } from "../../store/blueprintStore.js";
910

1011
interface PruneBlueprintsOptions {
@@ -87,29 +88,6 @@ function categorizeBlueprints(blueprints: Blueprint[], keepCount: number) {
8788
};
8889
}
8990

90-
/**
91-
* Format a timestamp for display
92-
*/
93-
function formatTimestamp(createTimeMs?: number): string {
94-
if (!createTimeMs) {
95-
return "unknown time";
96-
}
97-
98-
const now = Date.now();
99-
const diffMs = now - createTimeMs;
100-
const diffMinutes = Math.floor(diffMs / 60000);
101-
const diffHours = Math.floor(diffMs / 3600000);
102-
const diffDays = Math.floor(diffMs / 86400000);
103-
104-
if (diffMinutes < 60) {
105-
return `${diffMinutes} minute${diffMinutes !== 1 ? "s" : ""} ago`;
106-
} else if (diffHours < 24) {
107-
return `${diffHours} hour${diffHours !== 1 ? "s" : ""} ago`;
108-
} else {
109-
return `${diffDays} day${diffDays !== 1 ? "s" : ""} ago`;
110-
}
111-
}
112-
11391
/**
11492
* Display a summary of what will be kept and deleted
11593
*/
@@ -145,7 +123,7 @@ function displaySummary(
145123
} else {
146124
for (const blueprint of result.toKeep) {
147125
console.log(
148-
` ✓ ${blueprint.id} - Created ${formatTimestamp(blueprint.create_time_ms)}`,
126+
` ✓ ${blueprint.id} - Created ${formatRelativeTime(blueprint.create_time_ms)}`,
149127
);
150128
}
151129
}
@@ -163,7 +141,7 @@ function displaySummary(
163141
const statusLabel =
164142
blueprint.status === "build_complete" ? "successful" : "failed";
165143
console.log(
166-
` ${icon} ${blueprint.id} - Created ${formatTimestamp(blueprint.create_time_ms)} (${statusLabel})`,
144+
` ${icon} ${blueprint.id} - Created ${formatRelativeTime(blueprint.create_time_ms)} (${statusLabel})`,
167145
);
168146
}
169147
}
@@ -183,7 +161,7 @@ function displayDeletedBlueprints(deleted: Blueprint[]) {
183161
const statusLabel =
184162
blueprint.status === "build_complete" ? "successful" : "failed";
185163
console.log(
186-
` ${icon} ${blueprint.id} - Created ${formatTimestamp(blueprint.create_time_ms)} (${statusLabel})`,
164+
` ${icon} ${blueprint.id} - Created ${formatRelativeTime(blueprint.create_time_ms)} (${statusLabel})`,
187165
);
188166
}
189167
}

src/commands/snapshot/list.tsx

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import React from "react";
22
import { Box, Text, useInput, useApp } from "ink";
33
import figures from "figures";
44
import type { DiskSnapshotsCursorIDPage } from "@runloop/api-client/pagination";
5+
import type { DevboxSnapshotView } from "@runloop/api-client/resources/devboxes/devboxes";
56
import { getClient } from "../../utils/client.js";
67
import { Header } from "../../components/Header.js";
78
import { SpinnerComponent } from "../../components/Spinner.js";
@@ -696,10 +697,19 @@ export async function listSnapshots(options: ListOptions) {
696697
// Fetch snapshots
697698
const page = (await client.devboxes.listDiskSnapshots(
698699
queryParams,
699-
)) as DiskSnapshotsCursorIDPage<{ id: string }>;
700-
701-
// Extract snapshots array
702-
const snapshots = page.snapshots || [];
700+
)) as DiskSnapshotsCursorIDPage<DevboxSnapshotView>;
701+
702+
// Extract snapshots array and strip to plain objects to avoid
703+
// camelCase aliases added by the API client library
704+
const snapshots = (page.snapshots || []).map((s) => ({
705+
id: s.id,
706+
name: s.name ?? undefined,
707+
create_time_ms: s.create_time_ms,
708+
metadata: s.metadata,
709+
source_devbox_id: s.source_devbox_id,
710+
source_blueprint_id: s.source_blueprint_id ?? undefined,
711+
commit_message: s.commit_message ?? undefined,
712+
}));
703713

704714
output(snapshots, { format: options.output, defaultFormat: "json" });
705715
} catch (error) {

0 commit comments

Comments
 (0)