primitive-admin 1.1.0-alpha.4 → 1.1.0-alpha.41

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 (219) hide show
  1. package/README.md +325 -31
  2. package/assets/skill/skills/primitive-platform/SKILL.md +281 -0
  3. package/dist/bin/primitive.d.ts +2 -0
  4. package/dist/bin/primitive.js +268 -16
  5. package/dist/bin/primitive.js.map +1 -1
  6. package/dist/src/commands/admins.d.ts +2 -0
  7. package/dist/src/commands/admins.js +123 -18
  8. package/dist/src/commands/admins.js.map +1 -1
  9. package/dist/src/commands/analytics.d.ts +2 -0
  10. package/dist/src/commands/analytics.js +464 -55
  11. package/dist/src/commands/analytics.js.map +1 -1
  12. package/dist/src/commands/apps.d.ts +2 -0
  13. package/dist/src/commands/apps.js +55 -13
  14. package/dist/src/commands/apps.js.map +1 -1
  15. package/dist/src/commands/auth.d.ts +2 -0
  16. package/dist/src/commands/auth.js +177 -7
  17. package/dist/src/commands/auth.js.map +1 -1
  18. package/dist/src/commands/blob-buckets.d.ts +2 -0
  19. package/dist/src/commands/blob-buckets.js +422 -0
  20. package/dist/src/commands/blob-buckets.js.map +1 -0
  21. package/dist/src/commands/catalog.d.ts +2 -0
  22. package/dist/src/commands/catalog.js +34 -35
  23. package/dist/src/commands/catalog.js.map +1 -1
  24. package/dist/src/commands/collection-type-configs.d.ts +2 -0
  25. package/dist/src/commands/collection-type-configs.js +178 -0
  26. package/dist/src/commands/collection-type-configs.js.map +1 -0
  27. package/dist/src/commands/collections.d.ts +2 -0
  28. package/dist/src/commands/collections.js +515 -0
  29. package/dist/src/commands/collections.js.map +1 -0
  30. package/dist/src/commands/comparisons.d.ts +2 -0
  31. package/dist/src/commands/comparisons.js +6 -6
  32. package/dist/src/commands/comparisons.js.map +1 -1
  33. package/dist/src/commands/cron-triggers.d.ts +2 -0
  34. package/dist/src/commands/cron-triggers.js +357 -0
  35. package/dist/src/commands/cron-triggers.js.map +1 -0
  36. package/dist/src/commands/database-types.d.ts +2 -0
  37. package/dist/src/commands/database-types.js +471 -0
  38. package/dist/src/commands/database-types.js.map +1 -0
  39. package/dist/src/commands/databases.d.ts +2 -0
  40. package/dist/src/commands/databases.js +1462 -83
  41. package/dist/src/commands/databases.js.map +1 -1
  42. package/dist/src/commands/documents.d.ts +2 -0
  43. package/dist/src/commands/documents.js +518 -11
  44. package/dist/src/commands/documents.js.map +1 -1
  45. package/dist/src/commands/email-templates.d.ts +2 -0
  46. package/dist/src/commands/email-templates.js +281 -0
  47. package/dist/src/commands/email-templates.js.map +1 -0
  48. package/dist/src/commands/env.d.ts +12 -0
  49. package/dist/src/commands/env.js +260 -0
  50. package/dist/src/commands/env.js.map +1 -0
  51. package/dist/src/commands/group-type-configs.d.ts +2 -0
  52. package/dist/src/commands/group-type-configs.js +181 -0
  53. package/dist/src/commands/group-type-configs.js.map +1 -0
  54. package/dist/src/commands/groups.d.ts +2 -0
  55. package/dist/src/commands/groups.js +48 -45
  56. package/dist/src/commands/groups.js.map +1 -1
  57. package/dist/src/commands/guides.d.ts +159 -0
  58. package/dist/src/commands/guides.js +449 -35
  59. package/dist/src/commands/guides.js.map +1 -1
  60. package/dist/src/commands/init.d.ts +17 -0
  61. package/dist/src/commands/init.js +811 -201
  62. package/dist/src/commands/init.js.map +1 -1
  63. package/dist/src/commands/integrations.d.ts +2 -0
  64. package/dist/src/commands/integrations.js +533 -46
  65. package/dist/src/commands/integrations.js.map +1 -1
  66. package/dist/src/commands/llm.d.ts +2 -0
  67. package/dist/src/commands/llm.js +4 -2
  68. package/dist/src/commands/llm.js.map +1 -1
  69. package/dist/src/commands/metadata.d.ts +2 -0
  70. package/dist/src/commands/metadata.js +194 -0
  71. package/dist/src/commands/metadata.js.map +1 -0
  72. package/dist/src/commands/prompts.d.ts +2 -0
  73. package/dist/src/commands/prompts.js +50 -52
  74. package/dist/src/commands/prompts.js.map +1 -1
  75. package/dist/src/commands/rule-sets.d.ts +3 -0
  76. package/dist/src/commands/rule-sets.js +388 -0
  77. package/dist/src/commands/rule-sets.js.map +1 -0
  78. package/dist/src/commands/scripts.d.ts +2 -0
  79. package/dist/src/commands/scripts.js +667 -0
  80. package/dist/src/commands/scripts.js.map +1 -0
  81. package/dist/src/commands/secrets.d.ts +2 -0
  82. package/dist/src/commands/secrets.js +108 -0
  83. package/dist/src/commands/secrets.js.map +1 -0
  84. package/dist/src/commands/skill.d.ts +2 -0
  85. package/dist/src/commands/skill.js +29 -0
  86. package/dist/src/commands/skill.js.map +1 -0
  87. package/dist/src/commands/sync.d.ts +175 -0
  88. package/dist/src/commands/sync.js +4412 -233
  89. package/dist/src/commands/sync.js.map +1 -1
  90. package/dist/src/commands/tokens.d.ts +2 -0
  91. package/dist/src/commands/tokens.js +122 -19
  92. package/dist/src/commands/tokens.js.map +1 -1
  93. package/dist/src/commands/users.d.ts +2 -0
  94. package/dist/src/commands/users.js +440 -22
  95. package/dist/src/commands/users.js.map +1 -1
  96. package/dist/src/commands/waitlist.d.ts +2 -0
  97. package/dist/src/commands/waitlist.js +10 -10
  98. package/dist/src/commands/waitlist.js.map +1 -1
  99. package/dist/src/commands/webhooks.d.ts +2 -0
  100. package/dist/src/commands/webhooks.js +391 -0
  101. package/dist/src/commands/webhooks.js.map +1 -0
  102. package/dist/src/commands/workflows.d.ts +49 -0
  103. package/dist/src/commands/workflows.js +1009 -123
  104. package/dist/src/commands/workflows.js.map +1 -1
  105. package/dist/src/lib/api-client.d.ts +1283 -0
  106. package/dist/src/lib/api-client.js +1129 -83
  107. package/dist/src/lib/api-client.js.map +1 -1
  108. package/dist/src/lib/auth-flow.d.ts +8 -0
  109. package/dist/src/lib/block-layout.d.ts +160 -0
  110. package/dist/src/lib/block-layout.js +451 -0
  111. package/dist/src/lib/block-layout.js.map +1 -0
  112. package/dist/src/lib/cli-manifest.d.ts +60 -0
  113. package/dist/src/lib/cli-manifest.js +70 -0
  114. package/dist/src/lib/cli-manifest.js.map +1 -0
  115. package/dist/src/lib/config.d.ts +53 -0
  116. package/dist/src/lib/config.js +92 -53
  117. package/dist/src/lib/config.js.map +1 -1
  118. package/dist/src/lib/confirm-prompt.d.ts +83 -0
  119. package/dist/src/lib/confirm-prompt.js +110 -0
  120. package/dist/src/lib/confirm-prompt.js.map +1 -0
  121. package/dist/src/lib/constants.d.ts +2 -0
  122. package/dist/src/lib/constants.js +3 -0
  123. package/dist/src/lib/constants.js.map +1 -0
  124. package/dist/src/lib/crash-handlers.d.ts +20 -0
  125. package/dist/src/lib/crash-handlers.js +49 -0
  126. package/dist/src/lib/crash-handlers.js.map +1 -0
  127. package/dist/src/lib/credentials-store.d.ts +79 -0
  128. package/dist/src/lib/credentials-store.js +307 -0
  129. package/dist/src/lib/credentials-store.js.map +1 -0
  130. package/dist/src/lib/csv.d.ts +48 -0
  131. package/dist/src/lib/csv.js +177 -0
  132. package/dist/src/lib/csv.js.map +1 -0
  133. package/dist/src/lib/db-codegen/dbFingerprint.d.ts +10 -0
  134. package/dist/src/lib/db-codegen/dbFingerprint.js +17 -0
  135. package/dist/src/lib/db-codegen/dbFingerprint.js.map +1 -0
  136. package/dist/src/lib/db-codegen/dbGenerator.d.ts +111 -0
  137. package/dist/src/lib/db-codegen/dbGenerator.js +371 -0
  138. package/dist/src/lib/db-codegen/dbGenerator.js.map +1 -0
  139. package/dist/src/lib/db-codegen/dbNaming.d.ts +45 -0
  140. package/dist/src/lib/db-codegen/dbNaming.js +104 -0
  141. package/dist/src/lib/db-codegen/dbNaming.js.map +1 -0
  142. package/dist/src/lib/db-codegen/dbTemplates.d.ts +97 -0
  143. package/dist/src/lib/db-codegen/dbTemplates.js +159 -0
  144. package/dist/src/lib/db-codegen/dbTemplates.js.map +1 -0
  145. package/dist/src/lib/db-codegen/dbTsTypes.d.ts +78 -0
  146. package/dist/src/lib/db-codegen/dbTsTypes.js +112 -0
  147. package/dist/src/lib/db-codegen/dbTsTypes.js.map +1 -0
  148. package/dist/src/lib/env-resolver.d.ts +62 -0
  149. package/dist/src/lib/env-resolver.js +121 -0
  150. package/dist/src/lib/env-resolver.js.map +1 -0
  151. package/dist/src/lib/fetch.d.ts +5 -0
  152. package/dist/src/lib/generated-allowlist.d.ts +28 -0
  153. package/dist/src/lib/generated-allowlist.js +220 -0
  154. package/dist/src/lib/generated-allowlist.js.map +1 -0
  155. package/dist/src/lib/init-config.d.ts +59 -0
  156. package/dist/src/lib/init-config.js +113 -0
  157. package/dist/src/lib/init-config.js.map +1 -0
  158. package/dist/src/lib/migration-nag.d.ts +49 -0
  159. package/dist/src/lib/migration-nag.js +163 -0
  160. package/dist/src/lib/migration-nag.js.map +1 -0
  161. package/dist/src/lib/output.d.ts +86 -0
  162. package/dist/src/lib/output.js +150 -8
  163. package/dist/src/lib/output.js.map +1 -1
  164. package/dist/src/lib/paginate.d.ts +33 -0
  165. package/dist/src/lib/paginate.js +42 -0
  166. package/dist/src/lib/paginate.js.map +1 -0
  167. package/dist/src/lib/project-config.d.ts +97 -0
  168. package/dist/src/lib/project-config.js +209 -0
  169. package/dist/src/lib/project-config.js.map +1 -0
  170. package/dist/src/lib/query-operators.d.ts +43 -0
  171. package/dist/src/lib/query-operators.js +80 -0
  172. package/dist/src/lib/query-operators.js.map +1 -0
  173. package/dist/src/lib/refresh-admin-credentials.d.ts +65 -0
  174. package/dist/src/lib/refresh-admin-credentials.js +103 -0
  175. package/dist/src/lib/refresh-admin-credentials.js.map +1 -0
  176. package/dist/src/lib/resolve-platform.d.ts +45 -0
  177. package/dist/src/lib/resolve-platform.js +43 -0
  178. package/dist/src/lib/resolve-platform.js.map +1 -0
  179. package/dist/src/lib/skill-installer.d.ts +23 -0
  180. package/dist/src/lib/skill-installer.js +135 -0
  181. package/dist/src/lib/skill-installer.js.map +1 -0
  182. package/dist/src/lib/snapshots.d.ts +99 -0
  183. package/dist/src/lib/snapshots.js +357 -0
  184. package/dist/src/lib/snapshots.js.map +1 -0
  185. package/dist/src/lib/sync-paths.d.ts +72 -0
  186. package/dist/src/lib/sync-paths.js +130 -0
  187. package/dist/src/lib/sync-paths.js.map +1 -0
  188. package/dist/src/lib/template.d.ts +97 -0
  189. package/dist/src/lib/template.js +336 -62
  190. package/dist/src/lib/template.js.map +1 -1
  191. package/dist/src/lib/token-inject.d.ts +56 -0
  192. package/dist/src/lib/token-inject.js +204 -0
  193. package/dist/src/lib/token-inject.js.map +1 -0
  194. package/dist/src/lib/toml-database-config.d.ts +152 -0
  195. package/dist/src/lib/toml-database-config.js +600 -0
  196. package/dist/src/lib/toml-database-config.js.map +1 -0
  197. package/dist/src/lib/toml-metadata-config.d.ts +139 -0
  198. package/dist/src/lib/toml-metadata-config.js +427 -0
  199. package/dist/src/lib/toml-metadata-config.js.map +1 -0
  200. package/dist/src/lib/toml-params-validator.d.ts +129 -0
  201. package/dist/src/lib/toml-params-validator.js +298 -0
  202. package/dist/src/lib/toml-params-validator.js.map +1 -0
  203. package/dist/src/lib/version-check.d.ts +10 -0
  204. package/dist/src/lib/version-check.js +172 -0
  205. package/dist/src/lib/version-check.js.map +1 -0
  206. package/dist/src/lib/workflow-apply.d.ts +66 -0
  207. package/dist/src/lib/workflow-apply.js +117 -0
  208. package/dist/src/lib/workflow-apply.js.map +1 -0
  209. package/dist/src/lib/workflow-fragments.d.ts +41 -0
  210. package/dist/src/lib/workflow-fragments.js +121 -0
  211. package/dist/src/lib/workflow-fragments.js.map +1 -0
  212. package/dist/src/lib/workflow-toml-validator.d.ts +95 -0
  213. package/dist/src/lib/workflow-toml-validator.js +323 -0
  214. package/dist/src/lib/workflow-toml-validator.js.map +1 -0
  215. package/dist/src/types/index.d.ts +520 -0
  216. package/dist/src/validators.d.ts +64 -0
  217. package/dist/src/validators.js +63 -0
  218. package/dist/src/validators.js.map +1 -0
  219. package/package.json +18 -4
