@hasna/snapshots 0.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 (55) hide show
  1. package/LICENSE +130 -0
  2. package/README.md +90 -0
  3. package/dist/agent/index.d.ts +3 -0
  4. package/dist/agent/index.d.ts.map +1 -0
  5. package/dist/agent/index.js +12 -0
  6. package/dist/agent/index.js.map +1 -0
  7. package/dist/capture/index.d.ts +3 -0
  8. package/dist/capture/index.d.ts.map +1 -0
  9. package/dist/capture/index.js +267 -0
  10. package/dist/capture/index.js.map +1 -0
  11. package/dist/cli/index.d.ts +3 -0
  12. package/dist/cli/index.d.ts.map +1 -0
  13. package/dist/cli/index.js +195 -0
  14. package/dist/cli/index.js.map +1 -0
  15. package/dist/index.d.ts +8 -0
  16. package/dist/index.d.ts.map +1 -0
  17. package/dist/index.js +8 -0
  18. package/dist/index.js.map +1 -0
  19. package/dist/mcp/index.d.ts +3 -0
  20. package/dist/mcp/index.d.ts.map +1 -0
  21. package/dist/mcp/index.js +116 -0
  22. package/dist/mcp/index.js.map +1 -0
  23. package/dist/policy.d.ts +5 -0
  24. package/dist/policy.d.ts.map +1 -0
  25. package/dist/policy.js +38 -0
  26. package/dist/policy.js.map +1 -0
  27. package/dist/restore.d.ts +4 -0
  28. package/dist/restore.d.ts.map +1 -0
  29. package/dist/restore.js +157 -0
  30. package/dist/restore.js.map +1 -0
  31. package/dist/runtime.d.ts +38 -0
  32. package/dist/runtime.d.ts.map +1 -0
  33. package/dist/runtime.js +91 -0
  34. package/dist/runtime.js.map +1 -0
  35. package/dist/server/index.d.ts +3 -0
  36. package/dist/server/index.d.ts.map +1 -0
  37. package/dist/server/index.js +51 -0
  38. package/dist/server/index.js.map +1 -0
  39. package/dist/service.d.ts +16 -0
  40. package/dist/service.d.ts.map +1 -0
  41. package/dist/service.js +84 -0
  42. package/dist/service.js.map +1 -0
  43. package/dist/storage.d.ts +25 -0
  44. package/dist/storage.d.ts.map +1 -0
  45. package/dist/storage.js +215 -0
  46. package/dist/storage.js.map +1 -0
  47. package/dist/types.d.ts +90 -0
  48. package/dist/types.d.ts.map +1 -0
  49. package/dist/types.js +2 -0
  50. package/dist/types.js.map +1 -0
  51. package/dist/util.d.ts +26 -0
  52. package/dist/util.d.ts.map +1 -0
  53. package/dist/util.js +113 -0
  54. package/dist/util.js.map +1 -0
  55. package/package.json +78 -0
