@anhlp/alp-core 0.0.0-beta.1 → 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 (57) hide show
  1. package/dist/hook-run.d.ts +1 -1
  2. package/dist/hook-run.js +9 -3
  3. package/dist/index.d.ts +0 -5
  4. package/dist/index.js +0 -10
  5. package/dist/library-edit.js +1 -11
  6. package/dist/library.js +2 -4
  7. package/dist/permissions.js +6 -9
  8. package/dist/providers.d.ts +0 -12
  9. package/dist/providers.js +0 -18
  10. package/dist/task-rows.d.ts +0 -1
  11. package/dist/task-rows.js +0 -1
  12. package/dist/tasks.d.ts +1 -2
  13. package/dist/tasks.js +10 -25
  14. package/dist/trust.js +2 -3
  15. package/dist/user-settings.js +5 -6
  16. package/dist/verify.js +15 -3
  17. package/package.json +6 -53
  18. package/src/hook-run.js +4 -4
  19. package/src/index.js +0 -5
  20. package/src/library-edit.js +1 -14
  21. package/src/library.js +2 -4
  22. package/src/permissions.js +6 -9
  23. package/src/providers.js +0 -28
  24. package/src/task-rows.js +1 -1
  25. package/src/tasks.d.ts +1 -2
  26. package/src/tasks.js +9 -28
  27. package/src/trust.js +2 -3
  28. package/src/user-settings.js +5 -6
  29. package/src/verify.js +6 -3
  30. package/LICENSE +0 -202
  31. package/NOTICE +0 -7
  32. package/dist/atomic.d.ts +0 -2
  33. package/dist/atomic.js +0 -28
  34. package/dist/backup.d.ts +0 -27
  35. package/dist/backup.js +0 -582
  36. package/dist/log.d.ts +0 -37
  37. package/dist/log.js +0 -156
  38. package/dist/migrations.d.ts +0 -9
  39. package/dist/migrations.js +0 -95
  40. package/dist/safe-path.d.ts +0 -2
  41. package/dist/safe-path.js +0 -24
  42. package/dist/shell.d.ts +0 -16
  43. package/dist/shell.js +0 -22
  44. package/dist/state-schema.d.ts +0 -9
  45. package/dist/state-schema.js +0 -54
  46. package/src/atomic.d.ts +0 -2
  47. package/src/atomic.js +0 -28
  48. package/src/backup.d.ts +0 -27
  49. package/src/backup.js +0 -570
  50. package/src/log.d.ts +0 -37
  51. package/src/log.js +0 -177
  52. package/src/migrations.d.ts +0 -9
  53. package/src/migrations.js +0 -111
  54. package/src/safe-path.js +0 -20
  55. package/src/shell.js +0 -28
  56. package/src/state-schema.d.ts +0 -9
  57. package/src/state-schema.js +0 -60
