@letta-ai/dreams 0.0.1
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.
- package/README.md +146 -0
- package/bin/dreams.cjs +19 -0
- package/dist/auth/browser.js +43 -0
- package/dist/auth/commands.js +141 -0
- package/dist/auth/credentials.js +222 -0
- package/dist/auth/index.js +65 -0
- package/dist/auth/lock.js +92 -0
- package/dist/auth/oauth-refresh.js +68 -0
- package/dist/auth/oauth.js +193 -0
- package/dist/cli.js +149 -0
- package/dist/cloud/client.js +223 -0
- package/dist/cloud/upload.js +384 -0
- package/dist/local/blobs.js +190 -0
- package/dist/local/claude.js +315 -0
- package/dist/local/commands.js +160 -0
- package/dist/local/config.js +126 -0
- package/dist/local/db.js +265 -0
- package/dist/local/dreamignore.js +124 -0
- package/dist/local/ids.js +78 -0
- package/dist/local/leases.js +117 -0
- package/dist/local/scan.js +475 -0
- package/dist/managed-install.js +200 -0
- package/dist/onboard.js +131 -0
- package/dist/skill.js +138 -0
- package/dist/store.js +133 -0
- package/dist/version.js +48 -0
- package/package.json +32 -0
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Local configuration stored in the `kv` table.
|
|
4
|
+
*
|
|
5
|
+
* Approved roots are the privacy gate for [06]: they are DENY-BY-DEFAULT. No
|
|
6
|
+
* transcript bytes may enter the upload queue until a work root is explicitly
|
|
7
|
+
* approved (by an internal tester now, or the source-approval UX later). This
|
|
8
|
+
* holds even though [06] performs no network calls.
|
|
9
|
+
*/
|
|
10
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
11
|
+
if (k2 === undefined) k2 = k;
|
|
12
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
13
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
14
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
15
|
+
}
|
|
16
|
+
Object.defineProperty(o, k2, desc);
|
|
17
|
+
}) : (function(o, m, k, k2) {
|
|
18
|
+
if (k2 === undefined) k2 = k;
|
|
19
|
+
o[k2] = m[k];
|
|
20
|
+
}));
|
|
21
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
22
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
23
|
+
}) : function(o, v) {
|
|
24
|
+
o["default"] = v;
|
|
25
|
+
});
|
|
26
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
27
|
+
var ownKeys = function(o) {
|
|
28
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
29
|
+
var ar = [];
|
|
30
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
31
|
+
return ar;
|
|
32
|
+
};
|
|
33
|
+
return ownKeys(o);
|
|
34
|
+
};
|
|
35
|
+
return function (mod) {
|
|
36
|
+
if (mod && mod.__esModule) return mod;
|
|
37
|
+
var result = {};
|
|
38
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
39
|
+
__setModuleDefault(result, mod);
|
|
40
|
+
return result;
|
|
41
|
+
};
|
|
42
|
+
})();
|
|
43
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
44
|
+
exports.canonicalize = canonicalize;
|
|
45
|
+
exports.isContainedPath = isContainedPath;
|
|
46
|
+
exports.getApprovedRoots = getApprovedRoots;
|
|
47
|
+
exports.approveRoot = approveRoot;
|
|
48
|
+
exports.removeApprovedRoot = removeApprovedRoot;
|
|
49
|
+
exports.isUnderApprovedRoot = isUnderApprovedRoot;
|
|
50
|
+
const fs = __importStar(require("node:fs"));
|
|
51
|
+
const path = __importStar(require("node:path"));
|
|
52
|
+
const db_1 = require("./db");
|
|
53
|
+
const APPROVED_ROOTS_KEY = "approved_roots";
|
|
54
|
+
/** Resolve symlinks/aliases to a canonical path; fall back to a lexical resolve. */
|
|
55
|
+
function canonicalize(p) {
|
|
56
|
+
try {
|
|
57
|
+
return fs.realpathSync.native(p);
|
|
58
|
+
}
|
|
59
|
+
catch {
|
|
60
|
+
return path.resolve(p);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Canonical, non-lexical containment. `path.relative` (never a string prefix,
|
|
65
|
+
* which would treat /work/ab as under /work/a) decides nesting; on
|
|
66
|
+
* case-insensitive platforms the comparison is case-folded. Callers pass
|
|
67
|
+
* already-canonicalized paths.
|
|
68
|
+
*/
|
|
69
|
+
function isContainedPath(root, target, platform = process.platform) {
|
|
70
|
+
const p = platform === "win32" ? path.win32 : path.posix;
|
|
71
|
+
let a = root;
|
|
72
|
+
let b = target;
|
|
73
|
+
if (platform === "win32") {
|
|
74
|
+
a = a.toLowerCase();
|
|
75
|
+
b = b.toLowerCase();
|
|
76
|
+
}
|
|
77
|
+
if (a === b)
|
|
78
|
+
return true;
|
|
79
|
+
const rel = p.relative(a, b);
|
|
80
|
+
return rel !== "" && !rel.startsWith("..") && !p.isAbsolute(rel);
|
|
81
|
+
}
|
|
82
|
+
function getKv(db, key) {
|
|
83
|
+
const row = db.prepare("SELECT value FROM kv WHERE key = ?").get(key);
|
|
84
|
+
return row ? row.value : null;
|
|
85
|
+
}
|
|
86
|
+
function setKv(db, key, value) {
|
|
87
|
+
db.prepare("INSERT INTO kv (key, value) VALUES (?, ?) ON CONFLICT(key) DO UPDATE SET value = excluded.value").run(key, value);
|
|
88
|
+
}
|
|
89
|
+
function getApprovedRoots(db) {
|
|
90
|
+
const raw = getKv(db, APPROVED_ROOTS_KEY);
|
|
91
|
+
if (!raw)
|
|
92
|
+
return [];
|
|
93
|
+
try {
|
|
94
|
+
const parsed = JSON.parse(raw);
|
|
95
|
+
return Array.isArray(parsed) ? parsed : [];
|
|
96
|
+
}
|
|
97
|
+
catch {
|
|
98
|
+
return [];
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
function approveRoot(db, root) {
|
|
102
|
+
const resolved = canonicalize(root);
|
|
103
|
+
const roots = getApprovedRoots(db);
|
|
104
|
+
if (!roots.includes(resolved)) {
|
|
105
|
+
roots.push(resolved);
|
|
106
|
+
roots.sort();
|
|
107
|
+
setKv(db, APPROVED_ROOTS_KEY, JSON.stringify(roots));
|
|
108
|
+
setKv(db, `${APPROVED_ROOTS_KEY}_updated_at`, (0, db_1.nowIso)());
|
|
109
|
+
}
|
|
110
|
+
return roots;
|
|
111
|
+
}
|
|
112
|
+
function removeApprovedRoot(db, root) {
|
|
113
|
+
const resolved = canonicalize(root);
|
|
114
|
+
const roots = getApprovedRoots(db).filter((r) => r !== resolved);
|
|
115
|
+
setKv(db, APPROVED_ROOTS_KEY, JSON.stringify(roots));
|
|
116
|
+
return roots;
|
|
117
|
+
}
|
|
118
|
+
/** Canonical approved root that contains `target`, or null. Both sides are canonicalized. */
|
|
119
|
+
function isUnderApprovedRoot(roots, target) {
|
|
120
|
+
const canonicalTarget = canonicalize(target);
|
|
121
|
+
for (const root of roots) {
|
|
122
|
+
if (isContainedPath(canonicalize(root), canonicalTarget))
|
|
123
|
+
return root;
|
|
124
|
+
}
|
|
125
|
+
return null;
|
|
126
|
+
}
|
package/dist/local/db.js
ADDED
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Crash-safe local SQLite store for the Dreams CLI source subsystem.
|
|
4
|
+
*
|
|
5
|
+
* Uses the built-in `node:sqlite` (Node >= 22.13, no experimental flag). WAL +
|
|
6
|
+
* a busy timeout let concurrent CLI invocations touch the DB without clobbering
|
|
7
|
+
* each other; foreign keys keep source_files / cursors / queue rows consistent.
|
|
8
|
+
* Schema changes are applied as ordered, transactional migrations recorded in
|
|
9
|
+
* `kv.schema_version`.
|
|
10
|
+
*/
|
|
11
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
12
|
+
if (k2 === undefined) k2 = k;
|
|
13
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
14
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
15
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
16
|
+
}
|
|
17
|
+
Object.defineProperty(o, k2, desc);
|
|
18
|
+
}) : (function(o, m, k, k2) {
|
|
19
|
+
if (k2 === undefined) k2 = k;
|
|
20
|
+
o[k2] = m[k];
|
|
21
|
+
}));
|
|
22
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
23
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
24
|
+
}) : function(o, v) {
|
|
25
|
+
o["default"] = v;
|
|
26
|
+
});
|
|
27
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
28
|
+
var ownKeys = function(o) {
|
|
29
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
30
|
+
var ar = [];
|
|
31
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
32
|
+
return ar;
|
|
33
|
+
};
|
|
34
|
+
return ownKeys(o);
|
|
35
|
+
};
|
|
36
|
+
return function (mod) {
|
|
37
|
+
if (mod && mod.__esModule) return mod;
|
|
38
|
+
var result = {};
|
|
39
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
40
|
+
__setModuleDefault(result, mod);
|
|
41
|
+
return result;
|
|
42
|
+
};
|
|
43
|
+
})();
|
|
44
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
45
|
+
exports.SCHEMA_VERSION = void 0;
|
|
46
|
+
exports.dbFilePath = dbFilePath;
|
|
47
|
+
exports.tx = tx;
|
|
48
|
+
exports.openDb = openDb;
|
|
49
|
+
exports.closeDb = closeDb;
|
|
50
|
+
exports.nowIso = nowIso;
|
|
51
|
+
const node_sqlite_1 = require("node:sqlite");
|
|
52
|
+
const path = __importStar(require("node:path"));
|
|
53
|
+
const store_1 = require("../store");
|
|
54
|
+
const version_1 = require("../version");
|
|
55
|
+
function dbFilePath() {
|
|
56
|
+
return path.join((0, store_1.dreamsDir)(), "dreams.db");
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Each migration is a list of statements applied inside one transaction. Never
|
|
60
|
+
* edit an existing migration once shipped; append a new one instead.
|
|
61
|
+
*/
|
|
62
|
+
const MIGRATIONS = [
|
|
63
|
+
[
|
|
64
|
+
// `kv` is created by the bootstrap below (it anchors the version counter),
|
|
65
|
+
// so migration 0 does not recreate it.
|
|
66
|
+
`CREATE TABLE installation (
|
|
67
|
+
installation_id TEXT PRIMARY KEY,
|
|
68
|
+
created_at TEXT NOT NULL,
|
|
69
|
+
cli_version TEXT NOT NULL
|
|
70
|
+
)`,
|
|
71
|
+
`CREATE TABLE workspace_bindings (
|
|
72
|
+
id INTEGER PRIMARY KEY CHECK (id = 1),
|
|
73
|
+
api_base_url TEXT,
|
|
74
|
+
workspace_id TEXT,
|
|
75
|
+
organization_id TEXT,
|
|
76
|
+
user_id TEXT,
|
|
77
|
+
created_at TEXT NOT NULL,
|
|
78
|
+
updated_at TEXT NOT NULL
|
|
79
|
+
)`,
|
|
80
|
+
`CREATE TABLE sources (
|
|
81
|
+
id TEXT PRIMARY KEY,
|
|
82
|
+
source_type TEXT NOT NULL,
|
|
83
|
+
source_key TEXT NOT NULL,
|
|
84
|
+
display_name TEXT NOT NULL,
|
|
85
|
+
root_path TEXT,
|
|
86
|
+
adapter_version TEXT NOT NULL,
|
|
87
|
+
privacy_config_hash TEXT,
|
|
88
|
+
backfill_policy TEXT,
|
|
89
|
+
status TEXT NOT NULL,
|
|
90
|
+
server_source_id TEXT,
|
|
91
|
+
created_at TEXT NOT NULL,
|
|
92
|
+
updated_at TEXT NOT NULL,
|
|
93
|
+
UNIQUE (source_type, source_key)
|
|
94
|
+
)`,
|
|
95
|
+
`CREATE TABLE source_files (
|
|
96
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
97
|
+
source_id TEXT NOT NULL REFERENCES sources(id) ON DELETE CASCADE,
|
|
98
|
+
session_id TEXT NOT NULL,
|
|
99
|
+
generation INTEGER NOT NULL,
|
|
100
|
+
path TEXT NOT NULL,
|
|
101
|
+
cwd TEXT,
|
|
102
|
+
inode TEXT,
|
|
103
|
+
birthtime_ms INTEGER,
|
|
104
|
+
size INTEGER NOT NULL DEFAULT 0,
|
|
105
|
+
mtime_ms INTEGER,
|
|
106
|
+
first_chunk_fp TEXT,
|
|
107
|
+
prefix_fp TEXT,
|
|
108
|
+
is_current INTEGER NOT NULL DEFAULT 1,
|
|
109
|
+
created_at TEXT NOT NULL,
|
|
110
|
+
updated_at TEXT NOT NULL,
|
|
111
|
+
UNIQUE (source_id, session_id, generation)
|
|
112
|
+
)`,
|
|
113
|
+
`CREATE INDEX idx_source_files_session ON source_files (source_id, session_id, is_current)`,
|
|
114
|
+
`CREATE TABLE source_cursors (
|
|
115
|
+
source_file_id INTEGER PRIMARY KEY REFERENCES source_files(id) ON DELETE CASCADE,
|
|
116
|
+
queued_through_offset INTEGER NOT NULL DEFAULT 0,
|
|
117
|
+
acked_offset INTEGER NOT NULL DEFAULT 0,
|
|
118
|
+
updated_at TEXT NOT NULL
|
|
119
|
+
)`,
|
|
120
|
+
`CREATE TABLE upload_queue (
|
|
121
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
122
|
+
upload_id TEXT NOT NULL UNIQUE,
|
|
123
|
+
source_id TEXT NOT NULL REFERENCES sources(id) ON DELETE CASCADE,
|
|
124
|
+
source_file_id INTEGER NOT NULL REFERENCES source_files(id) ON DELETE CASCADE,
|
|
125
|
+
session_id TEXT NOT NULL,
|
|
126
|
+
generation INTEGER NOT NULL,
|
|
127
|
+
start_offset INTEGER NOT NULL,
|
|
128
|
+
end_offset INTEGER NOT NULL,
|
|
129
|
+
byte_length INTEGER NOT NULL,
|
|
130
|
+
content_sha256 TEXT NOT NULL,
|
|
131
|
+
priority TEXT NOT NULL,
|
|
132
|
+
state TEXT NOT NULL, -- pending | accepted ([07]) | canceled (privacy)
|
|
133
|
+
server_upload_id TEXT,
|
|
134
|
+
attempts INTEGER NOT NULL DEFAULT 0,
|
|
135
|
+
created_at TEXT NOT NULL,
|
|
136
|
+
updated_at TEXT NOT NULL,
|
|
137
|
+
UNIQUE (source_file_id, start_offset, end_offset)
|
|
138
|
+
)`,
|
|
139
|
+
`CREATE INDEX idx_upload_queue_state ON upload_queue (state, priority)`,
|
|
140
|
+
`CREATE TABLE excluded_ranges (
|
|
141
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
142
|
+
source_file_id INTEGER NOT NULL REFERENCES source_files(id) ON DELETE CASCADE,
|
|
143
|
+
start_offset INTEGER NOT NULL,
|
|
144
|
+
end_offset INTEGER NOT NULL,
|
|
145
|
+
reason TEXT NOT NULL,
|
|
146
|
+
created_at TEXT NOT NULL
|
|
147
|
+
)`,
|
|
148
|
+
`CREATE INDEX idx_excluded_ranges_file ON excluded_ranges (source_file_id)`,
|
|
149
|
+
`CREATE TABLE hook_installations (
|
|
150
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
151
|
+
target TEXT NOT NULL UNIQUE,
|
|
152
|
+
settings_path TEXT NOT NULL,
|
|
153
|
+
installed_version TEXT,
|
|
154
|
+
config_fingerprint TEXT,
|
|
155
|
+
created_at TEXT NOT NULL,
|
|
156
|
+
updated_at TEXT NOT NULL
|
|
157
|
+
)`,
|
|
158
|
+
`CREATE TABLE operations (
|
|
159
|
+
operation_id TEXT PRIMARY KEY,
|
|
160
|
+
kind TEXT NOT NULL,
|
|
161
|
+
result_json TEXT,
|
|
162
|
+
created_at TEXT NOT NULL
|
|
163
|
+
)`,
|
|
164
|
+
`CREATE TABLE leases (
|
|
165
|
+
name TEXT PRIMARY KEY,
|
|
166
|
+
owner TEXT NOT NULL,
|
|
167
|
+
acquired_at INTEGER NOT NULL,
|
|
168
|
+
expires_at INTEGER NOT NULL
|
|
169
|
+
)`,
|
|
170
|
+
],
|
|
171
|
+
[
|
|
172
|
+
// Policy gate for the future uploader ([07]): the policy fingerprint in force
|
|
173
|
+
// when a row was enqueued, plus per-file error state so one poisoned session
|
|
174
|
+
// (e.g. an oversized record) never aborts reconciliation of the others.
|
|
175
|
+
`ALTER TABLE upload_queue ADD COLUMN policy_fingerprint TEXT`,
|
|
176
|
+
`ALTER TABLE source_files ADD COLUMN last_error_code TEXT`,
|
|
177
|
+
`ALTER TABLE source_files ADD COLUMN last_error_message TEXT`,
|
|
178
|
+
`ALTER TABLE source_files ADD COLUMN last_error_at TEXT`,
|
|
179
|
+
],
|
|
180
|
+
[
|
|
181
|
+
// Cloud upload lifecycle ([07]): acceptance time, retry backoff schedule, and
|
|
182
|
+
// per-row error diagnostics. `state` also gains 'accepted' and 'quarantined'
|
|
183
|
+
// (the column is free-text, so no constraint change is needed).
|
|
184
|
+
`ALTER TABLE upload_queue ADD COLUMN accepted_at TEXT`,
|
|
185
|
+
`ALTER TABLE upload_queue ADD COLUMN next_attempt_at TEXT`,
|
|
186
|
+
`ALTER TABLE upload_queue ADD COLUMN last_error_code TEXT`,
|
|
187
|
+
`ALTER TABLE upload_queue ADD COLUMN last_error_message TEXT`,
|
|
188
|
+
// Due-row selection: pending rows for a source, ordered by priority then time.
|
|
189
|
+
`CREATE INDEX idx_upload_queue_due ON upload_queue (source_id, state, priority, next_attempt_at)`,
|
|
190
|
+
],
|
|
191
|
+
];
|
|
192
|
+
exports.SCHEMA_VERSION = MIGRATIONS.length;
|
|
193
|
+
function tx(db, fn) {
|
|
194
|
+
db.exec("BEGIN IMMEDIATE");
|
|
195
|
+
try {
|
|
196
|
+
const result = fn();
|
|
197
|
+
db.exec("COMMIT");
|
|
198
|
+
return result;
|
|
199
|
+
}
|
|
200
|
+
catch (error) {
|
|
201
|
+
try {
|
|
202
|
+
db.exec("ROLLBACK");
|
|
203
|
+
}
|
|
204
|
+
catch {
|
|
205
|
+
// A rollback failure must not mask the original error.
|
|
206
|
+
}
|
|
207
|
+
throw error;
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
function currentSchemaVersion(db) {
|
|
211
|
+
const row = db.prepare("SELECT value FROM kv WHERE key = 'schema_version'").get();
|
|
212
|
+
return row ? parseInt(row.value, 10) || 0 : 0;
|
|
213
|
+
}
|
|
214
|
+
function runMigrations(db) {
|
|
215
|
+
// The bootstrap table is idempotent. The version decision itself belongs
|
|
216
|
+
// inside the write lock so concurrent first opens cannot both choose v0.
|
|
217
|
+
db.exec("CREATE TABLE IF NOT EXISTS kv (key TEXT PRIMARY KEY, value TEXT NOT NULL)");
|
|
218
|
+
tx(db, () => {
|
|
219
|
+
const applied = currentSchemaVersion(db);
|
|
220
|
+
if (applied > MIGRATIONS.length) {
|
|
221
|
+
throw new Error(`Dreams database schema ${applied} is newer than supported schema ${MIGRATIONS.length}`);
|
|
222
|
+
}
|
|
223
|
+
for (let version = applied; version < MIGRATIONS.length; version++) {
|
|
224
|
+
for (const statement of MIGRATIONS[version])
|
|
225
|
+
db.exec(statement);
|
|
226
|
+
db.prepare("INSERT INTO kv (key, value) VALUES ('schema_version', ?) " +
|
|
227
|
+
"ON CONFLICT(key) DO UPDATE SET value = excluded.value").run(String(version + 1));
|
|
228
|
+
}
|
|
229
|
+
});
|
|
230
|
+
}
|
|
231
|
+
/**
|
|
232
|
+
* Mirror the durable installation identity (canonically ~/.dreams/installation.json,
|
|
233
|
+
* so it survives DB loss) into the `installation` table for FK/provenance use.
|
|
234
|
+
*/
|
|
235
|
+
function ensureInstallation(db, installationId, createdAt) {
|
|
236
|
+
db.prepare("INSERT INTO installation (installation_id, created_at, cli_version) VALUES (?, ?, ?) " +
|
|
237
|
+
"ON CONFLICT(installation_id) DO UPDATE SET cli_version = excluded.cli_version").run(installationId, createdAt, (0, version_1.packageVersion)());
|
|
238
|
+
}
|
|
239
|
+
let cached = null;
|
|
240
|
+
function openDb(options) {
|
|
241
|
+
if (cached)
|
|
242
|
+
return cached;
|
|
243
|
+
// Harden the state directory on every open (not only when minting an
|
|
244
|
+
// installation), so an upgrade from an older 0755 layout does not create
|
|
245
|
+
// dreams.db/WAL/SHM with world-readable project paths and session metadata.
|
|
246
|
+
(0, store_1.secureDreamsDir)();
|
|
247
|
+
const db = new node_sqlite_1.DatabaseSync(dbFilePath());
|
|
248
|
+
db.exec("PRAGMA busy_timeout = 5000");
|
|
249
|
+
db.exec("PRAGMA journal_mode = WAL");
|
|
250
|
+
db.exec("PRAGMA foreign_keys = ON");
|
|
251
|
+
runMigrations(db);
|
|
252
|
+
ensureInstallation(db, options.installationId, options.installationCreatedAt);
|
|
253
|
+
cached = db;
|
|
254
|
+
return db;
|
|
255
|
+
}
|
|
256
|
+
/** Test/most-callers helper: closes and forgets the cached handle. */
|
|
257
|
+
function closeDb() {
|
|
258
|
+
if (cached) {
|
|
259
|
+
cached.close();
|
|
260
|
+
cached = null;
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
function nowIso() {
|
|
264
|
+
return new Date().toISOString();
|
|
265
|
+
}
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* `.dreamignore` — the primary local exclusion mechanism, evaluated before a
|
|
4
|
+
* session's bytes are ever enqueued. A pragmatic gitignore-style subset:
|
|
5
|
+
* blank lines and `#` comments are skipped, `!` negates, a trailing `/` marks a
|
|
6
|
+
* directory-only rule, a leading `/` anchors to the root, and `*` / `?` are
|
|
7
|
+
* glob wildcards (`*` does not cross `/`, `**` does).
|
|
8
|
+
*
|
|
9
|
+
* Patterns are matched against a POSIX, root-relative path (e.g. the session's
|
|
10
|
+
* working directory relative to an approved root).
|
|
11
|
+
*/
|
|
12
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
13
|
+
if (k2 === undefined) k2 = k;
|
|
14
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
15
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
16
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
17
|
+
}
|
|
18
|
+
Object.defineProperty(o, k2, desc);
|
|
19
|
+
}) : (function(o, m, k, k2) {
|
|
20
|
+
if (k2 === undefined) k2 = k;
|
|
21
|
+
o[k2] = m[k];
|
|
22
|
+
}));
|
|
23
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
24
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
25
|
+
}) : function(o, v) {
|
|
26
|
+
o["default"] = v;
|
|
27
|
+
});
|
|
28
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
29
|
+
var ownKeys = function(o) {
|
|
30
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
31
|
+
var ar = [];
|
|
32
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
33
|
+
return ar;
|
|
34
|
+
};
|
|
35
|
+
return ownKeys(o);
|
|
36
|
+
};
|
|
37
|
+
return function (mod) {
|
|
38
|
+
if (mod && mod.__esModule) return mod;
|
|
39
|
+
var result = {};
|
|
40
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
41
|
+
__setModuleDefault(result, mod);
|
|
42
|
+
return result;
|
|
43
|
+
};
|
|
44
|
+
})();
|
|
45
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
46
|
+
exports.parseDreamignore = parseDreamignore;
|
|
47
|
+
exports.isIgnored = isIgnored;
|
|
48
|
+
exports.loadDreamignore = loadDreamignore;
|
|
49
|
+
exports.loadDreamignoreText = loadDreamignoreText;
|
|
50
|
+
const fs = __importStar(require("node:fs"));
|
|
51
|
+
const path = __importStar(require("node:path"));
|
|
52
|
+
function globToRegExp(pattern) {
|
|
53
|
+
const anchored = pattern.startsWith("/");
|
|
54
|
+
let body = anchored ? pattern.slice(1) : pattern;
|
|
55
|
+
let re = "";
|
|
56
|
+
for (let i = 0; i < body.length; i++) {
|
|
57
|
+
const c = body[i];
|
|
58
|
+
if (c === "*") {
|
|
59
|
+
if (body[i + 1] === "*") {
|
|
60
|
+
// `**` spans path separators.
|
|
61
|
+
re += ".*";
|
|
62
|
+
i++;
|
|
63
|
+
if (body[i + 1] === "/")
|
|
64
|
+
i++;
|
|
65
|
+
}
|
|
66
|
+
else {
|
|
67
|
+
re += "[^/]*";
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
else if (c === "?") {
|
|
71
|
+
re += "[^/]";
|
|
72
|
+
}
|
|
73
|
+
else if ("+.()|{}^$\\[]".includes(c)) {
|
|
74
|
+
re += "\\" + c;
|
|
75
|
+
}
|
|
76
|
+
else {
|
|
77
|
+
re += c;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
// Unanchored patterns match at any path segment boundary.
|
|
81
|
+
const prefix = anchored ? "^" : "^(?:.*/)?";
|
|
82
|
+
return new RegExp(`${prefix}${re}(?:/.*)?$`);
|
|
83
|
+
}
|
|
84
|
+
function parseDreamignore(text) {
|
|
85
|
+
const rules = [];
|
|
86
|
+
for (const raw of text.split(/\r?\n/)) {
|
|
87
|
+
const line = raw.trim();
|
|
88
|
+
if (line === "" || line.startsWith("#"))
|
|
89
|
+
continue;
|
|
90
|
+
let pattern = line;
|
|
91
|
+
const negated = pattern.startsWith("!");
|
|
92
|
+
if (negated)
|
|
93
|
+
pattern = pattern.slice(1);
|
|
94
|
+
const dirOnly = pattern.endsWith("/");
|
|
95
|
+
if (dirOnly)
|
|
96
|
+
pattern = pattern.slice(0, -1);
|
|
97
|
+
rules.push({ negated, dirOnly, regex: globToRegExp(pattern) });
|
|
98
|
+
}
|
|
99
|
+
return rules;
|
|
100
|
+
}
|
|
101
|
+
/** Last matching rule wins (gitignore semantics), so a later `!rule` can re-include. */
|
|
102
|
+
function isIgnored(rules, relPath, isDir = false) {
|
|
103
|
+
const normalized = relPath.split(path.sep).join("/").replace(/^\/+/, "");
|
|
104
|
+
let ignored = false;
|
|
105
|
+
for (const rule of rules) {
|
|
106
|
+
if (rule.dirOnly && !isDir)
|
|
107
|
+
continue;
|
|
108
|
+
if (rule.regex.test(normalized))
|
|
109
|
+
ignored = !rule.negated;
|
|
110
|
+
}
|
|
111
|
+
return ignored;
|
|
112
|
+
}
|
|
113
|
+
function loadDreamignore(root) {
|
|
114
|
+
return parseDreamignore(loadDreamignoreText(root));
|
|
115
|
+
}
|
|
116
|
+
/** Raw `.dreamignore` contents (empty string if absent) — used for the policy fingerprint. */
|
|
117
|
+
function loadDreamignoreText(root) {
|
|
118
|
+
try {
|
|
119
|
+
return fs.readFileSync(path.join(root, ".dreamignore"), "utf8");
|
|
120
|
+
}
|
|
121
|
+
catch {
|
|
122
|
+
return "";
|
|
123
|
+
}
|
|
124
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Deterministic identities for queued upload segments.
|
|
4
|
+
*
|
|
5
|
+
* The segment id doubles as the Cloud `client_upload_id` in [07], so it must
|
|
6
|
+
* satisfy the frozen upload contract (^[A-Za-z0-9_-]{1,128}$). It is derived
|
|
7
|
+
* ONLY from stable identity coordinates — installation, source, file/session
|
|
8
|
+
* identity, generation, and the exact byte range — and NEVER from the content
|
|
9
|
+
* hash. Keeping the hash out of the id is what makes the contract's integrity
|
|
10
|
+
* check work: the same identity+range with different bytes must collide on the
|
|
11
|
+
* id (→ 409 conflict) rather than masquerade as two independent uploads. The
|
|
12
|
+
* content hash is carried and verified as a separate field.
|
|
13
|
+
*
|
|
14
|
+
* Replacement/truncation mints a new generation, which is a fresh id namespace.
|
|
15
|
+
*/
|
|
16
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
17
|
+
if (k2 === undefined) k2 = k;
|
|
18
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
19
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
20
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
21
|
+
}
|
|
22
|
+
Object.defineProperty(o, k2, desc);
|
|
23
|
+
}) : (function(o, m, k, k2) {
|
|
24
|
+
if (k2 === undefined) k2 = k;
|
|
25
|
+
o[k2] = m[k];
|
|
26
|
+
}));
|
|
27
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
28
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
29
|
+
}) : function(o, v) {
|
|
30
|
+
o["default"] = v;
|
|
31
|
+
});
|
|
32
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
33
|
+
var ownKeys = function(o) {
|
|
34
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
35
|
+
var ar = [];
|
|
36
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
37
|
+
return ar;
|
|
38
|
+
};
|
|
39
|
+
return ownKeys(o);
|
|
40
|
+
};
|
|
41
|
+
return function (mod) {
|
|
42
|
+
if (mod && mod.__esModule) return mod;
|
|
43
|
+
var result = {};
|
|
44
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
45
|
+
__setModuleDefault(result, mod);
|
|
46
|
+
return result;
|
|
47
|
+
};
|
|
48
|
+
})();
|
|
49
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
50
|
+
exports.sha256Hex = sha256Hex;
|
|
51
|
+
exports.segmentId = segmentId;
|
|
52
|
+
const crypto = __importStar(require("node:crypto"));
|
|
53
|
+
function sha256Hex(bytes) {
|
|
54
|
+
return crypto.createHash("sha256").update(bytes).digest("hex");
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Length-delimited, versioned encoding so no field boundary is ambiguous even
|
|
58
|
+
* if a value contains delimiter-like characters. The `segment-v1` prefix lets
|
|
59
|
+
* the derivation evolve without silently colliding with old ids.
|
|
60
|
+
*/
|
|
61
|
+
function encodeSegmentTuple(identity) {
|
|
62
|
+
const fields = [
|
|
63
|
+
"segment-v1",
|
|
64
|
+
identity.installation_id,
|
|
65
|
+
identity.source_key,
|
|
66
|
+
identity.file_identity,
|
|
67
|
+
String(identity.generation),
|
|
68
|
+
String(identity.start_offset),
|
|
69
|
+
String(identity.end_offset),
|
|
70
|
+
];
|
|
71
|
+
const parts = fields.map((f) => `${Buffer.byteLength(f, "utf8")}:${f}`);
|
|
72
|
+
return Buffer.from(parts.join(""), "utf8");
|
|
73
|
+
}
|
|
74
|
+
/** `seg_v1_<base64url(sha256(tuple))>` — 50 chars, contract-safe. */
|
|
75
|
+
function segmentId(identity) {
|
|
76
|
+
const digest = crypto.createHash("sha256").update(encodeSegmentTuple(identity)).digest();
|
|
77
|
+
return `seg_v1_${digest.toString("base64url")}`;
|
|
78
|
+
}
|