@@ -0,0 +1,91 @@
1
+ import { captureAll } from "./capture/index.js";
2
+ import { SnapshotStore } from "./storage.js";
3
+ import { createRestorePlan } from "./restore.js";
4
+ export async function captureSnapshot(options = {}) {
5
+ const store = new SnapshotStore({ path: options.dbPath });
6
+ try {
7
+ const capture = await captureAll(options);
8
+ const snapshot = store.saveSnapshot(capture.resources, {
9
+ name: options.name,
10
+ diagnostics: capture.diagnostics
11
+ });
12
+ return {
13
+ snapshot,
14
+ resource_count: capture.resources.length,
15
+ diagnostic_count: capture.diagnostics.length,
16
+ duplicate: Boolean(snapshot.duplicateOf)
17
+ };
18
+ }
19
+ finally {
20
+ store.close();
21
+ }
22
+ }
23
+ export function listSnapshots(options = {}) {
24
+ const store = new SnapshotStore({ path: options.dbPath });
25
+ try {
26
+ return store.listSnapshots(options.limit ?? 50);
27
+ }
28
+ finally {
29
+ store.close();
30
+ }
31
+ }
32
+ export function getSnapshotEnvelope(options) {
33
+ const store = new SnapshotStore({ path: options.dbPath });
34
+ try {
35
+ const snapshot = store.getSnapshot(options.id);
36
+ if (!snapshot)
37
+ throw new Error(`Snapshot not found: ${options.id}`);
38
+ return {
39
+ snapshot,
40
+ resources: store.getSnapshotResources(options.id)
41
+ };
42
+ }
43
+ finally {
44
+ store.close();
45
+ }
46
+ }
47
+ export function listResources(options = {}) {
48
+ const store = new SnapshotStore({ path: options.dbPath });
49
+ try {
50
+ return {
51
+ resources: store.listResources(options.limit ?? 200)
52
+ };
53
+ }
54
+ finally {
55
+ store.close();
56
+ }
57
+ }
58
+ export function planSnapshotRestore(options) {
59
+ const store = new SnapshotStore({ path: options.dbPath });
60
+ try {
61
+ const snapshot = store.getSnapshot(options.id);
62
+ if (!snapshot)
63
+ throw new Error(`Snapshot not found: ${options.id}`);
64
+ const resources = store.getSnapshotResources(options.id);
65
+ const plan = createRestorePlan(snapshot, resources, store.listPolicies(), options);
66
+ store.saveRestorePlan(plan);
67
+ return plan;
68
+ }
69
+ finally {
70
+ store.close();
71
+ }
72
+ }
73
+ export function upsertPolicy(options) {
74
+ const store = new SnapshotStore({ path: options.dbPath });
75
+ try {
76
+ return store.upsertPolicy(options.selector, options.mode, options.reason);
77
+ }
78
+ finally {
79
+ store.close();
80
+ }
81
+ }
82
+ export function listPolicies(options = {}) {
83
+ const store = new SnapshotStore({ path: options.dbPath });
84
+ try {
85
+ return store.listPolicies();
86
+ }
87
+ finally {
88
+ store.close();
89
+ }
90
+ }
91
+ //# sourceMappingURL=runtime.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"runtime.js","sourceRoot":"","sources":["../src/runtime.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAChD,OAAO,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAC7C,OAAO,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAC;AAiBjD,MAAM,CAAC,KAAK,UAAU,eAAe,CAAC,UAAkC,EAAE;IACxE,MAAM,KAAK,GAAG,IAAI,aAAa,CAAC,EAAE,IAAI,EAAE,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;IAC1D,IAAI,CAAC;QACH,MAAM,OAAO,GAAG,MAAM,UAAU,CAAC,OAAO,CAAC,CAAC;QAC1C,MAAM,QAAQ,GAAG,KAAK,CAAC,YAAY,CAAC,OAAO,CAAC,SAAS,EAAE;YACrD,IAAI,EAAE,OAAO,CAAC,IAAI;YAClB,WAAW,EAAE,OAAO,CAAC,WAAW;SACjC,CAAC,CAAC;QACH,OAAO;YACL,QAAQ;YACR,cAAc,EAAE,OAAO,CAAC,SAAS,CAAC,MAAM;YACxC,gBAAgB,EAAE,OAAO,CAAC,WAAW,CAAC,MAAM;YAC5C,SAAS,EAAE,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAC;SACzC,CAAC;IACJ,CAAC;YAAS,CAAC;QACT,KAAK,CAAC,KAAK,EAAE,CAAC;IAChB,CAAC;AACH,CAAC;AAED,MAAM,UAAU,aAAa,CAAC,UAA+C,EAAE;IAC7E,MAAM,KAAK,GAAG,IAAI,aAAa,CAAC,EAAE,IAAI,EAAE,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;IAC1D,IAAI,CAAC;QACH,OAAO,KAAK,CAAC,aAAa,CAAC,OAAO,CAAC,KAAK,IAAI,EAAE,CAAC,CAAC;IAClD,CAAC;YAAS,CAAC;QACT,KAAK,CAAC,KAAK,EAAE,CAAC;IAChB,CAAC;AACH,CAAC;AAED,MAAM,UAAU,mBAAmB,CAAC,OAAwC;IAC1E,MAAM,KAAK,GAAG,IAAI,aAAa,CAAC,EAAE,IAAI,EAAE,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;IAC1D,IAAI,CAAC;QACH,MAAM,QAAQ,GAAG,KAAK,CAAC,WAAW,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;QAC/C,IAAI,CAAC,QAAQ;YAAE,MAAM,IAAI,KAAK,CAAC,uBAAuB,OAAO,CAAC,EAAE,EAAE,CAAC,CAAC;QACpE,OAAO;YACL,QAAQ;YACR,SAAS,EAAE,KAAK,CAAC,oBAAoB,CAAC,OAAO,CAAC,EAAE,CAAC;SAClD,CAAC;IACJ,CAAC;YAAS,CAAC;QACT,KAAK,CAAC,KAAK,EAAE,CAAC;IAChB,CAAC;AACH,CAAC;AAED,MAAM,UAAU,aAAa,CAAC,UAA+C,EAAE;IAC7E,MAAM,KAAK,GAAG,IAAI,aAAa,CAAC,EAAE,IAAI,EAAE,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;IAC1D,IAAI,CAAC;QACH,OAAO;YACL,SAAS,EAAE,KAAK,CAAC,aAAa,CAAC,OAAO,CAAC,KAAK,IAAI,GAAG,CAAC;SACrD,CAAC;IACJ,CAAC;YAAS,CAAC;QACT,KAAK,CAAC,KAAK,EAAE,CAAC;IAChB,CAAC;AACH,CAAC;AAED,MAAM,UAAU,mBAAmB,CAAC,OAAkE;IACpG,MAAM,KAAK,GAAG,IAAI,aAAa,CAAC,EAAE,IAAI,EAAE,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;IAC1D,IAAI,CAAC;QACH,MAAM,QAAQ,GAAG,KAAK,CAAC,WAAW,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;QAC/C,IAAI,CAAC,QAAQ;YAAE,MAAM,IAAI,KAAK,CAAC,uBAAuB,OAAO,CAAC,EAAE,EAAE,CAAC,CAAC;QACpE,MAAM,SAAS,GAAG,KAAK,CAAC,oBAAoB,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;QACzD,MAAM,IAAI,GAAG,iBAAiB,CAAC,QAAQ,EAAE,SAAS,EAAE,KAAK,CAAC,YAAY,EAAE,EAAE,OAAO,CAAC,CAAC;QACnF,KAAK,CAAC,eAAe,CAAC,IAAqF,CAAC,CAAC;QAC7G,OAAO,IAAI,CAAC;IACd,CAAC;YAAS,CAAC;QACT,KAAK,CAAC,KAAK,EAAE,CAAC;IAChB,CAAC;AACH,CAAC;AAED,MAAM,UAAU,YAAY,CAAC,OAAuG;IAClI,MAAM,KAAK,GAAG,IAAI,aAAa,CAAC,EAAE,IAAI,EAAE,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;IAC1D,IAAI,CAAC;QACH,OAAO,KAAK,CAAC,YAAY,CAAC,OAAO,CAAC,QAAQ,EAAE,OAAO,CAAC,IAAI,EAAE,OAAO,CAAC,MAAM,CAAC,CAAC;IAC5E,CAAC;YAAS,CAAC;QACT,KAAK,CAAC,KAAK,EAAE,CAAC;IAChB,CAAC;AACH,CAAC;AAED,MAAM,UAAU,YAAY,CAAC,UAA0B,EAAE;IACvD,MAAM,KAAK,GAAG,IAAI,aAAa,CAAC,EAAE,IAAI,EAAE,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;IAC1D,IAAI,CAAC;QACH,OAAO,KAAK,CAAC,YAAY,EAAE,CAAC;IAC9B,CAAC;YAAS,CAAC;QACT,KAAK,CAAC,KAAK,EAAE,CAAC;IAChB,CAAC;AACH,CAAC"}
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env bun
2
+ export {};
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/server/index.ts"],"names":[],"mappings":""}
@@ -0,0 +1,51 @@
1
+ #!/usr/bin/env bun
2
+ import { captureSnapshot, getSnapshotEnvelope, listSnapshots, planSnapshotRestore } from "../runtime.js";
3
+ const port = Number(process.env.SNAPSHOTS_PORT ?? process.env.PORT ?? 7337);
4
+ function json(value, status = 200) {
5
+ return new Response(JSON.stringify(value, null, 2), {
6
+ status,
7
+ headers: { "content-type": "application/json" }
8
+ });
9
+ }
10
+ function pathParts(url) {
11
+ return url.pathname.split("/").filter(Boolean);
12
+ }
13
+ Bun.serve({
14
+ port,
15
+ async fetch(request) {
16
+ const url = new URL(request.url);
17
+ const parts = pathParts(url);
18
+ try {
19
+ if (request.method === "GET" && url.pathname === "/health") {
20
+ return json({ ok: true, service: "@hasna/snapshots" });
21
+ }
22
+ if (request.method === "GET" && url.pathname === "/snapshots") {
23
+ return json({ snapshots: listSnapshots({ limit: Number(url.searchParams.get("limit") ?? 50) }) });
24
+ }
25
+ if (request.method === "POST" && url.pathname === "/snapshots") {
26
+ const body = await readBody(request);
27
+ return json(await captureSnapshot({
28
+ name: typeof body.name === "string" ? body.name : undefined,
29
+ include: Array.isArray(body.include) ? body.include.map(String) : undefined
30
+ }));
31
+ }
32
+ if (request.method === "GET" && parts[0] === "snapshots" && parts[1]) {
33
+ return json(getSnapshotEnvelope({ id: parts[1] }));
34
+ }
35
+ if (request.method === "POST" && parts[0] === "restore" && parts[1] === "plan" && parts[2]) {
36
+ return json(planSnapshotRestore({ id: parts[2] }));
37
+ }
38
+ return json({ ok: false, error: "not found" }, 404);
39
+ }
40
+ catch (error) {
41
+ return json({ ok: false, error: error instanceof Error ? error.message : String(error) }, 500);
42
+ }
43
+ }
44
+ });
45
+ console.log(JSON.stringify({ event: "listening", port }));
46
+ async function readBody(request) {
47
+ if (!request.headers.get("content-type")?.includes("application/json"))
48
+ return {};
49
+ return (await request.json());
50
+ }
51
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/server/index.ts"],"names":[],"mappings":";AACA,OAAO,EAAE,eAAe,EAAE,mBAAmB,EAAE,aAAa,EAAE,mBAAmB,EAAE,MAAM,eAAe,CAAC;AAEzG,MAAM,IAAI,GAAG,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,cAAc,IAAI,OAAO,CAAC,GAAG,CAAC,IAAI,IAAI,IAAI,CAAC,CAAC;AAE5E,SAAS,IAAI,CAAC,KAAc,EAAE,MAAM,GAAG,GAAG;IACxC,OAAO,IAAI,QAAQ,CAAC,IAAI,CAAC,SAAS,CAAC,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE;QAClD,MAAM;QACN,OAAO,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE;KAChD,CAAC,CAAC;AACL,CAAC;AAED,SAAS,SAAS,CAAC,GAAQ;IACzB,OAAO,GAAG,CAAC,QAAQ,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;AACjD,CAAC;AAED,GAAG,CAAC,KAAK,CAAC;IACR,IAAI;IACJ,KAAK,CAAC,KAAK,CAAC,OAAO;QACjB,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;QACjC,MAAM,KAAK,GAAG,SAAS,CAAC,GAAG,CAAC,CAAC;QAC7B,IAAI,CAAC;YACH,IAAI,OAAO,CAAC,MAAM,KAAK,KAAK,IAAI,GAAG,CAAC,QAAQ,KAAK,SAAS,EAAE,CAAC;gBAC3D,OAAO,IAAI,CAAC,EAAE,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,kBAAkB,EAAE,CAAC,CAAC;YACzD,CAAC;YACD,IAAI,OAAO,CAAC,MAAM,KAAK,KAAK,IAAI,GAAG,CAAC,QAAQ,KAAK,YAAY,EAAE,CAAC;gBAC9D,OAAO,IAAI,CAAC,EAAE,SAAS,EAAE,aAAa,CAAC,EAAE,KAAK,EAAE,MAAM,CAAC,GAAG,CAAC,YAAY,CAAC,GAAG,CAAC,OAAO,CAAC,IAAI,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC;YACpG,CAAC;YACD,IAAI,OAAO,CAAC,MAAM,KAAK,MAAM,IAAI,GAAG,CAAC,QAAQ,KAAK,YAAY,EAAE,CAAC;gBAC/D,MAAM,IAAI,GAAG,MAAM,QAAQ,CAAC,OAAO,CAAC,CAAC;gBACrC,OAAO,IAAI,CAAC,MAAM,eAAe,CAAC;oBAChC,IAAI,EAAE,OAAO,IAAI,CAAC,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,SAAS;oBAC3D,OAAO,EAAE,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,SAAS;iBAC5E,CAAC,CAAC,CAAC;YACN,CAAC;YACD,IAAI,OAAO,CAAC,MAAM,KAAK,KAAK,IAAI,KAAK,CAAC,CAAC,CAAC,KAAK,WAAW,IAAI,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC;gBACrE,OAAO,IAAI,CAAC,mBAAmB,CAAC,EAAE,EAAE,EAAE,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;YACrD,CAAC;YACD,IAAI,OAAO,CAAC,MAAM,KAAK,MAAM,IAAI,KAAK,CAAC,CAAC,CAAC,KAAK,SAAS,IAAI,KAAK,CAAC,CAAC,CAAC,KAAK,MAAM,IAAI,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC;gBAC3F,OAAO,IAAI,CAAC,mBAAmB,CAAC,EAAE,EAAE,EAAE,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;YACrD,CAAC;YACD,OAAO,IAAI,CAAC,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,WAAW,EAAE,EAAE,GAAG,CAAC,CAAC;QACtD,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,OAAO,IAAI,CAAC,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,EAAE,GAAG,CAAC,CAAC;QACjG,CAAC;IACH,CAAC;CACF,CAAC,CAAC;AAEH,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,KAAK,EAAE,WAAW,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC;AAE1D,KAAK,UAAU,QAAQ,CAAC,OAAgB;IACtC,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,cAAc,CAAC,EAAE,QAAQ,CAAC,kBAAkB,CAAC;QAAE,OAAO,EAAE,CAAC;IAClF,OAAO,CAAC,MAAM,OAAO,CAAC,IAAI,EAAE,CAA4B,CAAC;AAC3D,CAAC"}
@@ -0,0 +1,16 @@
1
+ import type { JsonObject } from "./types.js";
2
+ export interface ServicePlanOptions {
3
+ command?: string;
4
+ intervalSeconds?: number;
5
+ platform?: NodeJS.Platform;
6
+ }
7
+ export interface ServicePlan {
8
+ kind: "systemd" | "launchd";
9
+ path: string;
10
+ content: string;
11
+ applyCommand: string[];
12
+ note: string;
13
+ }
14
+ export declare function planService(options?: ServicePlanOptions): ServicePlan;
15
+ export declare function applyServicePlan(plan: ServicePlan, yes: boolean): JsonObject;
16
+ //# sourceMappingURL=service.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"service.d.ts","sourceRoot":"","sources":["../src/service.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAG7C,MAAM,WAAW,kBAAkB;IACjC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,EAAE,MAAM,CAAC,QAAQ,CAAC;CAC5B;AAED,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,SAAS,GAAG,SAAS,CAAC;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,IAAI,EAAE,MAAM,CAAC;CACd;AAED,wBAAgB,WAAW,CAAC,OAAO,GAAE,kBAAuB,GAAG,WAAW,CAqBzE;AAED,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,WAAW,EAAE,GAAG,EAAE,OAAO,GAAG,UAAU,CAkB5E"}
@@ -0,0 +1,84 @@
1
+ import { existsSync, writeFileSync } from "node:fs";
2
+ import { homedir, platform } from "node:os";
3
+ import { join } from "node:path";
4
+ import { ensureDir } from "./util.js";
5
+ export function planService(options = {}) {
6
+ const targetPlatform = options.platform ?? platform();
7
+ const command = options.command ?? "snapshots-agent run --interval 300";
8
+ if (targetPlatform === "darwin") {
9
+ const path = join(homedir(), "Library", "LaunchAgents", "com.hasna.snapshots.plist");
10
+ return {
11
+ kind: "launchd",
12
+ path,
13
+ content: launchdPlist(command, options.intervalSeconds ?? 300),
14
+ applyCommand: ["launchctl", "load", path],
15
+ note: "launchd plan is dry-run until service install --apply --yes is used."
16
+ };
17
+ }
18
+ const path = join(homedir(), ".config", "systemd", "user", "hasna-snapshots.service");
19
+ return {
20
+ kind: "systemd",
21
+ path,
22
+ content: systemdUnit(command),
23
+ applyCommand: ["systemctl", "--user", "enable", "--now", "hasna-snapshots.service"],
24
+ note: "systemd plan is dry-run until service install --apply --yes is used."
25
+ };
26
+ }
27
+ export function applyServicePlan(plan, yes) {
28
+ if (!yes) {
29
+ return {
30
+ applied: false,
31
+ reason: "Writing service files requires --apply --yes.",
32
+ plan: plan
33
+ };
34
+ }
35
+ ensureDir(plan.path.split("/").slice(0, -1).join("/"));
36
+ if (existsSync(plan.path)) {
37
+ return { applied: false, reason: `Service file already exists: ${plan.path}`, plan: plan };
38
+ }
39
+ writeFileSync(plan.path, plan.content, "utf8");
40
+ return {
41
+ applied: true,
42
+ path: plan.path,
43
+ next_command: plan.applyCommand
44
+ };
45
+ }
46
+ function systemdUnit(command) {
47
+ return `[Unit]
48
+ Description=Hasna snapshots agent
49
+
50
+ [Service]
51
+ Type=simple
52
+ ExecStart=${command}
53
+ Restart=on-failure
54
+ RestartSec=10
55
+
56
+ [Install]
57
+ WantedBy=default.target
58
+ `;
59
+ }
60
+ function launchdPlist(command, intervalSeconds) {
61
+ const [program, ...args] = command.split(/\s+/);
62
+ const array = [program, ...args].map((arg) => ` <string>${escapeXml(arg)}</string>`).join("\n");
63
+ return `<?xml version="1.0" encoding="UTF-8"?>
64
+ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
65
+ <plist version="1.0">
66
+ <dict>
67
+ <key>Label</key>
68
+ <string>com.hasna.snapshots</string>
69
+ <key>ProgramArguments</key>
70
+ <array>
71
+ ${array}
72
+ </array>
73
+ <key>StartInterval</key>
74
+ <integer>${intervalSeconds}</integer>
75
+ <key>RunAtLoad</key>
76
+ <true/>
77
+ </dict>
78
+ </plist>
79
+ `;
80
+ }
81
+ function escapeXml(value) {
82
+ return value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;");
83
+ }
84
+ //# sourceMappingURL=service.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"service.js","sourceRoot":"","sources":["../src/service.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,25 @@
1
+ import { Database } from "bun:sqlite";
2
+ import type { JsonObject, RestorePolicy, SnapshotRecord, SnapshotResource, SnapshotSaveOptions, StoredSnapshotResource, StorageOptions } from "./types.js";
3
+ export declare class SnapshotStore {
4
+ readonly path: string;
5
+ readonly db: Database;
6
+ constructor(options?: StorageOptions);
7
+ close(): void;
8
+ migrate(): void;
9
+ saveSnapshot(resources: SnapshotResource[], options?: SnapshotSaveOptions): SnapshotRecord;
10
+ listSnapshots(limit?: number): SnapshotRecord[];
11
+ getSnapshot(id: string): SnapshotRecord | undefined;
12
+ getSnapshotByHash(hash: string): SnapshotRecord | undefined;
13
+ getSnapshotResources(snapshotId: string): StoredSnapshotResource[];
14
+ listResources(limit?: number): StoredSnapshotResource[];
15
+ upsertPolicy(selector: string, mode: RestorePolicy["mode"], reason?: string): RestorePolicy;
16
+ listPolicies(): RestorePolicy[];
17
+ saveRestorePlan(plan: JsonObject & {
18
+ id: string;
19
+ snapshotId: string;
20
+ createdAt: string;
21
+ }): void;
22
+ }
23
+ export declare function toStoredResource(resource: SnapshotResource): StoredSnapshotResource;
24
+ export declare function defaultSnapshotName(): string;
25
+ //# sourceMappingURL=storage.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"storage.d.ts","sourceRoot":"","sources":["../src/storage.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAEtC,OAAO,KAAK,EAEV,UAAU,EACV,aAAa,EACb,cAAc,EACd,gBAAgB,EAChB,mBAAmB,EACnB,sBAAsB,EACtB,cAAc,EACf,MAAM,YAAY,CAAC;AAKpB,qBAAa,aAAa;IACxB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,EAAE,EAAE,QAAQ,CAAC;gBAEV,OAAO,GAAE,cAAmB;IASxC,KAAK,IAAI,IAAI;IAIb,OAAO,IAAI,IAAI;IAoDf,YAAY,CAAC,SAAS,EAAE,gBAAgB,EAAE,EAAE,OAAO,GAAE,mBAAwB,GAAG,cAAc;IA6E9F,aAAa,CAAC,KAAK,SAAK,GAAG,cAAc,EAAE;IAI3C,WAAW,CAAC,EAAE,EAAE,MAAM,GAAG,cAAc,GAAG,SAAS;IAKnD,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,cAAc,GAAG,SAAS;IAK3D,oBAAoB,CAAC,UAAU,EAAE,MAAM,GAAG,sBAAsB,EAAE;IAOlE,aAAa,CAAC,KAAK,SAAM,GAAG,sBAAsB,EAAE;IAOpD,YAAY,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,aAAa,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,aAAa;IAY3F,YAAY,IAAI,aAAa,EAAE;IAI/B,eAAe,CAAC,IAAI,EAAE,UAAU,GAAG;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,UAAU,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI;CAKhG;AAED,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,gBAAgB,GAAG,sBAAsB,CAanF;AAwCD,wBAAgB,mBAAmB,IAAI,MAAM,CAE5C"}
@@ -0,0 +1,215 @@
1
+ import { Database } from "bun:sqlite";
2
+ import { basename } from "node:path";
3
+ import { defaultDbPath, ensureParentDir, nowIso, sha256, stableJson } from "./util.js";
4
+ export class SnapshotStore {
5
+ path;
6
+ db;
7
+ constructor(options = {}) {
8
+ this.path = options.path ?? defaultDbPath();
9
+ ensureParentDir(this.path);
10
+ this.db = new Database(this.path);
11
+ this.db.exec("PRAGMA journal_mode = WAL");
12
+ this.db.exec("PRAGMA foreign_keys = ON");
13
+ this.migrate();
14
+ }
15
+ close() {
16
+ this.db.close();
17
+ }
18
+ migrate() {
19
+ this.db.exec(`
20
+ CREATE TABLE IF NOT EXISTS resources (
21
+ id TEXT PRIMARY KEY,
22
+ kind TEXT NOT NULL,
23
+ name TEXT NOT NULL,
24
+ source TEXT NOT NULL,
25
+ parent_id TEXT,
26
+ hash TEXT NOT NULL,
27
+ payload TEXT NOT NULL,
28
+ first_seen_at TEXT NOT NULL,
29
+ last_seen_at TEXT NOT NULL
30
+ );
31
+
32
+ CREATE TABLE IF NOT EXISTS snapshots (
33
+ id TEXT PRIMARY KEY,
34
+ name TEXT,
35
+ hash TEXT NOT NULL UNIQUE,
36
+ created_at TEXT NOT NULL,
37
+ resource_count INTEGER NOT NULL,
38
+ summary TEXT NOT NULL
39
+ );
40
+
41
+ CREATE TABLE IF NOT EXISTS snapshot_resources (
42
+ snapshot_id TEXT NOT NULL,
43
+ resource_id TEXT NOT NULL,
44
+ kind TEXT NOT NULL,
45
+ name TEXT NOT NULL,
46
+ source TEXT NOT NULL,
47
+ parent_id TEXT,
48
+ hash TEXT NOT NULL,
49
+ payload TEXT NOT NULL,
50
+ PRIMARY KEY (snapshot_id, resource_id),
51
+ FOREIGN KEY (snapshot_id) REFERENCES snapshots(id) ON DELETE CASCADE
52
+ );
53
+
54
+ CREATE TABLE IF NOT EXISTS policies (
55
+ selector TEXT PRIMARY KEY,
56
+ mode TEXT NOT NULL,
57
+ reason TEXT,
58
+ updated_at TEXT NOT NULL
59
+ );
60
+
61
+ CREATE TABLE IF NOT EXISTS restore_plans (
62
+ id TEXT PRIMARY KEY,
63
+ snapshot_id TEXT NOT NULL,
64
+ created_at TEXT NOT NULL,
65
+ payload TEXT NOT NULL
66
+ );
67
+ `);
68
+ }
69
+ saveSnapshot(resources, options = {}) {
70
+ const createdAt = options.createdAt ?? nowIso();
71
+ const storedResources = resources.map(toStoredResource).sort((a, b) => a.id.localeCompare(b.id));
72
+ const snapshotHash = sha256(stableJson(storedResources.map((resource) => ({
73
+ id: resource.id,
74
+ hash: resource.hash
75
+ }))));
76
+ const existing = this.getSnapshotByHash(snapshotHash);
77
+ if (existing) {
78
+ return { ...existing, duplicateOf: existing.id };
79
+ }
80
+ const id = options.id ?? `snap_${createdAt.replace(/[-:.TZ]/g, "").slice(0, 14)}_${snapshotHash.slice(0, 12)}`;
81
+ const summary = summarizeResources(storedResources, options.diagnostics ?? []);
82
+ const insertResource = this.db.query(`
83
+ INSERT INTO resources (id, kind, name, source, parent_id, hash, payload, first_seen_at, last_seen_at)
84
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
85
+ ON CONFLICT(id) DO UPDATE SET
86
+ kind = excluded.kind,
87
+ name = excluded.name,
88
+ source = excluded.source,
89
+ parent_id = excluded.parent_id,
90
+ hash = excluded.hash,
91
+ payload = excluded.payload,
92
+ last_seen_at = excluded.last_seen_at
93
+ `);
94
+ const insertSnapshot = this.db.query(`
95
+ INSERT INTO snapshots (id, name, hash, created_at, resource_count, summary)
96
+ VALUES (?, ?, ?, ?, ?, ?)
97
+ `);
98
+ const insertSnapshotResource = this.db.query(`
99
+ INSERT INTO snapshot_resources (snapshot_id, resource_id, kind, name, source, parent_id, hash, payload)
100
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?)
101
+ `);
102
+ const transaction = this.db.transaction(() => {
103
+ for (const resource of storedResources) {
104
+ insertResource.run(resource.id, resource.kind, resource.name, resource.source, resource.parentId ?? null, resource.hash, JSON.stringify(resource), createdAt, createdAt);
105
+ }
106
+ insertSnapshot.run(id, options.name ?? null, snapshotHash, createdAt, storedResources.length, JSON.stringify(summary));
107
+ for (const resource of storedResources) {
108
+ insertSnapshotResource.run(id, resource.id, resource.kind, resource.name, resource.source, resource.parentId ?? null, resource.hash, JSON.stringify(resource));
109
+ }
110
+ });
111
+ transaction();
112
+ return {
113
+ id,
114
+ name: options.name,
115
+ hash: snapshotHash,
116
+ createdAt,
117
+ resourceCount: storedResources.length,
118
+ summary
119
+ };
120
+ }
121
+ listSnapshots(limit = 50) {
122
+ return this.db.query("SELECT * FROM snapshots ORDER BY created_at DESC LIMIT ?").all(limit).map(snapshotFromRow);
123
+ }
124
+ getSnapshot(id) {
125
+ const row = this.db.query("SELECT * FROM snapshots WHERE id = ?").get(id);
126
+ return row ? snapshotFromRow(row) : undefined;
127
+ }
128
+ getSnapshotByHash(hash) {
129
+ const row = this.db.query("SELECT * FROM snapshots WHERE hash = ?").get(hash);
130
+ return row ? snapshotFromRow(row) : undefined;
131
+ }
132
+ getSnapshotResources(snapshotId) {
133
+ return this.db
134
+ .query("SELECT payload FROM snapshot_resources WHERE snapshot_id = ? ORDER BY kind, name, resource_id")
135
+ .all(snapshotId)
136
+ .map((row) => JSON.parse(String(row.payload)));
137
+ }
138
+ listResources(limit = 200) {
139
+ return this.db
140
+ .query("SELECT payload FROM resources ORDER BY last_seen_at DESC LIMIT ?")
141
+ .all(limit)
142
+ .map((row) => JSON.parse(String(row.payload)));
143
+ }
144
+ upsertPolicy(selector, mode, reason) {
145
+ const updatedAt = nowIso();
146
+ this.db
147
+ .query(`INSERT INTO policies (selector, mode, reason, updated_at)
148
+ VALUES (?, ?, ?, ?)
149
+ ON CONFLICT(selector) DO UPDATE SET mode = excluded.mode, reason = excluded.reason, updated_at = excluded.updated_at`)
150
+ .run(selector, mode, reason ?? null, updatedAt);
151
+ return { selector, mode, reason, updatedAt };
152
+ }
153
+ listPolicies() {
154
+ return this.db.query("SELECT * FROM policies ORDER BY selector").all().map(policyFromRow);
155
+ }
156
+ saveRestorePlan(plan) {
157
+ this.db
158
+ .query("INSERT OR REPLACE INTO restore_plans (id, snapshot_id, created_at, payload) VALUES (?, ?, ?, ?)")
159
+ .run(plan.id, plan.snapshotId, plan.createdAt, JSON.stringify(plan));
160
+ }
161
+ }
162
+ export function toStoredResource(resource) {
163
+ const payload = {
164
+ id: resource.id,
165
+ kind: resource.kind,
166
+ name: resource.name,
167
+ source: resource.source,
168
+ attributes: resource.attributes
169
+ };
170
+ if (resource.parentId)
171
+ payload.parentId = resource.parentId;
172
+ return {
173
+ ...resource,
174
+ hash: sha256(stableJson(payload))
175
+ };
176
+ }
177
+ function summarizeResources(resources, diagnostics) {
178
+ const byKind = {};
179
+ const bySource = {};
180
+ for (const resource of resources) {
181
+ byKind[resource.kind] = (byKind[resource.kind] ?? 0) + 1;
182
+ bySource[resource.source] = (bySource[resource.source] ?? 0) + 1;
183
+ }
184
+ return {
185
+ by_kind: byKind,
186
+ by_source: bySource,
187
+ diagnostics: diagnostics.map((diagnostic) => ({
188
+ source: diagnostic.source,
189
+ level: diagnostic.level,
190
+ message: diagnostic.message
191
+ }))
192
+ };
193
+ }
194
+ function snapshotFromRow(row) {
195
+ return {
196
+ id: String(row.id),
197
+ name: row.name == null ? undefined : String(row.name),
198
+ hash: String(row.hash),
199
+ createdAt: String(row.created_at),
200
+ resourceCount: Number(row.resource_count),
201
+ summary: JSON.parse(String(row.summary))
202
+ };
203
+ }
204
+ function policyFromRow(row) {
205
+ return {
206
+ selector: String(row.selector),
207
+ mode: row.mode,
208
+ reason: row.reason == null ? undefined : String(row.reason),
209
+ updatedAt: String(row.updated_at)
210
+ };
211
+ }
212
+ export function defaultSnapshotName() {
213
+ return `${basename(process.cwd())}-${nowIso()}`;
214
+ }
215
+ //# sourceMappingURL=storage.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"storage.js","sourceRoot":"","sources":["../src/storage.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,90 @@
1
+ export type JsonPrimitive = string | number | boolean | null;
2
+ export type JsonValue = JsonPrimitive | JsonObject | JsonValue[];
3
+ export interface JsonObject {
4
+ [key: string]: JsonValue;
5
+ }
6
+ export type ResourceKind = "machine" | "project" | "tmux-session" | "tmux-window" | "tmux-pane" | "process" | "service" | "session" | "browser-state" | "desktop-window" | "diagnostic";
7
+ export interface SnapshotResource {
8
+ id: string;
9
+ kind: ResourceKind;
10
+ name: string;
11
+ source: string;
12
+ parentId?: string;
13
+ attributes: JsonObject;
14
+ observedAt: string;
15
+ }
16
+ export interface StoredSnapshotResource extends SnapshotResource {
17
+ hash: string;
18
+ }
19
+ export interface SnapshotRecord {
20
+ id: string;
21
+ name?: string;
22
+ hash: string;
23
+ createdAt: string;
24
+ resourceCount: number;
25
+ summary: JsonObject;
26
+ duplicateOf?: string;
27
+ }
28
+ export interface CaptureDiagnostic {
29
+ source: string;
30
+ level: "info" | "warning" | "error";
31
+ message: string;
32
+ detail?: JsonValue;
33
+ }
34
+ export interface CaptureResult {
35
+ resources: SnapshotResource[];
36
+ diagnostics: CaptureDiagnostic[];
37
+ }
38
+ export type PolicyMode = "observe" | "restore" | "ignore";
39
+ export interface RestorePolicy {
40
+ selector: string;
41
+ mode: PolicyMode;
42
+ reason?: string;
43
+ updatedAt: string;
44
+ }
45
+ export type RestoreOperationStatus = "planned" | "noop" | "blocked" | "skipped" | "applied" | "failed";
46
+ export interface RestoreOperation {
47
+ id: string;
48
+ kind: string;
49
+ resourceId: string;
50
+ resourceKind: ResourceKind;
51
+ summary: string;
52
+ status: RestoreOperationStatus;
53
+ command?: string[];
54
+ reason?: string;
55
+ resource?: StoredSnapshotResource;
56
+ }
57
+ export interface RestorePlan {
58
+ id: string;
59
+ snapshotId: string;
60
+ createdAt: string;
61
+ apply: boolean;
62
+ operations: RestoreOperation[];
63
+ summary: {
64
+ planned: number;
65
+ noop: number;
66
+ blocked: number;
67
+ skipped: number;
68
+ applied: number;
69
+ failed: number;
70
+ };
71
+ }
72
+ export interface StorageOptions {
73
+ path?: string;
74
+ }
75
+ export interface CaptureOptions {
76
+ include?: string[];
77
+ cwd?: string;
78
+ now?: string;
79
+ }
80
+ export interface SnapshotSaveOptions {
81
+ id?: string;
82
+ name?: string;
83
+ createdAt?: string;
84
+ diagnostics?: CaptureDiagnostic[];
85
+ }
86
+ export interface RestoreExecutionOptions {
87
+ apply?: boolean;
88
+ yes?: boolean;
89
+ }
90
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
package/dist/types.js ADDED
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":""}