package/dist/log.js DELETED
@@ -1,156 +0,0 @@
1
- // packages/core/src/log.js
2
- import { open, stat } from "node:fs/promises";
3
- var LEVELS = Object.freeze({ debug: 10, info: 20, warn: 30, error: 40 });
4
- var REDACTED = "[redacted]";
5
- var OMITTED = "[omitted]";
6
- var MAX_STRING = 2e3;
7
- var MAX_DEPTH = 5;
8
- var MAX_KEYS = 50;
9
- var MAX_ITEMS = 20;
10
- var STACK_LINES = 8;
11
- var SECRET_KEY = /pass(word|wd)?|secret|token|api[-_]?key|authorization|credential|cookie|private[-_]?key|bearer|signature/i;
12
- var OMITTED_KEY = /^(env|environment|prompt|prompts|instructions|content|text|input|headers|body|args|argv)$/i;
13
- var SECRET_NAME = /KEY|TOKEN|SECRET|PASSWORD|PASSWD|CREDENTIAL|AUTH|COOKIE|PRIVATE/i;
14
- var PATTERNS = [
15
- [/\b(Bearer|Basic)\s+[A-Za-z0-9._~+/=-]{8,}/gi, `$1 ${REDACTED}`],
16
- [/\b(sk|rk|pk)-[A-Za-z0-9_-]{16,}/g, REDACTED],
17
- [/\bgh[pousr]_[A-Za-z0-9]{20,}/g, REDACTED],
18
- [/\bgithub_pat_[A-Za-z0-9_]{20,}/g, REDACTED],
19
- [/\bxox[abprs]-[A-Za-z0-9-]{10,}/g, REDACTED],
20
- [/\bAKIA[0-9A-Z]{16}\b/g, REDACTED],
21
- [/\bAIza[0-9A-Za-z_-]{30,}/g, REDACTED],
22
- [/\beyJ[A-Za-z0-9_-]{8,}\.eyJ[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{4,}/g, REDACTED],
23
- [/-----BEGIN [A-Z ]*PRIVATE KEY-----[\s\S]*?(-----END [A-Z ]*PRIVATE KEY-----|$)/g, REDACTED],
24
- [/(\b[a-z][a-z0-9+.-]*:\/\/)[^\s/@:]+:[^\s/@]+@/gi, `$1${REDACTED}@`],
25
- // NAME=value and NAME: value for names that look like secrets; also JSON "token": "value".
26
- [/(\b[\w.-]*(?:key|token|secret|passw(?:or)?d|credential|authorization|cookie)[\w.-]*["']?\s*[=:]\s*)(?:"[^"]{4,}"|'[^']{4,}'|[^\s,;&"']{8,})/gi, `$1${REDACTED}`],
27
- // --token value, --api-key value
28
- [/(--[\w-]*(?:key|token|secret|passw(?:or)?d|credential)[\w-]*\s+)[^\s-][^\s]*/gi, `$1${REDACTED}`]
29
- ];
30
- function secretValues(env) {
31
- const values = /* @__PURE__ */ new Set();
32
- for (const [name, value] of Object.entries(env ?? {})) if (typeof value === "string" && value.length >= 8 && SECRET_NAME.test(name)) values.add(value);
33
- return [...values].sort((a, b) => b.length - a.length);
34
- }
35
- function scrubText(text, { env = process.env, max = MAX_STRING } = {}) {
36
- let result = String(text);
37
- for (const value of secretValues(env)) result = result.split(value).join(REDACTED);
38
- for (const [pattern, replacement] of PATTERNS) result = result.replace(pattern, replacement);
39
- return result.length > max ? `${result.slice(0, max)}... (${result.length - max} more characters)` : result;
40
- }
41
- function describeError(error, options = {}) {
42
- if (!(error instanceof Error)) return { message: scrubText(error, options) };
43
- const stack = typeof error.stack === "string" ? error.stack.split("\n").slice(0, STACK_LINES).join("\n") : void 0;
44
- return {
45
- name: error.name,
46
- ...typeof error.code === "string" || typeof error.code === "number" ? { code: error.code } : {},
47
- message: scrubText(error.message, options),
48
- ...stack ? { stack: scrubText(stack, options) } : {},
49
- ...error.cause !== void 0 ? { cause: describeError(error.cause, options) } : {}
50
- };
51
- }
52
- function redact(value, options = {}, depth = 0, seen = /* @__PURE__ */ new WeakSet()) {
53
- if (value === null || value === void 0 || typeof value === "boolean" || typeof value === "number") return value;
54
- if (typeof value === "string") return scrubText(value, options);
55
- if (typeof value === "bigint") return String(value);
56
- if (typeof value !== "object") return void 0;
57
- if (value instanceof Error) return describeError(value, options);
58
- if (seen.has(value)) return "[circular]";
59
- if (depth >= MAX_DEPTH) return "[too deep]";
60
- seen.add(value);
61
- if (Array.isArray(value)) {
62
- const items = value.slice(0, MAX_ITEMS).map((item) => redact(item, options, depth + 1, seen));
63
- return value.length > MAX_ITEMS ? [...items, `... ${value.length - MAX_ITEMS} more`] : items;
64
- }
65
- const result = {};
66
- for (const [key, item] of Object.entries(value).slice(0, MAX_KEYS)) {
67
- result[key] = OMITTED_KEY.test(key) ? OMITTED : SECRET_KEY.test(key) ? REDACTED : redact(item, options, depth + 1, seen);
68
- }
69
- return result;
70
- }
71
- var CORRELATION = ["sessionId", "rootId", "assignmentId", "clientMessageId", "requestId", "agent"];
72
- var stderrSink = (line) => {
73
- try {
74
- process.stderr.write(`${line}
75
- `);
76
- } catch {
77
- }
78
- };
79
- function createLogger({ component, level, context = {}, write = stderrSink, now = () => /* @__PURE__ */ new Date(), env = process.env } = {}) {
80
- const threshold = LEVELS[level ?? env.ALP_LOG_LEVEL] ?? LEVELS.info;
81
- const options = { env };
82
- const emit = (name, message, fields = {}) => {
83
- if (LEVELS[name] < threshold) return;
84
- try {
85
- const { code, error, ...rest } = { ...context, ...fields };
86
- const record = {
87
- time: now().toISOString(),
88
- level: name,
89
- component,
90
- ...code ? { code: String(code) } : {},
91
- msg: scrubText(message, options)
92
- };
93
- for (const key of CORRELATION) if (rest[key] !== void 0) record[key] = redact(String(rest[key]), options);
94
- if (error !== void 0) {
95
- record.error = describeError(error, options);
96
- if (!record.code && record.error.code !== void 0) record.code = String(record.error.code);
97
- }
98
- for (const [key, value] of Object.entries(rest)) {
99
- if (CORRELATION.includes(key)) continue;
100
- record[key] = OMITTED_KEY.test(key) ? OMITTED : SECRET_KEY.test(key) ? REDACTED : redact(value, options);
101
- }
102
- write(JSON.stringify(record));
103
- } catch {
104
- }
105
- };
106
- return {
107
- component,
108
- debug: (message, fields) => emit("debug", message, fields),
109
- info: (message, fields) => emit("info", message, fields),
110
- warn: (message, fields) => emit("warn", message, fields),
111
- error: (message, fields) => emit("error", message, fields),
112
- child: (extra = {}, name = component) => createLogger({ component: name, level, context: { ...context, ...extra }, write, now, env })
113
- };
114
- }
115
- var TAIL_BYTES = 256 * 1024;
116
- async function readLogTail(file, { bytes = TAIL_BYTES, env = process.env } = {}) {
117
- let handle;
118
- try {
119
- handle = await open(file, "r");
120
- const { size } = await stat(file);
121
- const start = Math.max(0, size - bytes);
122
- const buffer = Buffer.alloc(size - start);
123
- await handle.read(buffer, 0, buffer.length, start);
124
- const lines = buffer.toString("utf8").split("\n");
125
- if (start > 0) lines.shift();
126
- const records = [];
127
- for (const line of lines) {
128
- if (!line.trim()) continue;
129
- try {
130
- const record = JSON.parse(line);
131
- if (record && typeof record === "object" && typeof record.time === "string" && typeof record.level === "string" && typeof record.msg === "string") {
132
- records.push(redact(record, { env }));
133
- continue;
134
- }
135
- } catch {
136
- }
137
- records.push({ legacy: scrubText(line, { env, max: 500 }) });
138
- }
139
- return records;
140
- } catch (error) {
141
- if (error?.code === "ENOENT") return [];
142
- throw error;
143
- } finally {
144
- await handle?.close();
145
- }
146
- }
147
- export {
148
- LEVELS,
149
- OMITTED,
150
- REDACTED,
151
- createLogger,
152
- describeError,
153
- readLogTail,
154
- redact,
155
- scrubText
156
- };
@@ -1,9 +0,0 @@
1
- export type Migration = { from: number; to: number; name: string; run(home: string): Promise<void> };
2
- export const MIGRATIONS: Migration[];
3
- export type MigrationResult = { from: number; to: number; steps: Array<{ name: string; from: number; to: number; backup: string; resumed: boolean }> };
4
- export function migrateState(home: string, options?: {
5
- by?: string;
6
- migrations?: Migration[];
7
- target?: number;
8
- log?: { info(message: string, fields?: object): void; warn(message: string, fields?: object): void };
9
- }): Promise<MigrationResult>;
@@ -1,95 +0,0 @@
1
- // packages/core/src/migrations.js
2
- import { readdir, readFile, rm, stat } from "node:fs/promises";
3
- import path from "node:path";
4
- import { AlpError } from "./errors.js";
5
- import { writeAtomic } from "./atomic.js";
6
- import { STATE_SCHEMA, assertStateSupported, migrationFile, readStateSchema, stateDir, writeStateSchema } from "./state-schema.js";
7
- import { STATE_UNITS, automaticBackup, restoreBackup } from "./backup.js";
8
- async function timelineCursors(home) {
9
- const directory = path.join(stateDir(home), "timeline");
10
- const names = await readdir(directory).catch((error) => {
11
- if (error?.code === "ENOENT") return [];
12
- throw error;
13
- });
14
- for (const name of names.filter((name2) => name2.endsWith(".jsonl"))) {
15
- const file = path.join(directory, name);
16
- const lines = [];
17
- let cursor = 0;
18
- let changed = false;
19
- for (const line of (await readFile(file, "utf8")).split("\n")) {
20
- if (!line) continue;
21
- let envelope;
22
- try {
23
- envelope = JSON.parse(line);
24
- } catch {
25
- lines.push(line);
26
- continue;
27
- }
28
- cursor = typeof envelope?.cursor === "number" && envelope.cursor > cursor ? envelope.cursor : cursor + 1;
29
- if (envelope?.cursor === cursor) {
30
- lines.push(line);
31
- continue;
32
- }
33
- lines.push(JSON.stringify({ ...envelope, cursor }));
34
- changed = true;
35
- }
36
- if (changed) await writeAtomic(file, lines.map((line) => `${line}
37
- `).join(""));
38
- }
39
- }
40
- var MIGRATIONS = [
41
- { from: 1, to: 2, name: "timeline-cursors", run: timelineCursors }
42
- ];
43
- async function readJournal(home) {
44
- try {
45
- return JSON.parse(await readFile(migrationFile(home), "utf8"));
46
- } catch (error) {
47
- if (error?.code === "ENOENT") return void 0;
48
- return {};
49
- }
50
- }
51
- var exists = (file) => stat(file).then(() => true, () => false);
52
- async function migrateState(home, { by = "alp", migrations = MIGRATIONS, target = STATE_SCHEMA, log } = {}) {
53
- const info = assertStateSupported(await readStateSchema(home), home);
54
- let journal = await readJournal(home);
55
- if (journal && Number.isSafeInteger(journal.to) && journal.to <= info.schema) {
56
- await rm(migrationFile(home), { force: true });
57
- journal = void 0;
58
- }
59
- if (info.fresh && !journal) {
60
- await writeStateSchema(home, target, by);
61
- return { from: target, to: target, steps: [] };
62
- }
63
- let schema = info.schema;
64
- const steps = [];
65
- while (schema < target) {
66
- const step = migrations.find((candidate) => candidate.from === schema);
67
- if (!step) throw new AlpError("STATE_MIGRATION_MISSING", `ALP has no migration from state schema ${schema}; the state was not changed`);
68
- const resumed = journal?.to === step.to && typeof journal.backup === "string" && await exists(journal.backup);
69
- const backup = resumed ? journal.backup : await automaticBackup(home, `pre-migrate-v${step.from}-v${step.to}`, { units: STATE_UNITS, by });
70
- if (!resumed) await writeAtomic(migrationFile(home), JSON.stringify({ from: step.from, to: step.to, name: step.name, backup, by, startedAt: (/* @__PURE__ */ new Date()).toISOString() }) + "\n");
71
- log?.info(`${resumed ? "continuing" : "running"} state migration ${step.name} (schema ${step.from} \u2192 ${step.to}); backup ${backup}`, { code: "ALP_STATE_MIGRATING", from: step.from, to: step.to, migration: step.name, resumed });
72
- try {
73
- await step.run(home);
74
- } catch (error) {
75
- try {
76
- await restoreBackup({ home, file: backup, projects: false, safety: false, clearLive: false, by });
77
- } catch (rollback) {
78
- throw new AlpError("STATE_MIGRATION_ROLLBACK_FAILED", `state migration ${step.name} failed (${error?.message ?? error}) and putting the state back from ${backup} failed too (${rollback?.message ?? rollback}); the next start tries the migration again. See docs/alp/data-recovery.md`, { cause: error });
79
- }
80
- await rm(migrationFile(home), { force: true });
81
- throw new AlpError("STATE_MIGRATION_FAILED", `state migration ${step.name} (schema ${step.from} \u2192 ${step.to}) failed: ${error?.message ?? error}; the state was put back from ${backup} and is unchanged. See docs/alp/data-recovery.md`, { cause: error });
82
- }
83
- await writeStateSchema(home, step.to, by);
84
- await rm(migrationFile(home), { force: true });
85
- steps.push({ name: step.name, from: step.from, to: step.to, backup, resumed });
86
- schema = step.to;
87
- journal = void 0;
88
- }
89
- if (!info.marked && !steps.length) await writeStateSchema(home, schema, by);
90
- return { from: info.schema, to: schema, steps };
91
- }
92
- export {
93
- MIGRATIONS,
94
- migrateState
95
- };
@@ -1,2 +0,0 @@
1
- /** Preflight static repository paths. Does not isolate concurrent same-user processes. */
2
- export function assertNoSymlinks(directory: any, relative: any): Promise<void>;
package/dist/safe-path.js DELETED
@@ -1,24 +0,0 @@
1
- // packages/core/src/safe-path.js
2
- import { lstat } from "node:fs/promises";
3
- import path from "node:path";
4
- import { AlpError } from "./errors.js";
5
- async function assertNoSymlinks(directory, relative) {
6
- const root = path.resolve(directory);
7
- const target = path.resolve(root, relative);
8
- const tail = path.relative(root, target);
9
- if (tail === ".." || tail.startsWith(`..${path.sep}`) || path.isAbsolute(tail)) {
10
- throw new AlpError("UNSAFE_PATH", "Path escapes its scope");
11
- }
12
- let current = root;
13
- for (const part of ["", ...tail.split(path.sep).filter(Boolean)]) {
14
- if (part) current = path.join(current, part);
15
- const info = await lstat(current).catch((error) => {
16
- if (error.code !== "ENOENT") throw error;
17
- });
18
- if (!info) return;
19
- if (info.isSymbolicLink()) throw new AlpError("UNSAFE_PATH", `Refusing symlink in scoped path: ${current}`);
20
- }
21
- }
22
- export {
23
- assertNoSymlinks
24
- };
package/dist/shell.d.ts DELETED
@@ -1,16 +0,0 @@
1
- /**
2
- * Starts `command` through the platform's shell: /bin/sh -c in its own process group, or on
3
- * Windows cmd.exe /d /s /c with the line quoted whole (cmd strips only the outermost quotes,
4
- * and Node would otherwise escape the quotes inside it).
5
- * @param {string} command
6
- * @param {import('node:child_process').SpawnOptions} options
7
- */
8
- export function spawnShell(command: string, options: import("node:child_process").SpawnOptions): import("child_process").ChildProcess;
9
- /**
10
- * Signals a command `spawnShell` started and everything it started. Windows has no signals and
11
- * cmd.exe's children outlive it, so there the whole tree is ended at once by the first signal
12
- * and no cleanup runs.
13
- * @param {import('node:child_process').ChildProcess} child
14
- * @param {NodeJS.Signals} name
15
- */
16
- export function signalShell(child: import("node:child_process").ChildProcess, name: NodeJS.Signals): void;
package/dist/shell.js DELETED
@@ -1,22 +0,0 @@
1
- // packages/core/src/shell.js
2
- import { execFile, spawn } from "node:child_process";
3
- function spawnShell(command, options) {
4
- if (process.platform !== "win32") return spawn("/bin/sh", ["-c", command], { ...options, detached: true });
5
- return spawn(process.env.ComSpec ?? "cmd.exe", ["/d", "/s", "/c", `"${command}"`], { ...options, windowsHide: true, windowsVerbatimArguments: true });
6
- }
7
- function signalShell(child, name) {
8
- if (process.platform === "win32") {
9
- if (name === "SIGTERM") execFile("taskkill.exe", ["/PID", String(child.pid), "/T", "/F"], { windowsHide: true }, () => {
10
- });
11
- return;
12
- }
13
- try {
14
- process.kill(-child.pid, name);
15
- } catch {
16
- child.kill(name);
17
- }
18
- }
19
- export {
20
- signalShell,
21
- spawnShell
22
- };
@@ -1,9 +0,0 @@
1
- export const STATE_SCHEMA: number;
2
- export const OLDEST_STATE_SCHEMA: number;
3
- export function stateDir(home: string): string;
4
- export function schemaFile(home: string): string;
5
- export function migrationFile(home: string): string;
6
- export type StateSchemaInfo = { schema: number; fresh: boolean; marked: boolean; by?: string };
7
- export function readStateSchema(home: string): Promise<StateSchemaInfo>;
8
- export function assertStateSupported(info: StateSchemaInfo, home: string): StateSchemaInfo;
9
- export function writeStateSchema(home: string, schema: number, by?: string): Promise<void>;
@@ -1,54 +0,0 @@
1
- // packages/core/src/state-schema.js
2
- import { readdir, readFile, mkdir } from "node:fs/promises";
3
- import path from "node:path";
4
- import { AlpError } from "./errors.js";
5
- import { writeAtomic } from "./atomic.js";
6
- var STATE_SCHEMA = 2;
7
- var OLDEST_STATE_SCHEMA = 1;
8
- var stateDir = (home) => path.join(home, "state");
9
- var schemaFile = (home) => path.join(stateDir(home), "schema.json");
10
- var migrationFile = (home) => path.join(stateDir(home), "migration.json");
11
- var DATA = ["sessions", "timeline", "receipts"];
12
- async function readStateSchema(home) {
13
- let text;
14
- try {
15
- text = await readFile(schemaFile(home), "utf8");
16
- } catch (error) {
17
- if (error?.code !== "ENOENT") throw error;
18
- for (const name of DATA) {
19
- const entries = await readdir(path.join(stateDir(home), name)).catch((error2) => {
20
- if (error2?.code === "ENOENT") return [];
21
- throw error2;
22
- });
23
- if (entries.some((entry) => !entry.endsWith(".tmp"))) return { schema: 1, fresh: false, marked: false };
24
- }
25
- return { schema: STATE_SCHEMA, fresh: true, marked: false };
26
- }
27
- let marker;
28
- try {
29
- marker = JSON.parse(text);
30
- } catch {
31
- }
32
- if (!marker || !Number.isSafeInteger(marker.schema) || marker.schema < 1) {
33
- throw new AlpError("STATE_SCHEMA_UNREADABLE", `${schemaFile(home)} does not name a state schema; ALP does not open state whose format it cannot tell. Restore a backup, or put back the file`);
34
- }
35
- return { schema: marker.schema, fresh: false, marked: true, ...typeof marker.by === "string" ? { by: marker.by } : {} };
36
- }
37
- function assertStateSupported(info, home) {
38
- if (info.schema <= STATE_SCHEMA) return info;
39
- throw new AlpError("STATE_SCHEMA_TOO_NEW", `${stateDir(home)} holds state schema ${info.schema}${info.by ? `, written by ${info.by}` : ""}; this ALP knows schema ${STATE_SCHEMA} at most and leaves it untouched. Run that ALP version or a newer one, or replace it with a backup made for this version: alp restore FILE --replace-newer (docs/alp/data-recovery.md)`);
40
- }
41
- async function writeStateSchema(home, schema, by) {
42
- await mkdir(stateDir(home), { recursive: true, mode: 448 });
43
- await writeAtomic(schemaFile(home), JSON.stringify({ schema, by, at: (/* @__PURE__ */ new Date()).toISOString() }) + "\n");
44
- }
45
- export {
46
- OLDEST_STATE_SCHEMA,
47
- STATE_SCHEMA,
48
- assertStateSupported,
49
- migrationFile,
50
- readStateSchema,
51
- schemaFile,
52
- stateDir,
53
- writeStateSchema
54
- };
package/src/atomic.d.ts DELETED
@@ -1,2 +0,0 @@
1
- export function moveFile(from: string, to: string): Promise<void>;
2
- export function writeAtomic(file: string, body: string | Uint8Array, mode?: number): Promise<void>;
package/src/atomic.js DELETED
@@ -1,28 +0,0 @@
1
- import { randomBytes } from 'node:crypto';
2
- import { rename, rm, writeFile } from 'node:fs/promises';
3
-
4
- /**
5
- * Moves `from` to `to`. On Windows the move fails with EPERM, EACCES or EBUSY while another
6
- * process has the target open, such as a client reading it; it is tried again then.
7
- */
8
- export async function moveFile(from, to) {
9
- for (let attempt = 0; ; attempt++) {
10
- try { return await rename(from, to); }
11
- catch (error) {
12
- if (process.platform !== 'win32' || attempt >= 20 || !['EPERM', 'EACCES', 'EBUSY'].includes(error?.code)) throw error;
13
- await new Promise(resolve => setTimeout(resolve, 10 + attempt * 10));
14
- }
15
- }
16
- }
17
-
18
- /** Writes `file` through a temporary file beside it, so a reader sees the old or the new content. */
19
- export async function writeAtomic(file, body, mode = 0o600) {
20
- const temporary = `${file}.${process.pid}.${randomBytes(4).toString('hex')}.tmp`;
21
- try {
22
- await writeFile(temporary, body, { mode });
23
- await moveFile(temporary, file);
24
- } catch (error) {
25
- await rm(temporary, { force: true }).catch(() => {});
26
- throw error;
27
- }
28
- }
package/src/backup.d.ts DELETED
@@ -1,27 +0,0 @@
1
- export const BACKUP_FORMAT: number;
2
- export const BACKUP_EXTENSION: string;
3
- export const STATE_UNITS: string[];
4
- export const CLEARED_UNITS: string[];
5
- export const HOME_UNITS: string[];
6
- export const PROJECT_UNITS: string[];
7
- export function backupsDir(home: string): string;
8
- export function knownProjects(home: string): Promise<string[]>;
9
- export type BackupSummary = { file: string; files: number; bytes: number; projects: string[]; stateSchema: number | null; sessions: number; tasks: number };
10
- export function createBackup(options: { home: string; out: string; units?: string[]; projects?: string[]; by?: string; kind?: string }): Promise<BackupSummary>;
11
- export function automaticBackup(home: string, kind: string, options?: { units?: string[]; projects?: string[]; by?: string }): Promise<string>;
12
- export type BackupContents = { file: string; kind: string; by?: string; createdAt: string; home: string; stateSchema: number | null; units: string[]; projects: string[]; sessions: number; tasks: number; files: number; bytes: number; external: number };
13
- export function verifyBackup(file: string): Promise<BackupContents>;
14
- export function recoverInterruptedRestore(home: string): Promise<{ outcome: 'rolled back' | 'completed'; file?: string; safety?: string } | undefined>;
15
- export type RestoreResult = {
16
- file: string;
17
- safety?: string;
18
- stateSchema: number | null;
19
- interrupted?: { outcome: 'rolled back' | 'completed'; file?: string; safety?: string };
20
- units: Array<{ area: string; unit: string; present: boolean }>;
21
- projects: string[];
22
- skipped: Array<{ root: string; reason: string }>;
23
- warnings: string[];
24
- };
25
- export function restoreBackup(options: { home: string; file: string; projects?: boolean; safety?: boolean; clearLive?: boolean; replaceNewer?: boolean; by?: string; beforeSwap?: (index: number, total: number) => Promise<void> | void }): Promise<RestoreResult>;
26
- export function readRestoredMarker(home: string): Promise<{ at?: string; from?: string; by?: string } | undefined>;
27
- export function clearRestoredMarker(home: string): Promise<void>;