@treeseed/sdk 0.1.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.
Files changed (63) hide show
  1. package/README.md +565 -0
  2. package/dist/cli-tools.js +44 -0
  3. package/dist/content-store.js +237 -0
  4. package/dist/d1-store.js +549 -0
  5. package/dist/frontmatter.js +33 -0
  6. package/dist/git-runtime.js +67 -0
  7. package/dist/index.js +12 -0
  8. package/dist/model-registry.js +164 -0
  9. package/dist/runtime.js +36 -0
  10. package/dist/scripts/.ts-run-1775616845195-odh4xzphk3l.js +22 -0
  11. package/dist/scripts/.ts-run-1775616848931-9386s6kwrl.js +126 -0
  12. package/dist/scripts/assert-release-tag-version.d.ts +1 -0
  13. package/dist/scripts/assert-release-tag-version.js +23 -0
  14. package/dist/scripts/build-dist.d.ts +1 -0
  15. package/dist/scripts/build-dist.js +114 -0
  16. package/dist/scripts/package-tools.d.ts +15 -0
  17. package/dist/scripts/package-tools.js +76 -0
  18. package/dist/scripts/publish-package.d.ts +1 -0
  19. package/dist/scripts/publish-package.js +20 -0
  20. package/dist/scripts/release-verify.d.ts +1 -0
  21. package/dist/scripts/release-verify.js +49 -0
  22. package/dist/scripts/run-ts.js +45 -0
  23. package/dist/scripts/test-smoke.d.ts +1 -0
  24. package/dist/scripts/test-smoke.js +77 -0
  25. package/dist/sdk-filters.js +77 -0
  26. package/dist/sdk-types.js +24 -0
  27. package/dist/sdk.js +232 -0
  28. package/dist/src/cli-tools.d.ts +3 -0
  29. package/dist/src/content-store.d.ts +24 -0
  30. package/dist/src/d1-store.d.ts +108 -0
  31. package/dist/src/frontmatter.d.ts +6 -0
  32. package/dist/src/git-runtime.d.ts +16 -0
  33. package/dist/src/index.d.ts +6 -0
  34. package/dist/src/model-registry.d.ts +4 -0
  35. package/dist/src/runtime.d.ts +1 -0
  36. package/dist/src/sdk-filters.d.ts +4 -0
  37. package/dist/src/sdk-types.d.ts +285 -0
  38. package/dist/src/sdk.d.ts +109 -0
  39. package/dist/src/stores/cursor-store.d.ts +10 -0
  40. package/dist/src/stores/envelopes.d.ts +116 -0
  41. package/dist/src/stores/helpers.d.ts +12 -0
  42. package/dist/src/stores/lease-store.d.ts +18 -0
  43. package/dist/src/stores/message-store.d.ts +12 -0
  44. package/dist/src/stores/run-store.d.ts +10 -0
  45. package/dist/src/stores/subscription-store.d.ts +9 -0
  46. package/dist/src/types/agents.d.ts +100 -0
  47. package/dist/src/types/cloudflare.d.ts +32 -0
  48. package/dist/src/wrangler-d1.d.ts +25 -0
  49. package/dist/stores/cursor-store.js +158 -0
  50. package/dist/stores/envelopes.js +219 -0
  51. package/dist/stores/helpers.js +42 -0
  52. package/dist/stores/lease-store.js +183 -0
  53. package/dist/stores/message-store.js +249 -0
  54. package/dist/stores/run-store.js +166 -0
  55. package/dist/stores/subscription-store.js +171 -0
  56. package/dist/test/test-fixture.d.ts +1 -0
  57. package/dist/test/utils/envelopes.test.d.ts +1 -0
  58. package/dist/test/utils/sdk.test.d.ts +1 -0
  59. package/dist/types/agents.js +40 -0
  60. package/dist/types/cloudflare.js +0 -0
  61. package/dist/vitest.config.d.ts +2 -0
  62. package/dist/wrangler-d1.js +84 -0
  63. package/package.json +130 -0
