EXTENSION seed
seed-export
Export a tree's form without its content. The skeleton: node hierarchy, cascade configuration, extension scoping, persona definitions, perspective filters, mode overrides, tool configs. Everything that defines HOW the tree works. Not the notes. Not the conversations. Not the contribution history. The DNA. Another land imports the seed file and grows a replica with the same shape, same cascade topology, same scoping rules, same personas. But empty. Ready for new content. Knowledge transfer through structure replay, not content copy. The rule: structural metadata that defines behavior is exported. Accumulated data metadata that was generated through use is not. If removing it breaks nothing about how the tree operates, it is accumulated data. If removing it changes what the AI can do or how signals flow, it is structural. Seven namespaces pass the filter: cascade, extensions, tools, modes, persona, perspective, purpose. Everything else is excluded: codebook dictionaries, evolution metrics, long-memory connections, embed vectors, compress essences, inverse-tree profiles, contradiction state, prune candidates, explore maps, scout history. Delegation boundaries are preserved. If a branch had a delegated owner, the seed records that structure without carrying the actual userIds (those users do not exist on the target land). The new operator fills the delegation placeholders after planting. Optional cascade topology export summarizes signal flow patterns: which nodes originated signals, which received them, through which paths. Not the actual .flow results. A structural summary so the planting land understands the intended information flow. Three operations. Export walks the tree and produces a JSON seed file. Analyze reads a seed file without planting and reports node count, depth, required extensions, and which are missing on this land. Plant creates the full node hierarchy with structural metadata applied, rootOwner set to the planting user, and warnings for any missing extensions whose metadata is preserved but inactive until installed.
v1.0.1 by TreeOS Site 0 downloads 5 files 701 lines 22.5 KB published 38d ago
treeos ext install seed-export
View changelog

Manifest

Provides

  • routes
  • tools
  • 1 CLI commands
  • 2 energy actions

Requires

  • services: hooks, contributions
  • models: Node

Optional

  • services: energy
  • extensions: propagation
SHA256: 9865294c38fca6b5a5c3b106f0bf839745638a3f10cc38e0d672ddece965fa42

Dependents

1 package depend on this

PackageTypeRelationship
treeos v1.0.1osstandalone

CLI Commands

CommandMethodDescription
seedPOSTTree skeleton export and import. Actions: export, plant, analyze.
seed exportPOSTExport current tree as seed file
seed plantPOSTPlant a seed file at current position
seed analyzePOSTAnalyze a seed file before planting

Source Code