@@ -0,0 +1,135 @@
1
+ import { existsSync, mkdirSync, readFileSync, writeFileSync, rmSync } from "fs";
2
+ import { readFile } from "fs/promises";
3
+ import { createHash } from "crypto";
4
+ import { homedir } from "os";
5
+ import { join, dirname } from "path";
6
+ import { fileURLToPath } from "url";
7
+ import chalk from "chalk";
8
+ const CLAUDE_DIR = join(homedir(), ".claude");
9
+ const SKILLS_DIR = join(CLAUDE_DIR, "skills");
10
+ const SKILL_DIR = join(SKILLS_DIR, "primitive-platform");
11
+ const SKILL_FILE = join(SKILL_DIR, "SKILL.md");
12
+ const HASH_FILE = join(SKILL_DIR, ".version");
13
+ const __dirname = dirname(fileURLToPath(import.meta.url));
14
+ function getBundledSkillPath() {
15
+ // In dist: dist/src/lib/skill-installer.js -> ../../../assets/...
16
+ // In source (tsx): src/lib/skill-installer.ts -> ../../assets/...
17
+ const distPath = fileURLToPath(new URL("../../../assets/skill/skills/primitive-platform/SKILL.md", import.meta.url));
18
+ if (existsSync(distPath))
19
+ return distPath;
20
+ return fileURLToPath(new URL("../../assets/skill/skills/primitive-platform/SKILL.md", import.meta.url));
21
+ }
22
+ function computeHash(content) {
23
+ return createHash("sha256").update(content).digest("hex").slice(0, 12);
24
+ }
25
+ function getInstalledHash() {
26
+ try {
27
+ if (!existsSync(HASH_FILE))
28
+ return null;
29
+ return readFileSync(HASH_FILE, "utf-8").trim();
30
+ }
31
+ catch {
32
+ return null;
33
+ }
34
+ }
35
+ /**
36
+ * Returns true if the skill is currently installed.
37
+ */
38
+ export function isSkillInstalled() {
39
+ return getInstalledHash() !== null;
40
+ }
41
+ /**
42
+ * Auto-update an already-installed skill if a newer version is bundled.
43
+ * Called after command execution for non-init, non-skill commands.
44
+ * If the skill is not installed, shows a non-blocking hint instead.
45
+ * Never throws — fails silently like version-check.
46
+ */
47
+ export async function checkSkillStatus() {
48
+ try {
49
+ const bundledPath = getBundledSkillPath();
50
+ if (!existsSync(bundledPath))
51
+ return;
52
+ if (!existsSync(CLAUDE_DIR))
53
+ return;
54
+ const bundledContent = await readFile(bundledPath, "utf-8");
55
+ const bundledHash = computeHash(bundledContent);
56
+ const installedHash = getInstalledHash();
57
+ if (installedHash) {
58
+ // Already installed — silently auto-update if needed
59
+ if (installedHash === bundledHash)
60
+ return;
61
+ mkdirSync(SKILL_DIR, { recursive: true });
62
+ writeFileSync(SKILL_FILE, bundledContent);
63
+ writeFileSync(HASH_FILE, bundledHash);
64
+ console.log(chalk.dim("Updated Primitive platform skill for Claude Code"));
65
+ }
66
+ else {
67
+ // Not installed — show a non-blocking hint
68
+ console.log(chalk.dim(`Claude Code skill available — run ${chalk.cyan("primitive skill install")} to set up`));
69
+ }
70
+ }
71
+ catch {
72
+ // Never let skill check affect CLI operation
73
+ }
74
+ }
75
+ /**
76
+ * Explicitly install or reinstall the skill. Shows output.
77
+ */
78
+ export function installSkillExplicit() {
79
+ const bundledPath = getBundledSkillPath();
80
+ if (!existsSync(bundledPath)) {
81
+ console.error(chalk.red("Skill assets not found. Reinstall the CLI."));
82
+ return false;
83
+ }
84
+ if (!existsSync(CLAUDE_DIR)) {
85
+ console.error(chalk.red("Claude Code not found (~/.claude does not exist)."));
86
+ console.error(chalk.dim("Install Claude Code first: https://claude.ai/code"));
87
+ return false;
88
+ }
89
+ const bundledContent = readFileSync(bundledPath, "utf-8");
90
+ const bundledHash = computeHash(bundledContent);
91
+ mkdirSync(SKILL_DIR, { recursive: true });
92
+ writeFileSync(SKILL_FILE, bundledContent);
93
+ writeFileSync(HASH_FILE, bundledHash);
94
+ console.log(chalk.green("Primitive platform skill installed for Claude Code."));
95
+ console.log(chalk.dim(`Location: ${SKILL_FILE}`));
96
+ console.log(chalk.dim(`Version: ${bundledHash}`));
97
+ return true;
98
+ }
99
+ /**
100
+ * Uninstall the skill.
101
+ */
102
+ export function uninstallSkill() {
103
+ if (!existsSync(SKILL_DIR)) {
104
+ console.log(chalk.dim("Skill is not installed."));
105
+ return true;
106
+ }
107
+ rmSync(SKILL_DIR, { recursive: true });
108
+ console.log(chalk.green("Primitive platform skill uninstalled."));
109
+ return true;
110
+ }
111
+ /**
112
+ * Show current skill installation status.
113
+ */
114
+ export function skillStatus() {
115
+ const installedHash = getInstalledHash();
116
+ const bundledPath = getBundledSkillPath();
117
+ const bundledHash = existsSync(bundledPath)
118
+ ? computeHash(readFileSync(bundledPath, "utf-8"))
119
+ : null;
120
+ if (!installedHash) {
121
+ console.log(chalk.yellow("Skill is not installed."));
122
+ }
123
+ else {
124
+ console.log(chalk.green("Skill is installed."));
125
+ console.log(chalk.dim(` Location: ${SKILL_FILE}`));
126
+ console.log(chalk.dim(` Version: ${installedHash}`));
127
+ }
128
+ if (bundledHash) {
129
+ console.log(chalk.dim(` Bundled: ${bundledHash}`));
130
+ if (installedHash && installedHash !== bundledHash) {
131
+ console.log(chalk.yellow(" Update available — run: primitive skill install"));
132
+ }
133
+ }
134
+ }
135
+ //# sourceMappingURL=skill-installer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"skill-installer.js","sourceRoot":"","sources":["../../../src/lib/skill-installer.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,99 @@
1
+ /**
2
+ * Sync snapshots — point-in-time backups of a sync directory taken before a
3
+ * destructive `sync pull` overwrites local TOML state (issue #578, Phase 1).
4
+ *
5
+ * A snapshot is a full recursive copy of the live sync tree (including
6
+ * `.primitive-sync.json`, the content-hash baseline) into a timestamped slot
7
+ * under the snapshots root:
8
+ *
9
+ * <snapshotsRoot>/<YYYY-MM-DDTHH-MM-SS>/
10
+ * <full pre-pull copy of the sync tree, incl. .primitive-sync.json>
11
+ * .snapshot-complete # integrity marker, written LAST
12
+ * .audit-id # ULID cross-link (Phase 2; absent in P1)
13
+ *
14
+ * The `.snapshot-complete` marker is written last so a partial/interrupted
15
+ * copy is detectable: `restoreSnapshot` refuses any snapshot missing it.
16
+ *
17
+ * Snapshots fail LOUD — `createSnapshot` throws if it can't write a complete
18
+ * snapshot. The caller (sync pull) aborts before touching any local file so we
19
+ * never perform a destructive pull without a recoverable backup.
20
+ *
21
+ * Restore is staged-then-swapped: the snapshot is materialized into a sibling
22
+ * temp dir, then the live `configDir` is atomically replaced, so a mid-restore
23
+ * crash never leaves a half-written tree.
24
+ */
25
+ /** Marker file written LAST inside a snapshot to prove it completed. */
26
+ export declare const SNAPSHOT_COMPLETE_MARKER = ".snapshot-complete";
27
+ /** Cross-link to the audit entry that produced the snapshot (Phase 2). */
28
+ export declare const SNAPSHOT_AUDIT_ID_MARKER = ".audit-id";
29
+ export interface SnapshotResult {
30
+ /** Snapshot id — the timestamp directory name (YYYY-MM-DDTHH-MM-SS). */
31
+ id: string;
32
+ /** Absolute path to the snapshot directory. */
33
+ path: string;
34
+ }
35
+ export interface SnapshotInfo {
36
+ /** Snapshot id — the timestamp directory name. */
37
+ id: string;
38
+ /** Absolute path to the snapshot directory. */
39
+ path: string;
40
+ /** Whether the `.snapshot-complete` marker is present. */
41
+ complete: boolean;
42
+ /** The audit entry id this snapshot is cross-linked to, if any. */
43
+ auditId: string | null;
44
+ /** Creation time (from the directory mtime). */
45
+ createdAt: Date;
46
+ }
47
+ /**
48
+ * Snapshot the current `syncDir` into a new timestamped slot under
49
+ * `snapshotsRoot`. Returns the created snapshot, or `null` when there's
50
+ * nothing to back up (missing/empty `syncDir`).
51
+ *
52
+ * Fails LOUD: throws if the copy or marker write fails. The partial snapshot
53
+ * dir is best-effort cleaned up before rethrowing so a failed snapshot never
54
+ * leaves a marker-less husk behind.
55
+ */
56
+ export declare function createSnapshot(syncDir: string, snapshotsRoot: string, opts?: {
57
+ auditId?: string;
58
+ now?: Date;
59
+ }): SnapshotResult | null;
60
+ /**
61
+ * List snapshots under `snapshotsRoot`, newest-first. Returns an empty array
62
+ * if the root doesn't exist. Each entry reports whether it's complete and any
63
+ * cross-linked audit id.
64
+ */
65
+ export declare function listSnapshots(snapshotsRoot: string): SnapshotInfo[];
66
+ /**
67
+ * Resolve a snapshot by id within `snapshotsRoot`. Accepts either the full
68
+ * timestamp directory name OR a unique prefix of at least `minPrefix`
69
+ * characters (default 8). Returns the matching snapshot, or throws a clear
70
+ * error on no-match / ambiguous-prefix.
71
+ *
72
+ * When `id` is omitted, resolves to the most recent COMPLETE snapshot.
73
+ */
74
+ export declare function resolveSnapshot(snapshotsRoot: string, id: string | undefined, minPrefix?: number): SnapshotInfo;
75
+ /**
76
+ * Restore a snapshot into `syncDir`, replacing whatever is there.
77
+ *
78
+ * Refuses to restore a snapshot missing the `.snapshot-complete` marker
79
+ * (partial/corrupted snapshot). The internal marker files (`.snapshot-complete`,
80
+ * `.audit-id`) are NOT copied into the restored tree.
81
+ *
82
+ * Staged-then-swapped: the snapshot is materialized into a sibling temp dir,
83
+ * then `syncDir` is atomically replaced, so a mid-restore crash never leaves a
84
+ * half-written tree.
85
+ *
86
+ * `opts.preserveDir` names a directory (the snapshots root) that lives INSIDE
87
+ * `syncDir` and must survive the full-tree swap — the Fork-4 legacy `--dir`
88
+ * case, where backups sit at `<syncDir>/.snapshots/`. Without preserving it,
89
+ * the swap would wipe the user's entire snapshot history on restore.
90
+ */
91
+ export declare function restoreSnapshot(snapshotPath: string, syncDir: string, opts?: {
92
+ preserveDir?: string;
93
+ }): void;
94
+ /**
95
+ * Prune snapshots older than `retentionDays` from `snapshotsRoot`. Only the
96
+ * given slot is touched (cross-slot retention is independent — each slot has
97
+ * its own snapshots root). No-op if the root doesn't exist.
98
+ */
99
+ export declare function pruneSnapshots(snapshotsRoot: string, retentionDays?: number): void;
@@ -0,0 +1,357 @@
1
+ /**
2
+ * Sync snapshots — point-in-time backups of a sync directory taken before a
3
+ * destructive `sync pull` overwrites local TOML state (issue #578, Phase 1).
4
+ *
5
+ * A snapshot is a full recursive copy of the live sync tree (including
6
+ * `.primitive-sync.json`, the content-hash baseline) into a timestamped slot
7
+ * under the snapshots root:
8
+ *
9
+ * <snapshotsRoot>/<YYYY-MM-DDTHH-MM-SS>/
10
+ * <full pre-pull copy of the sync tree, incl. .primitive-sync.json>
11
+ * .snapshot-complete # integrity marker, written LAST
12
+ * .audit-id # ULID cross-link (Phase 2; absent in P1)
13
+ *
14
+ * The `.snapshot-complete` marker is written last so a partial/interrupted
15
+ * copy is detectable: `restoreSnapshot` refuses any snapshot missing it.
16
+ *
17
+ * Snapshots fail LOUD — `createSnapshot` throws if it can't write a complete
18
+ * snapshot. The caller (sync pull) aborts before touching any local file so we
19
+ * never perform a destructive pull without a recoverable backup.
20
+ *
21
+ * Restore is staged-then-swapped: the snapshot is materialized into a sibling
22
+ * temp dir, then the live `configDir` is atomically replaced, so a mid-restore
23
+ * crash never leaves a half-written tree.
24
+ */
25
+ import { existsSync, readdirSync, statSync, writeFileSync, readFileSync, cpSync, rmSync, renameSync, mkdirSync, mkdtempSync, } from "fs";
26
+ import { join, dirname, resolve, relative, isAbsolute } from "path";
27
+ /**
28
+ * Returns true if `child` is `parent` or lives somewhere beneath it. Used to
29
+ * detect the legacy `--dir` case where the snapshots root sits INSIDE the sync
30
+ * tree (`<userDir>/.snapshots/`) — we must not copy that subtree into the
31
+ * snapshot, or `cpSync` recurses into itself.
32
+ */
33
+ function isInside(parent, child) {
34
+ const rel = relative(resolve(parent), resolve(child));
35
+ return rel === "" || (!rel.startsWith("..") && !isAbsolute(rel));
36
+ }
37
+ /** Marker file written LAST inside a snapshot to prove it completed. */
38
+ export const SNAPSHOT_COMPLETE_MARKER = ".snapshot-complete";
39
+ /** Cross-link to the audit entry that produced the snapshot (Phase 2). */
40
+ export const SNAPSHOT_AUDIT_ID_MARKER = ".audit-id";
41
+ /**
42
+ * Produce the timestamp directory name for a snapshot, e.g.
43
+ * `2026-06-01T17-42-18`. Colons (illegal on Windows, awkward everywhere) are
44
+ * replaced with hyphens; milliseconds and the trailing `Z` are dropped.
45
+ */
46
+ function snapshotTimestamp(now = new Date()) {
47
+ // 2026-06-01T17:42:18.401Z -> 2026-06-01T17-42-18
48
+ return now.toISOString().replace(/\.\d+Z$/, "").replace(/:/g, "-");
49
+ }
50
+ /**
51
+ * Returns true if `dir` exists and contains at least one entry. A fresh
52
+ * project (no prior sync dir, or an empty one) has nothing to back up.
53
+ */
54
+ function dirHasContent(dir) {
55
+ if (!existsSync(dir))
56
+ return false;
57
+ try {
58
+ return readdirSync(dir).length > 0;
59
+ }
60
+ catch {
61
+ return false;
62
+ }
63
+ }
64
+ /**
65
+ * Snapshot the current `syncDir` into a new timestamped slot under
66
+ * `snapshotsRoot`. Returns the created snapshot, or `null` when there's
67
+ * nothing to back up (missing/empty `syncDir`).
68
+ *
69
+ * Fails LOUD: throws if the copy or marker write fails. The partial snapshot
70
+ * dir is best-effort cleaned up before rethrowing so a failed snapshot never
71
+ * leaves a marker-less husk behind.
72
+ */
73
+ export function createSnapshot(syncDir, snapshotsRoot, opts = {}) {
74
+ if (!dirHasContent(syncDir)) {
75
+ return null;
76
+ }
77
+ const id = snapshotTimestamp(opts.now);
78
+ const dest = join(snapshotsRoot, id);
79
+ // Guard against an id collision (two pulls within the same second). Append a
80
+ // numeric suffix until we find a free slot so a rapid second pull can't
81
+ // clobber the first snapshot.
82
+ let finalId = id;
83
+ let finalDest = dest;
84
+ let suffix = 1;
85
+ while (existsSync(finalDest)) {
86
+ finalId = `${id}-${suffix}`;
87
+ finalDest = join(snapshotsRoot, finalId);
88
+ suffix += 1;
89
+ }
90
+ // Fork 4: under legacy `--dir`, the snapshots root lives INSIDE the sync
91
+ // tree (`<userDir>/.snapshots/`). Node's cpSync refuses to copy a directory
92
+ // into its own subtree (ERR_FS_CP_EINVAL) even with a filter, so in that
93
+ // case we stage the copy into an external temp dir (excluding the backups
94
+ // subtree) and then move it into place.
95
+ const rootIsInside = isInside(syncDir, snapshotsRoot);
96
+ const snapshotsRootResolved = resolve(snapshotsRoot);
97
+ const filter = rootIsInside
98
+ ? (src) => !isInside(snapshotsRootResolved, src)
99
+ : undefined;
100
+ let externalStage = null;
101
+ try {
102
+ mkdirSync(snapshotsRoot, { recursive: true });
103
+ if (rootIsInside) {
104
+ // Copy to a sibling of syncDir first (outside the sync tree, so no
105
+ // self-recursion), then move into the inside-the-tree slot. Staging in
106
+ // the parent keeps the move same-filesystem (avoids cross-device EXDEV
107
+ // that a tmpdir stage could hit).
108
+ const parent = dirname(resolve(syncDir));
109
+ mkdirSync(parent, { recursive: true });
110
+ externalStage = mkdtempSync(join(parent, ".primitive-snapshot-stage-"));
111
+ const stageTree = join(externalStage, "tree");
112
+ cpSync(syncDir, stageTree, {
113
+ recursive: true,
114
+ dereference: false,
115
+ ...(filter ? { filter } : {}),
116
+ });
117
+ renameSync(stageTree, finalDest);
118
+ }
119
+ else {
120
+ // Full recursive copy. `dereference: false` keeps symlinks from being
121
+ // silently followed (edge case: symlinks inside the sync tree).
122
+ cpSync(syncDir, finalDest, { recursive: true, dereference: false });
123
+ }
124
+ if (opts.auditId) {
125
+ writeFileSync(join(finalDest, SNAPSHOT_AUDIT_ID_MARKER), opts.auditId);
126
+ }
127
+ // Integrity marker written LAST — its presence proves the copy finished.
128
+ writeFileSync(join(finalDest, SNAPSHOT_COMPLETE_MARKER), new Date().toISOString());
129
+ }
130
+ catch (err) {
131
+ // Best-effort cleanup of the partial snapshot so we don't leave an
132
+ // incomplete (marker-less) directory around.
133
+ try {
134
+ rmSync(finalDest, { recursive: true, force: true });
135
+ }
136
+ catch {
137
+ // ignore cleanup failure
138
+ }
139
+ throw err;
140
+ }
141
+ finally {
142
+ if (externalStage) {
143
+ try {
144
+ rmSync(externalStage, { recursive: true, force: true });
145
+ }
146
+ catch {
147
+ // ignore
148
+ }
149
+ }
150
+ }
151
+ return { id: finalId, path: finalDest };
152
+ }
153
+ /**
154
+ * List snapshots under `snapshotsRoot`, newest-first. Returns an empty array
155
+ * if the root doesn't exist. Each entry reports whether it's complete and any
156
+ * cross-linked audit id.
157
+ */
158
+ export function listSnapshots(snapshotsRoot) {
159
+ if (!existsSync(snapshotsRoot))
160
+ return [];
161
+ let names;
162
+ try {
163
+ names = readdirSync(snapshotsRoot);
164
+ }
165
+ catch {
166
+ return [];
167
+ }
168
+ const infos = [];
169
+ for (const name of names) {
170
+ const path = join(snapshotsRoot, name);
171
+ let stat;
172
+ try {
173
+ stat = statSync(path);
174
+ }
175
+ catch {
176
+ continue;
177
+ }
178
+ if (!stat.isDirectory())
179
+ continue;
180
+ const complete = existsSync(join(path, SNAPSHOT_COMPLETE_MARKER));
181
+ let auditId = null;
182
+ const auditPath = join(path, SNAPSHOT_AUDIT_ID_MARKER);
183
+ if (existsSync(auditPath)) {
184
+ try {
185
+ auditId = readFileSync(auditPath, "utf-8").trim() || null;
186
+ }
187
+ catch {
188
+ auditId = null;
189
+ }
190
+ }
191
+ infos.push({ id: name, path, complete, auditId, createdAt: stat.mtime });
192
+ }
193
+ // Newest first. The id is a sortable timestamp string, so a reverse string
194
+ // sort matches chronological order (and stays correct for the `-N` suffixed
195
+ // collision variants within the same second).
196
+ infos.sort((a, b) => (a.id < b.id ? 1 : a.id > b.id ? -1 : 0));
197
+ return infos;
198
+ }
199
+ /**
200
+ * Resolve a snapshot by id within `snapshotsRoot`. Accepts either the full
201
+ * timestamp directory name OR a unique prefix of at least `minPrefix`
202
+ * characters (default 8). Returns the matching snapshot, or throws a clear
203
+ * error on no-match / ambiguous-prefix.
204
+ *
205
+ * When `id` is omitted, resolves to the most recent COMPLETE snapshot.
206
+ */
207
+ export function resolveSnapshot(snapshotsRoot, id, minPrefix = 8) {
208
+ const all = listSnapshots(snapshotsRoot);
209
+ if (all.length === 0) {
210
+ throw new Error(`No snapshots found in ${snapshotsRoot}`);
211
+ }
212
+ if (!id) {
213
+ const mostRecentComplete = all.find((s) => s.complete);
214
+ if (!mostRecentComplete) {
215
+ throw new Error(`No complete snapshots found in ${snapshotsRoot} (all are missing the ${SNAPSHOT_COMPLETE_MARKER} marker).`);
216
+ }
217
+ return mostRecentComplete;
218
+ }
219
+ // Exact match wins.
220
+ const exact = all.find((s) => s.id === id);
221
+ if (exact)
222
+ return exact;
223
+ // Prefix match (must be unique and >= minPrefix chars).
224
+ if (id.length < minPrefix) {
225
+ throw new Error(`Snapshot id "${id}" is too short — provide at least ${minPrefix} characters or the full timestamp.`);
226
+ }
227
+ const matches = all.filter((s) => s.id.startsWith(id));
228
+ if (matches.length === 0) {
229
+ throw new Error(`No snapshot matches "${id}" in ${snapshotsRoot}`);
230
+ }
231
+ if (matches.length > 1) {
232
+ throw new Error(`Snapshot prefix "${id}" is ambiguous (${matches.length} matches: ${matches
233
+ .map((m) => m.id)
234
+ .join(", ")}). Provide more characters.`);
235
+ }
236
+ return matches[0];
237
+ }
238
+ /**
239
+ * Restore a snapshot into `syncDir`, replacing whatever is there.
240
+ *
241
+ * Refuses to restore a snapshot missing the `.snapshot-complete` marker
242
+ * (partial/corrupted snapshot). The internal marker files (`.snapshot-complete`,
243
+ * `.audit-id`) are NOT copied into the restored tree.
244
+ *
245
+ * Staged-then-swapped: the snapshot is materialized into a sibling temp dir,
246
+ * then `syncDir` is atomically replaced, so a mid-restore crash never leaves a
247
+ * half-written tree.
248
+ *
249
+ * `opts.preserveDir` names a directory (the snapshots root) that lives INSIDE
250
+ * `syncDir` and must survive the full-tree swap — the Fork-4 legacy `--dir`
251
+ * case, where backups sit at `<syncDir>/.snapshots/`. Without preserving it,
252
+ * the swap would wipe the user's entire snapshot history on restore.
253
+ */
254
+ export function restoreSnapshot(snapshotPath, syncDir, opts = {}) {
255
+ if (!existsSync(snapshotPath)) {
256
+ throw new Error(`Snapshot not found: ${snapshotPath}`);
257
+ }
258
+ if (!existsSync(join(snapshotPath, SNAPSHOT_COMPLETE_MARKER))) {
259
+ throw new Error(`Refusing to restore: snapshot ${snapshotPath} is missing the ${SNAPSHOT_COMPLETE_MARKER} marker (partial/corrupted snapshot).`);
260
+ }
261
+ const parent = dirname(syncDir);
262
+ mkdirSync(parent, { recursive: true });
263
+ const unique = `${Date.now()}-${Math.floor(Math.random() * 1e6)}`;
264
+ const stageDir = join(parent, `.primitive-restore-stage-${unique}`);
265
+ const oldDir = join(parent, `.primitive-restore-old-${unique}`);
266
+ // Determine whether a backups dir living inside syncDir needs preserving.
267
+ const preserve = opts.preserveDir &&
268
+ isInside(syncDir, opts.preserveDir) &&
269
+ !isInside(snapshotPath, opts.preserveDir) // don't preserve a dir that's part of the snapshot
270
+ ? resolve(opts.preserveDir)
271
+ : null;
272
+ const preserveStash = preserve
273
+ ? join(parent, `.primitive-restore-keep-${unique}`)
274
+ : null;
275
+ try {
276
+ // Materialize the snapshot into the staging dir.
277
+ cpSync(snapshotPath, stageDir, { recursive: true, dereference: false });
278
+ // Drop the snapshot-internal marker files from the restored tree.
279
+ for (const marker of [SNAPSHOT_COMPLETE_MARKER, SNAPSHOT_AUDIT_ID_MARKER]) {
280
+ const markerPath = join(stageDir, marker);
281
+ if (existsSync(markerPath)) {
282
+ rmSync(markerPath, { force: true });
283
+ }
284
+ }
285
+ // Atomic-ish swap: move the live dir aside, move staging into place, then
286
+ // delete the old dir. Same-filesystem renames are atomic.
287
+ if (existsSync(syncDir)) {
288
+ renameSync(syncDir, oldDir);
289
+ }
290
+ try {
291
+ renameSync(stageDir, syncDir);
292
+ }
293
+ catch (err) {
294
+ // Swap failed — try to roll the original back so we never leave the
295
+ // user with no sync dir at all.
296
+ if (existsSync(oldDir) && !existsSync(syncDir)) {
297
+ try {
298
+ renameSync(oldDir, syncDir);
299
+ }
300
+ catch {
301
+ // ignore rollback failure; original error is more useful
302
+ }
303
+ }
304
+ throw err;
305
+ }
306
+ // Re-attach the preserved backups dir (Fork-4 `--dir` mode) into the
307
+ // freshly-restored tree, pulling it out of the displaced original.
308
+ if (preserve && preserveStash) {
309
+ const rel = relative(resolve(syncDir), preserve);
310
+ const fromOld = join(oldDir, rel);
311
+ const intoNew = join(syncDir, rel);
312
+ if (existsSync(fromOld) && !existsSync(intoNew)) {
313
+ mkdirSync(dirname(intoNew), { recursive: true });
314
+ renameSync(fromOld, intoNew);
315
+ }
316
+ }
317
+ // Success — remove the displaced original.
318
+ if (existsSync(oldDir)) {
319
+ rmSync(oldDir, { recursive: true, force: true });
320
+ }
321
+ }
322
+ finally {
323
+ // Clean up any leftover staging/old/stash dirs on failure.
324
+ for (const dir of [stageDir, oldDir, preserveStash].filter((d) => Boolean(d))) {
325
+ if (existsSync(dir)) {
326
+ try {
327
+ rmSync(dir, { recursive: true, force: true });
328
+ }
329
+ catch {
330
+ // ignore
331
+ }
332
+ }
333
+ }
334
+ }
335
+ }
336
+ /**
337
+ * Prune snapshots older than `retentionDays` from `snapshotsRoot`. Only the
338
+ * given slot is touched (cross-slot retention is independent — each slot has
339
+ * its own snapshots root). No-op if the root doesn't exist.
340
+ */
341
+ export function pruneSnapshots(snapshotsRoot, retentionDays = 28) {
342
+ if (!existsSync(snapshotsRoot))
343
+ return;
344
+ const cutoff = Date.now() - retentionDays * 24 * 60 * 60 * 1000;
345
+ for (const info of listSnapshots(snapshotsRoot)) {
346
+ if (info.createdAt.getTime() < cutoff) {
347
+ try {
348
+ rmSync(info.path, { recursive: true, force: true });
349
+ }
350
+ catch {
351
+ // Best-effort prune — a failure to delete one stale snapshot must not
352
+ // abort the pull that triggered the prune.
353
+ }
354
+ }
355
+ }
356
+ }
357
+ //# sourceMappingURL=snapshots.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"snapshots.js","sourceRoot":"","sources":["../../../src/lib/snapshots.ts"],"names":[],"mappings":"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"}