@@ -0,0 +1,237 @@
1
+ import { mkdir, readdir, readFile, stat, writeFile } from "node:fs/promises";
2
+ import path from "node:path";
3
+ import crypto from "node:crypto";
4
+ import { parseFrontmatterDocument, serializeFrontmatterDocument } from "./frontmatter.js";
5
+ import { resolveModelDefinition } from "./model-registry.js";
6
+ import { applyFilters, applySort } from "./sdk-filters.js";
7
+ import { GitRuntime } from "./git-runtime.js";
8
+ async function walkMarkdownFiles(root) {
9
+ try {
10
+ const entries = await readdir(root, { withFileTypes: true });
11
+ const files = await Promise.all(
12
+ entries.map(async (entry) => {
13
+ const fullPath = path.join(root, entry.name);
14
+ if (entry.isDirectory()) {
15
+ return walkMarkdownFiles(fullPath);
16
+ }
17
+ if (entry.isFile() && /\.(md|mdx)$/i.test(entry.name)) {
18
+ return [fullPath];
19
+ }
20
+ return [];
21
+ })
22
+ );
23
+ return files.flat();
24
+ } catch {
25
+ return [];
26
+ }
27
+ }
28
+ async function findWorktreeRoots(root) {
29
+ const entries = await readdir(root, { withFileTypes: true }).catch(() => []);
30
+ const names = new Set(entries.map((entry) => entry.name));
31
+ const nested = await Promise.all(
32
+ entries.filter((entry) => entry.isDirectory()).map((entry) => findWorktreeRoots(path.join(root, entry.name)))
33
+ );
34
+ return [
35
+ ...names.has(".git") ? [root] : [],
36
+ ...nested.flat()
37
+ ];
38
+ }
39
+ function inferSlug(filePath, root) {
40
+ const relativePath = path.relative(root, filePath).replace(/\\/g, "/");
41
+ return relativePath.replace(/\.(md|mdx)$/i, "");
42
+ }
43
+ async function readContentEntry(definition, filePath, contentDir) {
44
+ const source = await readFile(filePath, "utf8");
45
+ const parsed = parseFrontmatterDocument(source);
46
+ const fileStats = await stat(filePath);
47
+ const slug = inferSlug(filePath, contentDir);
48
+ return {
49
+ id: slug,
50
+ slug,
51
+ model: definition.name,
52
+ title: typeof parsed.frontmatter.title === "string" ? parsed.frontmatter.title : void 0,
53
+ path: filePath,
54
+ body: parsed.body,
55
+ frontmatter: parsed.frontmatter,
56
+ createdAt: typeof parsed.frontmatter.date === "string" ? String(parsed.frontmatter.date) : fileStats.birthtime.toISOString(),
57
+ updatedAt: typeof parsed.frontmatter.updated === "string" ? String(parsed.frontmatter.updated) : fileStats.mtime.toISOString()
58
+ };
59
+ }
60
+ function entryMatchesIdentity(entry, request) {
61
+ return [request.id, request.slug, request.key].filter(Boolean).includes(entry.id);
62
+ }
63
+ function ensureMutationAllowed(definition, operation) {
64
+ if (!definition.operations.includes(operation)) {
65
+ throw new Error(`Model "${definition.name}" does not allow ${operation}.`);
66
+ }
67
+ }
68
+ function sanitizeFrontmatterInput(data) {
69
+ const next = { ...data };
70
+ delete next.body;
71
+ delete next.branchPrefix;
72
+ return next;
73
+ }
74
+ class ContentStore {
75
+ constructor(repoRoot, database) {
76
+ this.repoRoot = repoRoot;
77
+ this.database = database;
78
+ this.gitRuntime = new GitRuntime(
79
+ repoRoot,
80
+ process.env.TREESEED_AGENT_DISABLE_GIT === "true"
81
+ );
82
+ }
83
+ repoRoot;
84
+ database;
85
+ gitRuntime;
86
+ async list(model) {
87
+ const definition = resolveModelDefinition(model);
88
+ if (definition.storage !== "content" || !definition.contentDir) {
89
+ throw new Error(`Model "${model}" is not content-backed.`);
90
+ }
91
+ const roots = [
92
+ {
93
+ contentDir: definition.contentDir
94
+ }
95
+ ];
96
+ const worktreeRoot = path.join(this.repoRoot, ".agent-worktrees");
97
+ const worktrees = await findWorktreeRoots(worktreeRoot);
98
+ const relativeContentDir = path.relative(this.repoRoot, definition.contentDir);
99
+ for (const worktree of worktrees) {
100
+ roots.push({
101
+ contentDir: path.join(worktree, relativeContentDir)
102
+ });
103
+ }
104
+ const files = (await Promise.all(roots.map((root) => walkMarkdownFiles(root.contentDir)))).flat();
105
+ const entries = await Promise.all(
106
+ files.map(async (filePath) => {
107
+ const matchingRoot = roots.find((root) => filePath.startsWith(root.contentDir));
108
+ return readContentEntry(definition, filePath, matchingRoot?.contentDir ?? definition.contentDir);
109
+ })
110
+ );
111
+ const deduped = /* @__PURE__ */ new Map();
112
+ for (const entry of entries) {
113
+ const existing = deduped.get(entry.id);
114
+ if (!existing || new Date(entry.updatedAt ?? 0).valueOf() >= new Date(existing.updatedAt ?? 0).valueOf()) {
115
+ deduped.set(entry.id, entry);
116
+ }
117
+ }
118
+ return [...deduped.values()];
119
+ }
120
+ async get(request) {
121
+ const entries = await this.list(request.model);
122
+ return entries.find((entry) => entryMatchesIdentity(entry, request)) ?? null;
123
+ }
124
+ async search(request) {
125
+ const items = await this.list(request.model);
126
+ const filtered = applyFilters(items, request.filters);
127
+ const sorted = applySort(filtered, request.sort);
128
+ return sorted.slice(0, request.limit ?? sorted.length);
129
+ }
130
+ async follow(request) {
131
+ const items = await this.search({
132
+ model: request.model,
133
+ filters: [
134
+ ...request.filters ?? [],
135
+ {
136
+ field: "updatedAt",
137
+ op: "updated_since",
138
+ value: request.since
139
+ }
140
+ ]
141
+ });
142
+ return {
143
+ items,
144
+ since: request.since
145
+ };
146
+ }
147
+ async pick(request) {
148
+ const definition = resolveModelDefinition(request.model);
149
+ const sorted = await this.search({
150
+ model: request.model,
151
+ filters: request.filters,
152
+ sort: [{ field: definition.pickField, direction: "desc" }],
153
+ limit: 25
154
+ });
155
+ for (const item of sorted) {
156
+ const lease = await this.database.tryClaimContentLease({
157
+ model: definition.name,
158
+ itemKey: item.id,
159
+ claimedBy: request.workerId,
160
+ leaseSeconds: request.leaseSeconds
161
+ });
162
+ if (lease) {
163
+ return {
164
+ item,
165
+ leaseToken: lease
166
+ };
167
+ }
168
+ }
169
+ return {
170
+ item: null,
171
+ leaseToken: null
172
+ };
173
+ }
174
+ async create(request) {
175
+ const definition = resolveModelDefinition(request.model);
176
+ ensureMutationAllowed(definition, "create");
177
+ if (!definition.contentDir) {
178
+ throw new Error(`Model "${request.model}" is not content-backed.`);
179
+ }
180
+ const slug = String(request.data.slug ?? request.data.id ?? crypto.randomUUID());
181
+ const extension = definition.name === "knowledge" ? ".md" : ".mdx";
182
+ const body = typeof request.data.body === "string" ? request.data.body : "";
183
+ const branchName = `${String(request.data.branchPrefix ?? "agent")}/${definition.name}-${slug}`;
184
+ const worktreePath = await this.gitRuntime.ensureWorktree(branchName);
185
+ const contentDirInWorktree = path.join(worktreePath, path.relative(this.repoRoot, definition.contentDir));
186
+ const relativePath = path.relative(this.repoRoot, path.join(definition.contentDir, `${slug}${extension}`));
187
+ const filePath = path.join(worktreePath, relativePath);
188
+ const frontmatter = {
189
+ ...sanitizeFrontmatterInput(request.data),
190
+ slug,
191
+ updated: (/* @__PURE__ */ new Date()).toISOString()
192
+ };
193
+ await mkdir(path.dirname(filePath), { recursive: true });
194
+ await writeFile(filePath, serializeFrontmatterDocument(frontmatter, body), "utf8");
195
+ const git = await this.gitRuntime.commitFileChange(
196
+ filePath,
197
+ branchName,
198
+ `agent(${definition.name}): create ${slug}`
199
+ );
200
+ return {
201
+ item: await readContentEntry(definition, filePath, contentDirInWorktree),
202
+ git
203
+ };
204
+ }
205
+ async update(request) {
206
+ const definition = resolveModelDefinition(request.model);
207
+ ensureMutationAllowed(definition, "update");
208
+ const existing = await this.get(request);
209
+ if (!existing) {
210
+ throw new Error(`No ${request.model} entry found for update.`);
211
+ }
212
+ const branchName = `${String(request.data.branchPrefix ?? "agent")}/${definition.name}-${existing.slug}`;
213
+ const worktreePath = await this.gitRuntime.ensureWorktree(branchName);
214
+ const relativePath = path.relative(this.repoRoot, existing.path);
215
+ const targetPath = path.join(worktreePath, relativePath);
216
+ const nextFrontmatter = {
217
+ ...existing.frontmatter,
218
+ ...sanitizeFrontmatterInput(request.data),
219
+ updated: (/* @__PURE__ */ new Date()).toISOString()
220
+ };
221
+ const nextBody = typeof request.data.body === "string" ? request.data.body : existing.body;
222
+ await mkdir(path.dirname(targetPath), { recursive: true });
223
+ await writeFile(targetPath, serializeFrontmatterDocument(nextFrontmatter, nextBody), "utf8");
224
+ const git = await this.gitRuntime.commitFileChange(
225
+ targetPath,
226
+ branchName,
227
+ `agent(${definition.name}): update ${existing.slug}`
228
+ );
229
+ return {
230
+ item: await readContentEntry(definition, targetPath, path.join(worktreePath, path.relative(this.repoRoot, definition.contentDir))),
231
+ git
232
+ };
233
+ }
234
+ }
235
+ export {
236
+ ContentStore
237
+ };