1// Seed Export Core
2//
3// Three operations:
4// 1. exportTreeSeed: walk a tree, extract structural metadata, produce a seed file
5// 2. plantTreeSeed: read a seed file, create the node hierarchy, apply metadata
6// 3. analyzeSeed: read a seed file without planting, report requirements
7
8import log from "../../seed/log.js";
9import { getDescendantIds } from "../../seed/tree/treeFetch.js";
10import { createNode } from "../../seed/tree/treeManagement.js";
11import { getExtension } from "../loader.js";
12
13let Node = null;
14let logContribution = async () => {};
15let useEnergy = async () => ({ energyUsed: 0 });
16let _metadata = null;
17
18export function setServices({ models, contributions, energy, metadata }) {
19  Node = models.Node;
20  logContribution = contributions.logContribution;
21  if (energy?.useEnergy) useEnergy = energy.useEnergy;
22  if (metadata) _metadata = metadata;
23}
24
25// ─────────────────────────────────────────────────────────────────────────
26// STRUCTURAL METADATA WHITELIST
27// ─────────────────────────────────────────────────────────────────────────
28
29// These namespaces define behavior. They get exported.
30// Everything else is accumulated data generated through use. Excluded.
31const STRUCTURAL_NAMESPACES = new Set([
32  "cascade",
33  "extensions",
34  "tools",
35  "modes",
36  "persona",
37  "perspective",
38  "purpose",
39]);
40
41function extractStructuralMetadata(node) {
42  const meta = node.metadata instanceof Map
43    ? Object.fromEntries(node.metadata)
44    : (node.metadata || {});
45
46  const structural = {};
47  for (const ns of STRUCTURAL_NAMESPACES) {
48    if (meta[ns] && Object.keys(meta[ns]).length > 0) {
49      structural[ns] = meta[ns];
50    }
51  }
52  return structural;
53}
54
55function getReferencedExtensions(structural) {
56  const refs = new Set();
57
58  // Namespaces themselves reference their extension
59  for (const ns of Object.keys(structural)) {
60    if (ns !== "extensions" && ns !== "tools" && ns !== "modes" && ns !== "cascade") {
61      refs.add(ns);
62    }
63  }
64
65  // extensions.blocked[] and extensions.allowed[] reference extension names
66  if (structural.extensions) {
67    for (const name of structural.extensions.blocked || []) refs.add(name);
68    for (const name of structural.extensions.allowed || []) refs.add(name);
69  }
70
71  // modes values reference extension-registered modes (e.g. "tree:fitness")
72  if (structural.modes) {
73    for (const modeKey of Object.values(structural.modes)) {
74      const parts = String(modeKey).split(":");
75      if (parts.length >= 2 && parts[0] !== "tree" && parts[0] !== "home" && parts[0] !== "land") {
76        refs.add(parts[0]);
77      }
78    }
79  }
80
81  return refs;
82}
83
84// ─────────────────────────────────────────────────────────────────────────
85// EXPORT
86// ─────────────────────────────────────────────────────────────────────────
87
88const SEED_EXPORT_VERSION = "1.0.0";
89
90export async function exportTreeSeed(rootId, userId, opts = {}) {
91  await useEnergy({ userId, action: "seedExport" });
92
93  const maxNodes = opts.maxExportNodes || 5000;
94  const maxDepth = opts.maxExportDepth || 20;
95
96  // Get all node IDs in this tree via children[] walk
97  const descendantIds = await getDescendantIds(rootId, { maxResults: maxNodes });
98
99  // Fetch all nodes
100  const nodes = await Node.find({
101    _id: { $in: descendantIds },
102    systemRole: { $eq: null },
103  })
104    .select("_id name type status parent children metadata rootOwner contributors")
105    .lean();
106
107  if (nodes.length === 0) throw new Error("Tree has no nodes to export");
108
109  const nodeMap = new Map();
110  for (const n of nodes) nodeMap.set(n._id.toString(), n);
111
112  const root = nodeMap.get(rootId.toString());
113  if (!root) throw new Error("Root node not found");
114
115  // Collect all referenced extensions across the tree
116  const allExtRefs = new Set();
117
118  // Recursive tree builder
119  function buildNode(nodeId, depth) {
120    const node = nodeMap.get(nodeId.toString());
121    if (!node || depth > maxDepth) return null;
122
123    const structural = extractStructuralMetadata(node);
124    for (const ext of getReferencedExtensions(structural)) allExtRefs.add(ext);
125
126    // Delegation boundary detection
127    const isRoot = nodeId.toString() === rootId.toString();
128    const hasDelegation = !isRoot && !!node.rootOwner;
129    const hasContributors = (node.contributors || []).length > 0;
130
131    const children = (node.children || [])
132      .map(childId => buildNode(childId.toString(), depth + 1))
133      .filter(Boolean);
134
135    const exported = {
136      name: node.name,
137      type: node.type || null,
138      status: node.status || "active",
139      children,
140    };
141
142    if (Object.keys(structural).length > 0) {
143      exported.metadata = structural;
144    }
145
146    if (hasDelegation) {
147      exported.delegated = true;
148    }
149
150    if (hasContributors) {
151      exported.contributorCount = node.contributors.length;
152    }
153
154    return exported;
155  }
156
157  const tree = buildNode(rootId.toString(), 0);
158  if (!tree) throw new Error("Failed to build tree skeleton");
159
160  // Calculate stats
161  let nodeCount = 0;
162  let maxTreeDepth = 0;
163  let cascadeNodeCount = 0;
164  let personaCount = 0;
165
166  function countStats(node, depth) {
167    nodeCount++;
168    if (depth > maxTreeDepth) maxTreeDepth = depth;
169    if (node.metadata?.cascade?.enabled) cascadeNodeCount++;
170    if (node.metadata?.persona?.name) personaCount++;
171    for (const child of node.children || []) {
172      countStats(child, depth + 1);
173    }
174  }
175  countStats(tree, 0);
176
177  // Build cascade topology if requested
178  let cascadeTopology = undefined;
179  if (opts.cascade) {
180    cascadeTopology = buildCascadeTopology(nodes, nodeMap);
181  }
182
183  // Determine which extensions are structurally required vs optional
184  const installedExts = new Set();
185  try {
186    const { getLoadedExtensionNames } = await import("../loader.js");
187    for (const name of getLoadedExtensionNames()) installedExts.add(name);
188  } catch {}
189
190  const requiredExtensions = [...allExtRefs].sort();
191  const optionalExtensions = requiredExtensions.filter(e => !installedExts.has(e));
192
193  // Look up username and land info
194  let exportedBy = "unknown";
195  let sourceLand = "unknown";
196  try {
197    const user = await (await import("../../seed/models/user.js")).default
198      .findById(userId).select("username").lean();
199    if (user) exportedBy = user.username;
200  } catch {}
201  try {
202    const { getLandIdentity } = await import("../../canopy/identity.js");
203    const identity = getLandIdentity();
204    if (identity?.domain) sourceLand = identity.domain;
205  } catch {}
206
207  const seedData = {
208    seedExportVersion: SEED_EXPORT_VERSION,
209    exportedAt: new Date().toISOString(),
210    sourceLand,
211    sourceTreeName: root.name,
212    sourceTreeId: rootId.toString(),
213    exportedBy,
214    tree,
215    requiredExtensions,
216    optionalExtensions,
217    stats: {
218      nodeCount,
219      maxDepth: maxTreeDepth,
220      extensionsReferenced: allExtRefs.size,
221      personasIncluded: personaCount,
222      cascadeNodesEnabled: cascadeNodeCount,
223    },
224  };
225
226  if (cascadeTopology) {
227    seedData.cascadeTopology = cascadeTopology;
228  }
229
230  // Log contribution
231  await logContribution({
232    userId,
233    nodeId: rootId.toString(),
234    wasAi: false,
235    action: "seed-export:exported",
236    extensionData: {
237      "seed-export": {
238        nodeCount,
239        maxDepth: maxTreeDepth,
240        requiredExtensions,
241      },
242    },
243  });
244
245  log.info("SeedExport", `Exported tree "${root.name}" (${nodeCount} nodes, depth ${maxTreeDepth})`);
246
247  return seedData;
248}
249
250function buildCascadeTopology(nodes, nodeMap) {
251  const topology = [];
252  const cascadeNodes = nodes.filter(n => {
253    const meta = n.metadata instanceof Map
254      ? n.metadata.get("cascade")
255      : n.metadata?.cascade;
256    return meta?.enabled;
257  });
258
259  for (const node of cascadeNodes) {
260    const nodeName = node.name;
261    for (const childId of node.children || []) {
262      const child = nodeMap.get(childId.toString());
263      if (!child) continue;
264      const childMeta = child.metadata instanceof Map
265        ? child.metadata.get("cascade")
266        : child.metadata?.cascade;
267      if (childMeta?.enabled) {
268        topology.push({
269          from: nodeName,
270          to: child.name,
271          direction: "outbound",
272        });
273      }
274    }
275  }
276
277  return topology;
278}
279
280// ─────────────────────────────────────────────────────────────────────────
281// PLANT
282// ─────────────────────────────────────────────────────────────────────────
283
284export async function plantTreeSeed(seedData, userId, username) {
285  if (!seedData?.seedExportVersion) {
286    throw new Error("Invalid seed file: missing seedExportVersion");
287  }
288  if (!seedData.tree) {
289    throw new Error("Invalid seed file: missing tree data");
290  }
291
292  await useEnergy({ userId, action: "seedPlant" });
293
294  const warnings = [];
295
296  // Check which required extensions are installed
297  let installedExts = new Set();
298  try {
299    const { getLoadedExtensionNames } = await import("../loader.js");
300    for (const name of getLoadedExtensionNames()) installedExts.add(name);
301  } catch {}
302
303  for (const ext of seedData.requiredExtensions || []) {
304    if (!installedExts.has(ext)) {
305      warnings.push(`Extension "${ext}" is not installed. Related metadata preserved but inactive.`);
306    }
307  }
308
309  let nodeCount = 0;
310  const maxPlantNodes = 5000;
311
312  async function plantNode(nodeData, parentId, isRoot) {
313    if (nodeCount >= maxPlantNodes) {
314      warnings.push(`Node cap reached (${maxPlantNodes}). Some branches were not planted.`);
315      return null;
316    }
317
318    // Create the node
319    const newNode = await createNode({
320      name: nodeData.name,
321      parentId,
322      isRoot,
323      userId,
324      type: nodeData.type || null,
325    });
326
327    nodeCount++;
328
329    // Set status if not active (prestige trimmed branches, completed nodes)
330    if (nodeData.status && nodeData.status !== "active") {
331      await Node.updateOne({ _id: newNode._id }, { $set: { status: nodeData.status } });
332    }
333
334    // Write structural metadata
335    if (nodeData.metadata) {
336      const nodeDoc = await Node.findById(newNode._id);
337      if (nodeDoc) {
338        for (const [ns, data] of Object.entries(nodeData.metadata)) {
339          try {
340            await _metadata.setExtMeta(nodeDoc, ns, data);
341          } catch (err) {
342            log.debug("SeedExport", `Failed to write metadata namespace "${ns}": ${err.message}`);
343          }
344        }
345      }
346    }
347
348    // Recurse for children
349    for (const childData of nodeData.children || []) {
350      await plantNode(childData, newNode._id.toString(), false);
351    }
352
353    return newNode;
354  }
355
356  const rootNode = await plantNode(seedData.tree, null, true);
357  if (!rootNode) throw new Error("Failed to create root node from seed");
358
359  // Log contribution
360  await logContribution({
361    userId,
362    nodeId: rootNode._id.toString(),
363    wasAi: false,
364    action: "seed-export:planted",
365    extensionData: {
366      "seed-export": {
367        source: seedData.sourceLand,
368        sourceTree: seedData.sourceTreeName,
369        nodeCount,
370        warnings: warnings.length,
371      },
372    },
373  });
374
375  log.info("SeedExport", `Planted seed "${seedData.sourceTreeName}" from ${seedData.sourceLand} (${nodeCount} nodes, ${warnings.length} warnings)`);
376
377  return {
378    rootId: rootNode._id.toString(),
379    rootName: rootNode.name,
380    nodeCount,
381    warnings,
382  };
383}
384
385// ─────────────────────────────────────────────────────────────────────────
386// ANALYZE
387// ─────────────────────────────────────────────────────────────────────────
388
389export async function analyzeSeed(seedData) {
390  if (!seedData?.seedExportVersion) {
391    throw new Error("Invalid seed file: missing seedExportVersion");
392  }
393  if (!seedData.tree) {
394    throw new Error("Invalid seed file: missing tree data");
395  }
396
397  // Check installed extensions
398  let installedExts = new Set();
399  try {
400    const { getLoadedExtensionNames } = await import("../loader.js");
401    for (const name of getLoadedExtensionNames()) installedExts.add(name);
402  } catch {}
403
404  const required = seedData.requiredExtensions || [];
405  const missing = required.filter(e => !installedExts.has(e));
406  const installed = required.filter(e => installedExts.has(e));
407
408  // Count tree stats from seed data
409  let nodeCount = 0;
410  let maxDepth = 0;
411  let delegatedBranches = 0;
412
413  function walk(node, depth) {
414    nodeCount++;
415    if (depth > maxDepth) maxDepth = depth;
416    if (node.delegated) delegatedBranches++;
417    for (const child of node.children || []) {
418      walk(child, depth + 1);
419    }
420  }
421  walk(seedData.tree, 0);
422
423  return {
424    seedExportVersion: seedData.seedExportVersion,
425    sourceLand: seedData.sourceLand,
426    sourceTreeName: seedData.sourceTreeName,
427    exportedAt: seedData.exportedAt,
428    exportedBy: seedData.exportedBy,
429    nodeCount,
430    maxDepth,
431    delegatedBranches,
432    extensions: {
433      required,
434      installed,
435      missing,
436    },
437    ready: missing.length === 0,
438    cascadeTopology: seedData.cascadeTopology ? seedData.cascadeTopology.length + " connections" : "not included",
439    stats: seedData.stats || {},
440  };
441}
442
1import log from "../../seed/log.js";
2import tools from "./tools.js";
3import { setServices, exportTreeSeed, plantTreeSeed, analyzeSeed } from "./core.js";
4
5export async function init(core) {
6  setServices({
7    models: core.models,
8    contributions: core.contributions,
9    energy: core.energy || null,
10    metadata: core.metadata,
11  });
12
13  const { default: router } = await import("./routes.js");
14
15  log.info("SeedExport", "Tree seed export and plant loaded");
16
17  return {
18    router,
19    tools,
20    exports: {
21      exportTreeSeed,
22      plantTreeSeed,
23      analyzeSeed,
24    },
25  };
26}
27
1export default {
2  name: "seed-export",
3  version: "1.0.1",
4  builtFor: "seed",
5  description:
6    "Export a tree's form without its content. The skeleton: node hierarchy, cascade " +
7    "configuration, extension scoping, persona definitions, perspective filters, mode " +
8    "overrides, tool configs. Everything that defines HOW the tree works. Not the notes. " +
9    "Not the conversations. Not the contribution history. The DNA." +
10    "\n\n" +
11    "Another land imports the seed file and grows a replica with the same shape, same " +
12    "cascade topology, same scoping rules, same personas. But empty. Ready for new " +
13    "content. Knowledge transfer through structure replay, not content copy." +
14    "\n\n" +
15    "The rule: structural metadata that defines behavior is exported. Accumulated data " +
16    "metadata that was generated through use is not. If removing it breaks nothing about " +
17    "how the tree operates, it is accumulated data. If removing it changes what the AI " +
18    "can do or how signals flow, it is structural. Seven namespaces pass the filter: " +
19    "cascade, extensions, tools, modes, persona, perspective, purpose. Everything else " +
20    "is excluded: codebook dictionaries, evolution metrics, long-memory connections, " +
21    "embed vectors, compress essences, inverse-tree profiles, contradiction state, " +
22    "prune candidates, explore maps, scout history." +
23    "\n\n" +
24    "Delegation boundaries are preserved. If a branch had a delegated owner, the seed " +
25    "records that structure without carrying the actual userIds (those users do not exist " +
26    "on the target land). The new operator fills the delegation placeholders after planting." +
27    "\n\n" +
28    "Optional cascade topology export summarizes signal flow patterns: which nodes " +
29    "originated signals, which received them, through which paths. Not the actual .flow " +
30    "results. A structural summary so the planting land understands the intended " +
31    "information flow." +
32    "\n\n" +
33    "Three operations. Export walks the tree and produces a JSON seed file. Analyze reads " +
34    "a seed file without planting and reports node count, depth, required extensions, and " +
35    "which are missing on this land. Plant creates the full node hierarchy with structural " +
36    "metadata applied, rootOwner set to the planting user, and warnings for any missing " +
37    "extensions whose metadata is preserved but inactive until installed.",
38
39  needs: {
40    services: ["hooks", "contributions"],
41    models: ["Node"],
42  },
43
44  optional: {
45    services: ["energy"],
46    extensions: ["propagation"],
47  },
48
49  provides: {
50    models: {},
51    routes: "./routes.js",
52    tools: true,
53    jobs: false,
54    orchestrator: false,
55    energyActions: {
56      seedExport: { cost: 1 },
57      seedPlant: { cost: 2 },
58    },
59    sessionTypes: {},
60
61    hooks: {
62      fires: [],
63      listens: [],
64    },
65
66    cli: [
67      {
68        command: "seed [action] [args...]", scope: ["tree"],
69        description: "Tree skeleton export and import. Actions: export, plant, analyze.",
70        method: "POST",
71        endpoint: "/node/:nodeId/seed-export",
72        subcommands: {
73          export: { method: "POST", endpoint: "/node/:nodeId/seed-export", description: "Export current tree as seed file" },
74          plant: { method: "POST", endpoint: "/node/:nodeId/seed-plant", args: ["file"], description: "Plant a seed file at current position" },
75          analyze: { method: "POST", endpoint: "/seed/analyze", args: ["file"], description: "Analyze a seed file before planting" },
76        },
77      },
78    ],
79  },
80};
81
1import express from "express";
2import { sendOk, sendError, ERR } from "../../seed/protocol.js";
3import authenticate from "../../seed/middleware/authenticate.js";
4import { exportTreeSeed, plantTreeSeed, analyzeSeed } from "./core.js";
5
6const router = express.Router();
7
8// GET /node/:nodeId/seed-export - Export subtree as a seed file
9router.get("/node/:nodeId/seed-export", authenticate, async (req, res) => {
10  try {
11    const { nodeId } = req.params;
12    const cascade = req.query.cascade === "true";
13    const seed = await exportTreeSeed(nodeId, req.userId, { cascade });
14    sendOk(res, seed);
15  } catch (err) {
16    sendError(res, 400, ERR.INVALID_INPUT, err.message);
17  }
18});
19
20// POST /seed/plant - Plant a seed file
21router.post("/seed/plant", authenticate, async (req, res) => {
22  try {
23    const seedData = req.body;
24    if (!seedData?.tree) {
25      return sendError(res, 400, ERR.INVALID_INPUT, "Request body must contain a seed file with a tree field");
26    }
27    const result = await plantTreeSeed(seedData, req.userId, req.username);
28    sendOk(res, result, 201);
29  } catch (err) {
30    sendError(res, 400, ERR.INVALID_INPUT, err.message);
31  }
32});
33
34// POST /seed/analyze - Analyze a seed file without planting
35router.post("/seed/analyze", authenticate, async (req, res) => {
36  try {
37    const seedData = req.body;
38    if (!seedData?.tree) {
39      return sendError(res, 400, ERR.INVALID_INPUT, "Request body must contain a seed file with a tree field");
40    }
41    const analysis = await analyzeSeed(seedData);
42    sendOk(res, analysis);
43  } catch (err) {
44    sendError(res, 400, ERR.INVALID_INPUT, err.message);
45  }
46});
47
48export default router;
49
1import { z } from "zod";
2import { exportTreeSeed, plantTreeSeed, analyzeSeed } from "./core.js";
3
4export default [
5  {
6    name: "seed-export",
7    description:
8      "Export the current tree as a seed file. Captures the node hierarchy and structural " +
9      "metadata (cascade, scoping, tools, modes, personas, perspectives, purpose) without " +
10      "any content. The DNA of the tree.",
11    schema: {
12      rootId: z.string().describe("Tree root to export."),
13      cascade: z.boolean().optional().default(false).describe("Include cascade topology summary."),
14      userId: z.string().describe("Injected by server. Ignore."),
15      chatId: z.string().nullable().optional().describe("Injected by server. Ignore."),
16      sessionId: z.string().nullable().optional().describe("Injected by server. Ignore."),
17    },
18    annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
19    handler: async ({ rootId, cascade, userId }) => {
20      try {
21        const seed = await exportTreeSeed(rootId, userId, { cascade });
22        return {
23          content: [{
24            type: "text",
25            text: JSON.stringify({
26              sourceTreeName: seed.sourceTreeName,
27              nodeCount: seed.stats.nodeCount,
28              maxDepth: seed.stats.maxDepth,
29              requiredExtensions: seed.requiredExtensions,
30              cascadeTopology: seed.cascadeTopology?.length || 0,
31              message: "Seed exported. Use seed-plant to plant it on another tree, or seed-analyze to inspect it.",
32            }, null, 2),
33          }],
34        };
35      } catch (err) {
36        return { content: [{ type: "text", text: `Export failed: ${err.message}` }] };
37      }
38    },
39  },
40  {
41    name: "seed-analyze",
42    description:
43      "Analyze a seed file before planting. Reports node count, depth, required extensions, " +
44      "and which are missing on this land.",
45    schema: {
46      seedJson: z.string().describe("The seed file JSON as a string."),
47      userId: z.string().describe("Injected by server. Ignore."),
48      chatId: z.string().nullable().optional().describe("Injected by server. Ignore."),
49      sessionId: z.string().nullable().optional().describe("Injected by server. Ignore."),
50    },
51    annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
52    handler: async ({ seedJson }) => {
53      try {
54        const seedData = JSON.parse(seedJson);
55        const analysis = await analyzeSeed(seedData);
56        return {
57          content: [{
58            type: "text",
59            text: JSON.stringify(analysis, null, 2),
60          }],
61        };
62      } catch (err) {
63        return { content: [{ type: "text", text: `Analysis failed: ${err.message}` }] };
64      }
65    },
66  },
67  {
68    name: "seed-plant",
69    description:
70      "Plant a seed file to create a new tree with the exported structure. " +
71      "Creates the full node hierarchy with structural metadata applied.",
72    schema: {
73      seedJson: z.string().describe("The seed file JSON as a string."),
74      userId: z.string().describe("Injected by server. Ignore."),
75      chatId: z.string().nullable().optional().describe("Injected by server. Ignore."),
76      sessionId: z.string().nullable().optional().describe("Injected by server. Ignore."),
77    },
78    annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: false },
79    handler: async ({ seedJson, userId }) => {
80      try {
81        const User = (await import("../../seed/models/user.js")).default;
82        const user = await User.findById(userId).select("username").lean();
83        const seedData = JSON.parse(seedJson);
84        const result = await plantTreeSeed(seedData, userId, user?.username || "system");
85        return {
86          content: [{
87            type: "text",
88            text: JSON.stringify({
89              rootId: result.rootId,
90              rootName: result.rootName,
91              nodeCount: result.nodeCount,
92              warnings: result.warnings,
93            }, null, 2),
94          }],
95        };
96      } catch (err) {
97        return { content: [{ type: "text", text: `Plant failed: ${err.message}` }] };
98      }
99    },
100  },
101];
102

Versions

Version Published Downloads
1.0.1 38d ago 0
1.0.0 48d ago 0
0 stars
0 flags
React from the CLI: treeos ext star seed-export

Comments

Loading comments...

Post comments from the CLI: treeos ext comment seed-export "your comment"
Max 3 comments per extension. One star and one flag per user.