trellis 3.0.2 → 3.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.
- package/README.md +9 -5
- package/dist/cli/index.js +94 -41
- package/dist/cms/client.d.ts +65 -0
- package/dist/cms/client.d.ts.map +1 -0
- package/dist/cms/index.d.ts +10 -0
- package/dist/cms/index.d.ts.map +1 -0
- package/dist/cms/index.js +413 -0
- package/dist/cms/internal.d.ts +27 -0
- package/dist/cms/internal.d.ts.map +1 -0
- package/dist/cms/scaffold.d.ts +18 -0
- package/dist/cms/scaffold.d.ts.map +1 -0
- package/dist/cms/types.d.ts +62 -0
- package/dist/cms/types.d.ts.map +1 -0
- package/dist/db/index.js +9 -9
- package/dist/server/index.js +18 -18
- package/package.json +7 -2
package/README.md
CHANGED
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
# Trellis
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
> **The Agentic Framework** — A structured runtime for building agents that understand code, remember everything, and explain themselves.
|
|
3
|
+
> **The Agentic Framework**
|
|
6
4
|
|
|
7
5
|
| Use Case | How |
|
|
8
6
|
| :----------------------------- | :-------------------------------------------------------------------------------------------------------- |
|
|
@@ -42,10 +40,12 @@ echo "# My Project" > README.md
|
|
|
42
40
|
trellis issue create --title "Bootstrap Visualization"
|
|
43
41
|
trellis milestone "Initial Release"
|
|
44
42
|
|
|
45
|
-
# 5.
|
|
43
|
+
# 5. Create coding session with trellis harness
|
|
46
44
|
trellis code
|
|
47
45
|
```
|
|
48
46
|
|
|
47
|
+

|
|
48
|
+
|
|
49
49
|
See the [CLI guide](https://trellis.computer/docs/cli) for complete documentation.
|
|
50
50
|
|
|
51
51
|
---
|
|
@@ -80,7 +80,11 @@ Ops are written to `.trellis/ops.json` and **never rewritten or deleted**.
|
|
|
80
80
|
|
|
81
81
|
## Documentation
|
|
82
82
|
|
|
83
|
-
- **[
|
|
83
|
+
- **[Local documentation](./docs/README.md)** — Canonical docs for the active codebase
|
|
84
|
+
- **[Vision](./docs/VISION.md)** — Local-first agentic OS framing
|
|
85
|
+
- **[Architecture](./docs/ARCHITECTURE.md)** — Current package layout and target runtime shape
|
|
86
|
+
- **[Roadmap](./ROADMAP.md)** — Active Trellis issue and milestone sequence
|
|
87
|
+
- **[Full documentation](https://trellis.computer)** — Published docs site
|
|
84
88
|
- **[CLI reference](./README-ARCHIVED.md#cli-overview)** — Command details (archived)
|
|
85
89
|
- **[API modules](./README-ARCHIVED.md#module--subpath-guide)** — Subpath imports (archived)
|
|
86
90
|
- **[DESIGN.md](./DESIGN.md)** — Architecture specification
|
package/dist/cli/index.js
CHANGED
|
@@ -2886,7 +2886,7 @@ var {
|
|
|
2886
2886
|
Help
|
|
2887
2887
|
} = import__.default;
|
|
2888
2888
|
|
|
2889
|
-
//
|
|
2889
|
+
// node_modules/chalk/source/vendor/ansi-styles/index.js
|
|
2890
2890
|
var ANSI_BACKGROUND_OFFSET = 10;
|
|
2891
2891
|
var wrapAnsi16 = (offset = 0) => (code) => `\x1B[${code + offset}m`;
|
|
2892
2892
|
var wrapAnsi256 = (offset = 0) => (code) => `\x1B[${38 + offset};5;${code}m`;
|
|
@@ -3063,7 +3063,7 @@ function assembleStyles() {
|
|
|
3063
3063
|
var ansiStyles = assembleStyles();
|
|
3064
3064
|
var ansi_styles_default = ansiStyles;
|
|
3065
3065
|
|
|
3066
|
-
//
|
|
3066
|
+
// node_modules/chalk/source/vendor/supports-color/index.js
|
|
3067
3067
|
import process2 from "process";
|
|
3068
3068
|
import os from "os";
|
|
3069
3069
|
import tty from "tty";
|
|
@@ -3195,7 +3195,7 @@ var supportsColor = {
|
|
|
3195
3195
|
};
|
|
3196
3196
|
var supports_color_default = supportsColor;
|
|
3197
3197
|
|
|
3198
|
-
//
|
|
3198
|
+
// node_modules/chalk/source/utilities.js
|
|
3199
3199
|
function stringReplaceAll(string, substring, replacer) {
|
|
3200
3200
|
let index = string.indexOf(substring);
|
|
3201
3201
|
if (index === -1) {
|
|
@@ -3228,7 +3228,7 @@ function stringEncaseCRLFWithFirstIndex(string, prefix, postfix, index) {
|
|
|
3228
3228
|
return returnValue;
|
|
3229
3229
|
}
|
|
3230
3230
|
|
|
3231
|
-
//
|
|
3231
|
+
// node_modules/chalk/source/index.js
|
|
3232
3232
|
var { stdout: stdoutColor, stderr: stderrColor } = supports_color_default;
|
|
3233
3233
|
var GENERATOR = Symbol("GENERATOR");
|
|
3234
3234
|
var STYLER = Symbol("STYLER");
|
|
@@ -5716,16 +5716,13 @@ function requireRepo(rootPath) {
|
|
|
5716
5716
|
process.exit(1);
|
|
5717
5717
|
}
|
|
5718
5718
|
}
|
|
5719
|
-
|
|
5720
|
-
const
|
|
5721
|
-
const isInteractive = process.stdout.isTTY && !process.argv.includes("--no-interactive");
|
|
5719
|
+
async function runInit(rootPath, opts = {}) {
|
|
5720
|
+
const isInteractive = opts.interactive !== false && process.stdout.isTTY && !process.argv.includes("--no-interactive");
|
|
5722
5721
|
if (TrellisVcsEngine.isRepo(rootPath)) {
|
|
5723
|
-
console.log(source_default.yellow("Already a Trellis workspace."));
|
|
5724
5722
|
return;
|
|
5725
5723
|
}
|
|
5726
5724
|
if (!hasProfile()) {
|
|
5727
|
-
const
|
|
5728
|
-
const identity = hasIdentity(trellisDir) ? loadIdentity(trellisDir) : null;
|
|
5725
|
+
const identity = hasIdentity(rootPath) ? loadIdentity(join6(rootPath, ".trellis")) : null;
|
|
5729
5726
|
if (isInteractive) {
|
|
5730
5727
|
console.log(source_default.cyan(`
|
|
5731
5728
|
Welcome to Trellis! Let's get you set up.`));
|
|
@@ -5884,7 +5881,6 @@ program2.command("init").description("Initialize a new TrellisVCS repository in
|
|
|
5884
5881
|
}
|
|
5885
5882
|
if (selectedIdes && selectedIdes.length > 0) {
|
|
5886
5883
|
for (const selectedIde of selectedIdes) {
|
|
5887
|
-
console.log(source_default.dim(` Scaffolding ${selectedIde} dotfolder...`));
|
|
5888
5884
|
writeIdeScaffold(rootPath, {
|
|
5889
5885
|
ide: selectedIde,
|
|
5890
5886
|
footprint,
|
|
@@ -5896,25 +5892,34 @@ program2.command("init").description("Initialize a new TrellisVCS repository in
|
|
|
5896
5892
|
});
|
|
5897
5893
|
}
|
|
5898
5894
|
}
|
|
5895
|
+
}
|
|
5896
|
+
program2.command("init").description("Initialize a new TrellisVCS repository in the current directory").option("-p, --path <path>", "Path to initialize", ".").option("--ides <ides...>", "IDEs to scaffold for (cursor, windsurf, claude, copilot, codex, gemini)").option("--framework <framework>", "Project framework (react, vue, svelte, next, nuxt, remotion, expo, bun, node, cli, library, animation, games, none)", "none").option("--footprint <footprint>", "Workspace footprint (minimal, standard, full)", "standard").option("--no-interactive", "Skip interactive prompts").action(async (opts) => {
|
|
5897
|
+
const rootPath = resolve(opts.path);
|
|
5898
|
+
if (TrellisVcsEngine.isRepo(rootPath)) {
|
|
5899
|
+
console.log(source_default.yellow("Already a Trellis workspace."));
|
|
5900
|
+
return;
|
|
5901
|
+
}
|
|
5902
|
+
await runInit(rootPath, {
|
|
5903
|
+
interactive: process.stdout.isTTY && !process.argv.includes("--no-interactive"),
|
|
5904
|
+
ides: opts.ides,
|
|
5905
|
+
framework: opts.framework,
|
|
5906
|
+
footprint: opts.footprint
|
|
5907
|
+
});
|
|
5899
5908
|
console.log(source_default.green("\u2713 Initialized Trellis repository"));
|
|
5909
|
+
const engine = new TrellisVcsEngine({ rootPath });
|
|
5910
|
+
const opsCount = (await engine.log()).length;
|
|
5900
5911
|
console.log(` ${source_default.dim("Path:")} ${rootPath}`);
|
|
5901
|
-
console.log(` ${source_default.dim("Ops:")} ${
|
|
5912
|
+
console.log(` ${source_default.dim("Ops:")} ${opsCount} initial operations scanned`);
|
|
5902
5913
|
console.log(` ${source_default.dim("Config:")} .trellis/config.json`);
|
|
5903
5914
|
console.log(` ${source_default.dim("Op log:")} .trellis/ops.json`);
|
|
5904
5915
|
console.log(` ${source_default.dim("Graph DB:")} .trellis/graph.db`);
|
|
5905
5916
|
console.log(` ${source_default.dim("Agent context:")} .trellis/agents/AGENTS.md`);
|
|
5906
|
-
|
|
5907
|
-
|
|
5908
|
-
|
|
5909
|
-
return ide === "cursor" ? ".cursor" : ide === "windsurf" ? ".windsurf" : ide === "claude" ? ".claude" : ide === "codex" ? ".codex" : ide === "gemini" ? ".gemini" : ".github/copilot";
|
|
5910
|
-
});
|
|
5911
|
-
console.log(` ${source_default.dim("IDE rules:")} ${ideFolders.join(", ")}`);
|
|
5912
|
-
}
|
|
5913
|
-
if (result.context.domain) {
|
|
5914
|
-
console.log(` ${source_default.dim("Domain:")} ${source_default.cyan(result.context.domain)} ${source_default.dim("(inferred)")}`);
|
|
5917
|
+
const preInfer = await inferProjectContext(rootPath);
|
|
5918
|
+
if (preInfer.domain) {
|
|
5919
|
+
console.log(` ${source_default.dim("Domain:")} ${source_default.cyan(preInfer.domain)} ${source_default.dim("(inferred)")}`);
|
|
5915
5920
|
}
|
|
5916
|
-
if (
|
|
5917
|
-
console.log(` ${source_default.dim("Ecosystem:")} ${
|
|
5921
|
+
if (preInfer.ecosystem && preInfer.ecosystem !== "unknown") {
|
|
5922
|
+
console.log(` ${source_default.dim("Ecosystem:")} ${preInfer.ecosystem}`);
|
|
5918
5923
|
}
|
|
5919
5924
|
console.log();
|
|
5920
5925
|
console.log(source_default.bold("Next steps:"));
|
|
@@ -5925,7 +5930,7 @@ program2.command("init").description("Initialize a new TrellisVCS repository in
|
|
|
5925
5930
|
console.log(` ${source_default.cyan("trellis milestone")} Create narrative checkpoints`);
|
|
5926
5931
|
console.log(` ${source_default.cyan("trellis garden")} Discover abandoned work`);
|
|
5927
5932
|
console.log(` ${source_default.cyan("trellis issue")} Create and track issues`);
|
|
5928
|
-
if (
|
|
5933
|
+
if (preInfer.confidence !== "high") {
|
|
5929
5934
|
console.log(` ${source_default.cyan("trellis season")} Enrich project context for agents`);
|
|
5930
5935
|
}
|
|
5931
5936
|
console.log();
|
|
@@ -8573,16 +8578,21 @@ program2.command("season").description("Enrich project context for agents \u2014
|
|
|
8573
8578
|
}
|
|
8574
8579
|
console.log();
|
|
8575
8580
|
});
|
|
8576
|
-
program2.command("code").alias("ide").description('Launch OpenCode in "Harness" mode, bridged to this Trellis repository').option("-p, --path <path>", "Repository path", ".").option("-m, --model <model>", "OpenCode model to use").option("-w, --web", "Launch MCP server in HTTP mode for web client access").option("--mcp-port <port>", "MCP HTTP server port (default: 3333)", "3333").action(async (opts) => {
|
|
8581
|
+
program2.command("code").alias("ide").description('Launch OpenCode in "Harness" mode, bridged to this Trellis repository').option("-p, --path <path>", "Repository path", ".").option("-m, --model <model>", "OpenCode model to use").option("-w, --web", "Launch MCP server in HTTP mode for web client access").option("--mcp-port <port>", "MCP HTTP server port (default: 3333)", "3333").option("--no-init", "Skip initialization even if not a Trellis workspace").action(async (opts) => {
|
|
8577
8582
|
const { resolve: resolve2, dirname: dirname6, join: join7 } = await import("path");
|
|
8578
8583
|
const { existsSync: existsSync6 } = await import("fs");
|
|
8579
8584
|
const { spawn } = await import("child_process");
|
|
8585
|
+
const { readFileSync: readFileSync4 } = await import("fs");
|
|
8586
|
+
const { createServer: createHttpServer } = await import("http");
|
|
8580
8587
|
const rootPath = resolve2(opts.path);
|
|
8581
|
-
if (!
|
|
8582
|
-
|
|
8583
|
-
|
|
8584
|
-
|
|
8585
|
-
|
|
8588
|
+
if (!opts.noInit) {
|
|
8589
|
+
if (!TrellisVcsEngine.isRepo(rootPath)) {
|
|
8590
|
+
console.log(source_default.dim("Not a Trellis workspace \u2014 initializing\u2026"));
|
|
8591
|
+
await runInit(rootPath, {
|
|
8592
|
+
interactive: false
|
|
8593
|
+
});
|
|
8594
|
+
console.log(source_default.green("\u2713 Initialized Trellis repository"));
|
|
8595
|
+
}
|
|
8586
8596
|
}
|
|
8587
8597
|
function findOpencode() {
|
|
8588
8598
|
const here = dirname6(process.argv[1]);
|
|
@@ -8639,24 +8649,46 @@ program2.command("code").alias("ide").description('Launch OpenCode in "Harness"
|
|
|
8639
8649
|
console.log(source_default.dim(` Web: ${mcpUrl}`));
|
|
8640
8650
|
}
|
|
8641
8651
|
console.log("");
|
|
8652
|
+
let mcpProcess = null;
|
|
8653
|
+
if (opts.web) {
|
|
8654
|
+
console.log(source_default.dim(` Starting MCP server on port ${mcpPort}\u2026`));
|
|
8655
|
+
mcpProcess = spawn("bun", ["run", mcp, "--quiet", "--path", rootPath, "--http", "--port", String(mcpPort)], {
|
|
8656
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
8657
|
+
detached: false
|
|
8658
|
+
});
|
|
8659
|
+
mcpProcess.stderr?.on("data", (data) => {
|
|
8660
|
+
const msg = data.toString();
|
|
8661
|
+
if (!msg.includes("TrellisVCS MCP Server")) {
|
|
8662
|
+
process.stderr.write(source_default.dim(` [mcp] ${msg}`));
|
|
8663
|
+
}
|
|
8664
|
+
});
|
|
8665
|
+
mcpProcess.on("error", (err) => {
|
|
8666
|
+
console.error(source_default.red(`
|
|
8667
|
+
\u2717 MCP server failed to start: ${err.message}`));
|
|
8668
|
+
process.exit(1);
|
|
8669
|
+
});
|
|
8670
|
+
const ready = await waitForMcpReady(mcpUrl, 1e4);
|
|
8671
|
+
if (!ready) {
|
|
8672
|
+
console.error(source_default.red(`
|
|
8673
|
+
\u2717 MCP server did not become ready in time`));
|
|
8674
|
+
if (mcpProcess) {
|
|
8675
|
+
mcpProcess.kill();
|
|
8676
|
+
}
|
|
8677
|
+
process.exit(1);
|
|
8678
|
+
}
|
|
8679
|
+
console.log(source_default.green(`\u2713 MCP server ready at ${mcpUrl}`));
|
|
8680
|
+
}
|
|
8642
8681
|
const args = [rootPath];
|
|
8643
8682
|
if (opts.model) {
|
|
8644
8683
|
args.push("--model", opts.model);
|
|
8645
8684
|
}
|
|
8646
8685
|
const mcpServers = {
|
|
8647
8686
|
mcpServers: {
|
|
8648
|
-
"trellis-vcs": {
|
|
8687
|
+
"trellis-vcs": opts.web ? {
|
|
8688
|
+
url: `${mcpUrl}/sse`
|
|
8689
|
+
} : {
|
|
8649
8690
|
command: "bun",
|
|
8650
|
-
args:
|
|
8651
|
-
"run",
|
|
8652
|
-
mcp,
|
|
8653
|
-
"--quiet",
|
|
8654
|
-
"--path",
|
|
8655
|
-
rootPath,
|
|
8656
|
-
"--http",
|
|
8657
|
-
"--port",
|
|
8658
|
-
String(mcpPort)
|
|
8659
|
-
] : ["run", mcp, "--quiet", "--path", rootPath]
|
|
8691
|
+
args: ["run", mcp, "--quiet", "--path", rootPath]
|
|
8660
8692
|
}
|
|
8661
8693
|
}
|
|
8662
8694
|
};
|
|
@@ -8672,6 +8704,9 @@ program2.command("code").alias("ide").description('Launch OpenCode in "Harness"
|
|
|
8672
8704
|
}
|
|
8673
8705
|
});
|
|
8674
8706
|
child.on("exit", (code) => {
|
|
8707
|
+
if (mcpProcess) {
|
|
8708
|
+
mcpProcess.kill();
|
|
8709
|
+
}
|
|
8675
8710
|
if (code === 0) {
|
|
8676
8711
|
console.log(source_default.green(`
|
|
8677
8712
|
\u2713 Agentic session complete.`));
|
|
@@ -8682,4 +8717,22 @@ program2.command("code").alias("ide").description('Launch OpenCode in "Harness"
|
|
|
8682
8717
|
process.exit(code ?? 0);
|
|
8683
8718
|
});
|
|
8684
8719
|
});
|
|
8720
|
+
async function waitForMcpReady(url, timeoutMs) {
|
|
8721
|
+
const { get } = await import("http");
|
|
8722
|
+
const start = Date.now();
|
|
8723
|
+
while (Date.now() - start < timeoutMs) {
|
|
8724
|
+
await new Promise((resolve2) => setTimeout(resolve2, 500));
|
|
8725
|
+
try {
|
|
8726
|
+
const res = await new Promise((resolve2, reject) => {
|
|
8727
|
+
const req = get(`${url}/health`, resolve2);
|
|
8728
|
+
req.on("error", reject);
|
|
8729
|
+
req.setTimeout(2000);
|
|
8730
|
+
});
|
|
8731
|
+
if (res.statusCode === 200) {
|
|
8732
|
+
return true;
|
|
8733
|
+
}
|
|
8734
|
+
} catch {}
|
|
8735
|
+
}
|
|
8736
|
+
return false;
|
|
8737
|
+
}
|
|
8685
8738
|
program2.parse();
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Trellis CMS Client
|
|
3
|
+
*
|
|
4
|
+
* Thin HTTP client for reading content collections from a Trellis-compatible store
|
|
5
|
+
* (currently opencode's /store/* routes). Reads only; writes happen through the
|
|
6
|
+
* IDE's CMS panel or the agent's `cms` tool.
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* import { createCmsClient } from "trellis/cms";
|
|
10
|
+
*
|
|
11
|
+
* const cms = createCmsClient({ url: "http://localhost:4096" });
|
|
12
|
+
*
|
|
13
|
+
* // List published blog posts with author resolved
|
|
14
|
+
* const posts = await cms.collection("blog_post").list({
|
|
15
|
+
* status: "published",
|
|
16
|
+
* expand: ["author"],
|
|
17
|
+
* });
|
|
18
|
+
*
|
|
19
|
+
* // Subscribe to live updates (polling for now, SSE later)
|
|
20
|
+
* const off = cms.collection("blog_post").subscribe((entries) => {
|
|
21
|
+
* console.log(entries);
|
|
22
|
+
* });
|
|
23
|
+
*
|
|
24
|
+
* @module trellis/cms
|
|
25
|
+
*/
|
|
26
|
+
import type { CmsClientOptions, Collection, Entry, EntrySubscriber, GetOptions, ListOptions, ListSubscriber, Unsubscribe } from './types.js';
|
|
27
|
+
export declare class CmsClient {
|
|
28
|
+
private readonly url;
|
|
29
|
+
private readonly basePath;
|
|
30
|
+
private readonly directory?;
|
|
31
|
+
readonly pollIntervalMs: number;
|
|
32
|
+
private readonly fetchFn;
|
|
33
|
+
private readonly apiKey?;
|
|
34
|
+
constructor(opts: CmsClientOptions);
|
|
35
|
+
collection<T extends Record<string, unknown> = Record<string, unknown>>(key: string): CollectionRef<T>;
|
|
36
|
+
entry<T extends Record<string, unknown> = Record<string, unknown>>(id: string): EntryRef<T>;
|
|
37
|
+
/** List all CMS collections (explicit + inferred). */
|
|
38
|
+
collections(): Promise<Collection[]>;
|
|
39
|
+
close(): void;
|
|
40
|
+
/** @internal */
|
|
41
|
+
_get<T>(path: string): Promise<T | undefined>;
|
|
42
|
+
/** @internal */
|
|
43
|
+
_entryById(id: string): Promise<Entry | null>;
|
|
44
|
+
}
|
|
45
|
+
export declare class CollectionRef<T extends Record<string, unknown> = Record<string, unknown>> {
|
|
46
|
+
private readonly client;
|
|
47
|
+
readonly key: string;
|
|
48
|
+
constructor(client: CmsClient, key: string);
|
|
49
|
+
list(opts?: ListOptions): Promise<Entry<T>[]>;
|
|
50
|
+
get(id: string, opts?: GetOptions): Promise<Entry<T> | null>;
|
|
51
|
+
/**
|
|
52
|
+
* Subscribe to changes. Currently implemented as polling; a future SSE-backed
|
|
53
|
+
* upgrade will replace the transport without changing this API.
|
|
54
|
+
*/
|
|
55
|
+
subscribe(callback: ListSubscriber<T>, opts?: ListOptions): Unsubscribe;
|
|
56
|
+
}
|
|
57
|
+
export declare class EntryRef<T extends Record<string, unknown> = Record<string, unknown>> {
|
|
58
|
+
private readonly client;
|
|
59
|
+
readonly id: string;
|
|
60
|
+
constructor(client: CmsClient, id: string);
|
|
61
|
+
get(opts?: GetOptions): Promise<Entry<T> | null>;
|
|
62
|
+
subscribe(callback: EntrySubscriber<T>, opts?: GetOptions): Unsubscribe;
|
|
63
|
+
}
|
|
64
|
+
export declare function createCmsClient(opts: CmsClientOptions): CmsClient;
|
|
65
|
+
//# sourceMappingURL=client.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../src/cms/client.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAEH,OAAO,KAAK,EACV,gBAAgB,EAChB,UAAU,EACV,KAAK,EACL,eAAe,EACf,UAAU,EACV,WAAW,EACX,cAAc,EACd,WAAW,EACZ,MAAM,YAAY,CAAC;AAiBpB,qBAAa,SAAS;IACpB,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAS;IAC7B,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAS;IAClC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAS;IACpC,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;IAChC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAe;IACvC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAS;gBAErB,IAAI,EAAE,gBAAgB;IASlC,UAAU,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACpE,GAAG,EAAE,MAAM,GACV,aAAa,CAAC,CAAC,CAAC;IAInB,KAAK,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,EAAE,EAAE,MAAM,GAAG,QAAQ,CAAC,CAAC,CAAC;IAI3F,sDAAsD;IAChD,WAAW,IAAI,OAAO,CAAC,UAAU,EAAE,CAAC;IAiD1C,KAAK,IAAI,IAAI;IAIb,gBAAgB;IACV,IAAI,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,CAAC,GAAG,SAAS,CAAC;IAYnD,gBAAgB;IACV,UAAU,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,KAAK,GAAG,IAAI,CAAC;CASpD;AAED,qBAAa,aAAa,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;IAElF,OAAO,CAAC,QAAQ,CAAC,MAAM;IACvB,QAAQ,CAAC,GAAG,EAAE,MAAM;gBADH,MAAM,EAAE,SAAS,EACzB,GAAG,EAAE,MAAM;IAGhB,IAAI,CAAC,IAAI,GAAE,WAAgB,GAAG,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC;IA+BjD,GAAG,CAAC,EAAE,EAAE,MAAM,EAAE,IAAI,GAAE,UAAe,GAAG,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC;IAYtE;;;OAGG;IACH,SAAS,CAAC,QAAQ,EAAE,cAAc,CAAC,CAAC,CAAC,EAAE,IAAI,GAAE,WAAgB,GAAG,WAAW;CAyB5E;AAED,qBAAa,QAAQ,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;IAE7E,OAAO,CAAC,QAAQ,CAAC,MAAM;IACvB,QAAQ,CAAC,EAAE,EAAE,MAAM;gBADF,MAAM,EAAE,SAAS,EACzB,EAAE,EAAE,MAAM;IAGf,GAAG,CAAC,IAAI,GAAE,UAAe,GAAG,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC;IAY1D,SAAS,CAAC,QAAQ,EAAE,eAAe,CAAC,CAAC,CAAC,EAAE,IAAI,GAAE,UAAe,GAAG,WAAW;CAyB5E;AAED,wBAAgB,eAAe,CAAC,IAAI,EAAE,gBAAgB,GAAG,SAAS,CAEjE"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Trellis CMS — client-side SDK for reading content collections.
|
|
3
|
+
*
|
|
4
|
+
* @module trellis/cms
|
|
5
|
+
*/
|
|
6
|
+
export { CmsClient, CollectionRef, EntryRef, createCmsClient } from './client.js';
|
|
7
|
+
export type { CmsClientOptions, Collection, Entry, EntryStatus, EntrySubscriber, Framework, GetOptions, ListOptions, ListSubscriber, Unsubscribe, } from './types.js';
|
|
8
|
+
export { scaffoldConsumer, scaffoldFilename } from './scaffold.js';
|
|
9
|
+
export type { ScaffoldOptions } from './scaffold.js';
|
|
10
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/cms/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,SAAS,EAAE,aAAa,EAAE,QAAQ,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAElF,YAAY,EACV,gBAAgB,EAChB,UAAU,EACV,KAAK,EACL,WAAW,EACX,eAAe,EACf,SAAS,EACT,UAAU,EACV,WAAW,EACX,cAAc,EACd,WAAW,GACZ,MAAM,YAAY,CAAC;AAEpB,OAAO,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AACnE,YAAY,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC"}
|
|
@@ -0,0 +1,413 @@
|
|
|
1
|
+
// @bun
|
|
2
|
+
import"../index-a76rekgs.js";
|
|
3
|
+
|
|
4
|
+
// src/cms/internal.ts
|
|
5
|
+
var SYSTEM_TYPES = new Set([
|
|
6
|
+
"issue",
|
|
7
|
+
"agent",
|
|
8
|
+
"project",
|
|
9
|
+
"memory",
|
|
10
|
+
"mcp",
|
|
11
|
+
"sprite",
|
|
12
|
+
"workunit",
|
|
13
|
+
"cycle",
|
|
14
|
+
"epic",
|
|
15
|
+
"roadmap",
|
|
16
|
+
"suggestion",
|
|
17
|
+
"file",
|
|
18
|
+
"directory",
|
|
19
|
+
"op",
|
|
20
|
+
"branch",
|
|
21
|
+
"decision",
|
|
22
|
+
"session",
|
|
23
|
+
"typeschema",
|
|
24
|
+
"field"
|
|
25
|
+
]);
|
|
26
|
+
function isSystemType(type) {
|
|
27
|
+
return SYSTEM_TYPES.has(type.trim().toLowerCase());
|
|
28
|
+
}
|
|
29
|
+
function typeKey(type) {
|
|
30
|
+
return type.trim().toLowerCase();
|
|
31
|
+
}
|
|
32
|
+
function entryFromFacts(entity, facts) {
|
|
33
|
+
const fields = {};
|
|
34
|
+
let status = "draft";
|
|
35
|
+
for (const f of facts) {
|
|
36
|
+
if (f.a === "type")
|
|
37
|
+
continue;
|
|
38
|
+
if (f.a === "cms_status") {
|
|
39
|
+
status = f.v === "published" ? "published" : "draft";
|
|
40
|
+
continue;
|
|
41
|
+
}
|
|
42
|
+
fields[f.a] = f.v;
|
|
43
|
+
}
|
|
44
|
+
return { id: entity.id, type: entity.type, status, fields };
|
|
45
|
+
}
|
|
46
|
+
function groupFactsByEntity(facts) {
|
|
47
|
+
const map = new Map;
|
|
48
|
+
for (const f of facts) {
|
|
49
|
+
const list = map.get(f.e);
|
|
50
|
+
if (list)
|
|
51
|
+
list.push(f);
|
|
52
|
+
else
|
|
53
|
+
map.set(f.e, [f]);
|
|
54
|
+
}
|
|
55
|
+
return map;
|
|
56
|
+
}
|
|
57
|
+
async function expandReferences(entries, expandKeys, fetchEntity) {
|
|
58
|
+
const ids = new Set;
|
|
59
|
+
for (const entry of entries) {
|
|
60
|
+
for (const key of expandKeys) {
|
|
61
|
+
const v = entry.fields[key];
|
|
62
|
+
if (typeof v === "string")
|
|
63
|
+
ids.add(v);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
if (ids.size === 0)
|
|
67
|
+
return entries;
|
|
68
|
+
const resolved = new Map;
|
|
69
|
+
await Promise.all([...ids].map(async (id) => {
|
|
70
|
+
try {
|
|
71
|
+
resolved.set(id, await fetchEntity(id));
|
|
72
|
+
} catch {
|
|
73
|
+
resolved.set(id, null);
|
|
74
|
+
}
|
|
75
|
+
}));
|
|
76
|
+
return entries.map((entry) => {
|
|
77
|
+
const next = { ...entry.fields };
|
|
78
|
+
for (const key of expandKeys) {
|
|
79
|
+
const v = next[key];
|
|
80
|
+
if (typeof v === "string" && resolved.has(v)) {
|
|
81
|
+
next[key] = resolved.get(v);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
return { ...entry, fields: next };
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
function fingerprint(value) {
|
|
88
|
+
return JSON.stringify(value);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// src/cms/client.ts
|
|
92
|
+
var DEFAULT_BASE_PATH = "/trellis/store";
|
|
93
|
+
var DEFAULT_POLL_MS = 2000;
|
|
94
|
+
var MIN_POLL_MS = 500;
|
|
95
|
+
var MAX_FACTS_PER_FETCH = 5000;
|
|
96
|
+
|
|
97
|
+
class CmsClient {
|
|
98
|
+
url;
|
|
99
|
+
basePath;
|
|
100
|
+
directory;
|
|
101
|
+
pollIntervalMs;
|
|
102
|
+
fetchFn;
|
|
103
|
+
apiKey;
|
|
104
|
+
constructor(opts) {
|
|
105
|
+
this.url = opts.url.replace(/\/+$/, "");
|
|
106
|
+
this.basePath = (opts.basePath ?? DEFAULT_BASE_PATH).replace(/\/+$/, "");
|
|
107
|
+
this.directory = opts.directory;
|
|
108
|
+
this.pollIntervalMs = Math.max(MIN_POLL_MS, opts.pollIntervalMs ?? DEFAULT_POLL_MS);
|
|
109
|
+
this.fetchFn = opts.fetch ?? globalThis.fetch.bind(globalThis);
|
|
110
|
+
this.apiKey = opts.apiKey;
|
|
111
|
+
}
|
|
112
|
+
collection(key) {
|
|
113
|
+
return new CollectionRef(this, key);
|
|
114
|
+
}
|
|
115
|
+
entry(id) {
|
|
116
|
+
return new EntryRef(this, id);
|
|
117
|
+
}
|
|
118
|
+
async collections() {
|
|
119
|
+
const [entities, facts] = await Promise.all([
|
|
120
|
+
this._get("/entities?limit=10000"),
|
|
121
|
+
this._get(`/facts?limit=${MAX_FACTS_PER_FETCH}`)
|
|
122
|
+
]);
|
|
123
|
+
const factsByEntity = groupFactsByEntity(facts ?? []);
|
|
124
|
+
const counts = new Map;
|
|
125
|
+
const canonical = new Map;
|
|
126
|
+
for (const e of entities ?? []) {
|
|
127
|
+
const k = typeKey(e.type);
|
|
128
|
+
counts.set(k, (counts.get(k) ?? 0) + 1);
|
|
129
|
+
if (!canonical.has(k))
|
|
130
|
+
canonical.set(k, e.type);
|
|
131
|
+
}
|
|
132
|
+
const out = new Map;
|
|
133
|
+
for (const e of entities ?? []) {
|
|
134
|
+
if (e.type !== "TypeSchema")
|
|
135
|
+
continue;
|
|
136
|
+
const efacts = factsByEntity.get(e.id) ?? [];
|
|
137
|
+
const isCms = efacts.some((f) => f.a === "cms" && f.v === true);
|
|
138
|
+
if (!isCms)
|
|
139
|
+
continue;
|
|
140
|
+
const name = e.id.replace(/^schema:/, "");
|
|
141
|
+
const k = typeKey(name);
|
|
142
|
+
const labelFact = efacts.find((f) => f.a === "label");
|
|
143
|
+
const label = typeof labelFact?.v === "string" ? labelFact.v : name;
|
|
144
|
+
out.set(k, {
|
|
145
|
+
key: k,
|
|
146
|
+
label,
|
|
147
|
+
inferred: false,
|
|
148
|
+
count: counts.get(k) ?? 0
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
for (const [k, count] of counts) {
|
|
152
|
+
if (isSystemType(k) || out.has(k))
|
|
153
|
+
continue;
|
|
154
|
+
out.set(k, {
|
|
155
|
+
key: k,
|
|
156
|
+
label: canonical.get(k) ?? k,
|
|
157
|
+
inferred: true,
|
|
158
|
+
count
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
return [...out.values()].sort((a, b) => a.label.localeCompare(b.label));
|
|
162
|
+
}
|
|
163
|
+
close() {}
|
|
164
|
+
async _get(path) {
|
|
165
|
+
const u = new URL(`${this.basePath}${path}`, this.url);
|
|
166
|
+
if (this.directory && !u.searchParams.has("directory")) {
|
|
167
|
+
u.searchParams.set("directory", this.directory);
|
|
168
|
+
}
|
|
169
|
+
const res = await this.fetchFn(u.toString(), {
|
|
170
|
+
headers: this.apiKey ? { Authorization: `Bearer ${this.apiKey}` } : {}
|
|
171
|
+
});
|
|
172
|
+
if (!res.ok)
|
|
173
|
+
return;
|
|
174
|
+
return await res.json();
|
|
175
|
+
}
|
|
176
|
+
async _entryById(id) {
|
|
177
|
+
const detail = await this._get(`/entity/${encodeURIComponent(id)}`);
|
|
178
|
+
if (!detail)
|
|
179
|
+
return null;
|
|
180
|
+
const typeFact = detail.facts.find((f) => f.a === "type");
|
|
181
|
+
const type = typeof typeFact?.v === "string" ? typeFact.v : "unknown";
|
|
182
|
+
return entryFromFacts({ id: detail.id, type }, detail.facts);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
class CollectionRef {
|
|
187
|
+
client;
|
|
188
|
+
key;
|
|
189
|
+
constructor(client, key) {
|
|
190
|
+
this.client = client;
|
|
191
|
+
this.key = key;
|
|
192
|
+
}
|
|
193
|
+
async list(opts = {}) {
|
|
194
|
+
const status = opts.status ?? "published";
|
|
195
|
+
const limit = opts.limit ?? 100;
|
|
196
|
+
const [entities, facts] = await Promise.all([
|
|
197
|
+
this.client._get(`/entities?type=${encodeURIComponent(this.key)}&limit=${limit}`),
|
|
198
|
+
this.client._get(`/facts?limit=${MAX_FACTS_PER_FETCH}`)
|
|
199
|
+
]);
|
|
200
|
+
if (!entities || entities.length === 0)
|
|
201
|
+
return [];
|
|
202
|
+
const factsByEntity = groupFactsByEntity(facts ?? []);
|
|
203
|
+
let entries = entities.map((e) => entryFromFacts(e, factsByEntity.get(e.id) ?? []));
|
|
204
|
+
if (status !== "all") {
|
|
205
|
+
entries = entries.filter((e) => e.status === status);
|
|
206
|
+
}
|
|
207
|
+
if (opts.expand && opts.expand.length > 0) {
|
|
208
|
+
entries = await expandReferences(entries, opts.expand, (id) => this.client._entryById(id));
|
|
209
|
+
}
|
|
210
|
+
return entries;
|
|
211
|
+
}
|
|
212
|
+
async get(id, opts = {}) {
|
|
213
|
+
const entry = await this.client._entryById(id);
|
|
214
|
+
if (!entry)
|
|
215
|
+
return null;
|
|
216
|
+
if (opts.expand && opts.expand.length > 0) {
|
|
217
|
+
const [expanded] = await expandReferences([entry], opts.expand, (eid) => this.client._entryById(eid));
|
|
218
|
+
return expanded;
|
|
219
|
+
}
|
|
220
|
+
return entry;
|
|
221
|
+
}
|
|
222
|
+
subscribe(callback, opts = {}) {
|
|
223
|
+
let stopped = false;
|
|
224
|
+
let last = "";
|
|
225
|
+
const tick = async () => {
|
|
226
|
+
if (stopped)
|
|
227
|
+
return;
|
|
228
|
+
try {
|
|
229
|
+
const entries = await this.list(opts);
|
|
230
|
+
const sig = fingerprint(entries);
|
|
231
|
+
if (sig !== last) {
|
|
232
|
+
last = sig;
|
|
233
|
+
callback(entries);
|
|
234
|
+
}
|
|
235
|
+
} catch {}
|
|
236
|
+
};
|
|
237
|
+
tick();
|
|
238
|
+
const interval = setInterval(tick, this.client.pollIntervalMs);
|
|
239
|
+
return () => {
|
|
240
|
+
stopped = true;
|
|
241
|
+
clearInterval(interval);
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
class EntryRef {
|
|
247
|
+
client;
|
|
248
|
+
id;
|
|
249
|
+
constructor(client, id) {
|
|
250
|
+
this.client = client;
|
|
251
|
+
this.id = id;
|
|
252
|
+
}
|
|
253
|
+
async get(opts = {}) {
|
|
254
|
+
const entry = await this.client._entryById(this.id);
|
|
255
|
+
if (!entry)
|
|
256
|
+
return null;
|
|
257
|
+
if (opts.expand && opts.expand.length > 0) {
|
|
258
|
+
const [expanded] = await expandReferences([entry], opts.expand, (eid) => this.client._entryById(eid));
|
|
259
|
+
return expanded;
|
|
260
|
+
}
|
|
261
|
+
return entry;
|
|
262
|
+
}
|
|
263
|
+
subscribe(callback, opts = {}) {
|
|
264
|
+
let stopped = false;
|
|
265
|
+
let last = "";
|
|
266
|
+
const tick = async () => {
|
|
267
|
+
if (stopped)
|
|
268
|
+
return;
|
|
269
|
+
try {
|
|
270
|
+
const entry = await this.get(opts);
|
|
271
|
+
const sig = fingerprint(entry);
|
|
272
|
+
if (sig !== last) {
|
|
273
|
+
last = sig;
|
|
274
|
+
callback(entry);
|
|
275
|
+
}
|
|
276
|
+
} catch {}
|
|
277
|
+
};
|
|
278
|
+
tick();
|
|
279
|
+
const interval = setInterval(tick, this.client.pollIntervalMs);
|
|
280
|
+
return () => {
|
|
281
|
+
stopped = true;
|
|
282
|
+
clearInterval(interval);
|
|
283
|
+
};
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
function createCmsClient(opts) {
|
|
287
|
+
return new CmsClient(opts);
|
|
288
|
+
}
|
|
289
|
+
// src/cms/scaffold.ts
|
|
290
|
+
var DEFAULT_URL = "http://localhost:4096";
|
|
291
|
+
function expandLiteral(expand) {
|
|
292
|
+
if (!expand || expand.length === 0)
|
|
293
|
+
return "";
|
|
294
|
+
return ` expand: ${JSON.stringify(expand)},`;
|
|
295
|
+
}
|
|
296
|
+
function vanilla(opts) {
|
|
297
|
+
const url = opts.url ?? DEFAULT_URL;
|
|
298
|
+
const exp = expandLiteral(opts.expand);
|
|
299
|
+
return `import { createCmsClient } from "trellis/cms";
|
|
300
|
+
|
|
301
|
+
const cms = createCmsClient({ url: "${url}" });
|
|
302
|
+
|
|
303
|
+
const collection = cms.collection("${opts.collection}");
|
|
304
|
+
|
|
305
|
+
// One-shot fetch (defaults to status: "published")
|
|
306
|
+
const entries = await collection.list({${exp}});
|
|
307
|
+
console.log(entries);
|
|
308
|
+
|
|
309
|
+
// Live updates \u2014 re-fires whenever the collection changes
|
|
310
|
+
const off = collection.subscribe(
|
|
311
|
+
(entries) => {
|
|
312
|
+
console.log("Updated:", entries);
|
|
313
|
+
},
|
|
314
|
+
{${exp}},
|
|
315
|
+
);
|
|
316
|
+
// off(); // call to stop receiving updates
|
|
317
|
+
`;
|
|
318
|
+
}
|
|
319
|
+
function react(opts) {
|
|
320
|
+
const url = opts.url ?? DEFAULT_URL;
|
|
321
|
+
const exp = expandLiteral(opts.expand);
|
|
322
|
+
return `import { useEffect, useState } from "react";
|
|
323
|
+
import { createCmsClient, type Entry } from "trellis/cms";
|
|
324
|
+
|
|
325
|
+
const cms = createCmsClient({ url: "${url}" });
|
|
326
|
+
|
|
327
|
+
export function use${pascal(opts.collection)}() {
|
|
328
|
+
const [entries, setEntries] = useState<Entry[]>([]);
|
|
329
|
+
useEffect(() => {
|
|
330
|
+
const off = cms.collection("${opts.collection}").subscribe(setEntries, {${exp}});
|
|
331
|
+
return off;
|
|
332
|
+
}, []);
|
|
333
|
+
return entries;
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
// Usage:
|
|
337
|
+
// const posts = use${pascal(opts.collection)}();
|
|
338
|
+
// return posts.map(p => <article key={p.id}>...</article>);
|
|
339
|
+
`;
|
|
340
|
+
}
|
|
341
|
+
function solid(opts) {
|
|
342
|
+
const url = opts.url ?? DEFAULT_URL;
|
|
343
|
+
const exp = expandLiteral(opts.expand);
|
|
344
|
+
return `import { createSignal, onCleanup } from "solid-js";
|
|
345
|
+
import { createCmsClient, type Entry } from "trellis/cms";
|
|
346
|
+
|
|
347
|
+
const cms = createCmsClient({ url: "${url}" });
|
|
348
|
+
|
|
349
|
+
export function create${pascal(opts.collection)}() {
|
|
350
|
+
const [entries, setEntries] = createSignal<Entry[]>([]);
|
|
351
|
+
const off = cms.collection("${opts.collection}").subscribe(setEntries, {${exp}});
|
|
352
|
+
onCleanup(off);
|
|
353
|
+
return entries;
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
// Usage in a component:
|
|
357
|
+
// const posts = create${pascal(opts.collection)}();
|
|
358
|
+
// return <For each={posts()}>{(p) => <article>...</article>}</For>;
|
|
359
|
+
`;
|
|
360
|
+
}
|
|
361
|
+
function vue(opts) {
|
|
362
|
+
const url = opts.url ?? DEFAULT_URL;
|
|
363
|
+
const exp = expandLiteral(opts.expand);
|
|
364
|
+
return `import { ref, onUnmounted } from "vue";
|
|
365
|
+
import { createCmsClient, type Entry } from "trellis/cms";
|
|
366
|
+
|
|
367
|
+
const cms = createCmsClient({ url: "${url}" });
|
|
368
|
+
|
|
369
|
+
export function use${pascal(opts.collection)}() {
|
|
370
|
+
const entries = ref<Entry[]>([]);
|
|
371
|
+
const off = cms.collection("${opts.collection}").subscribe((next) => {
|
|
372
|
+
entries.value = next;
|
|
373
|
+
}, {${exp}});
|
|
374
|
+
onUnmounted(off);
|
|
375
|
+
return entries;
|
|
376
|
+
}
|
|
377
|
+
`;
|
|
378
|
+
}
|
|
379
|
+
function pascal(s) {
|
|
380
|
+
return s.split(/[_\-\s]+/).filter(Boolean).map((w) => w[0].toUpperCase() + w.slice(1)).join("");
|
|
381
|
+
}
|
|
382
|
+
function scaffoldConsumer(opts) {
|
|
383
|
+
switch (opts.framework ?? "vanilla") {
|
|
384
|
+
case "react":
|
|
385
|
+
return react(opts);
|
|
386
|
+
case "solid":
|
|
387
|
+
return solid(opts);
|
|
388
|
+
case "vue":
|
|
389
|
+
return vue(opts);
|
|
390
|
+
default:
|
|
391
|
+
return vanilla(opts);
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
function scaffoldFilename(opts) {
|
|
395
|
+
const base = `cms-${opts.collection.replace(/[^a-z0-9]+/gi, "-").toLowerCase()}`;
|
|
396
|
+
switch (opts.framework ?? "vanilla") {
|
|
397
|
+
case "react":
|
|
398
|
+
case "solid":
|
|
399
|
+
return `${base}.ts`;
|
|
400
|
+
case "vue":
|
|
401
|
+
return `${base}.ts`;
|
|
402
|
+
default:
|
|
403
|
+
return `${base}.js`;
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
export {
|
|
407
|
+
scaffoldFilename,
|
|
408
|
+
scaffoldConsumer,
|
|
409
|
+
createCmsClient,
|
|
410
|
+
EntryRef,
|
|
411
|
+
CollectionRef,
|
|
412
|
+
CmsClient
|
|
413
|
+
};
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Trellis CMS — internal helpers (fact join, status detection, reference expansion).
|
|
3
|
+
*/
|
|
4
|
+
import type { Entry } from './types.js';
|
|
5
|
+
export type RawFact = {
|
|
6
|
+
e: string;
|
|
7
|
+
a: string;
|
|
8
|
+
v: string | number | boolean;
|
|
9
|
+
};
|
|
10
|
+
export type RawEntity = {
|
|
11
|
+
id: string;
|
|
12
|
+
type: string;
|
|
13
|
+
};
|
|
14
|
+
export declare function isSystemType(type: string): boolean;
|
|
15
|
+
export declare function typeKey(type: string): string;
|
|
16
|
+
export declare function entryFromFacts(entity: RawEntity, facts: RawFact[]): Entry;
|
|
17
|
+
export declare function groupFactsByEntity(facts: RawFact[]): Map<string, RawFact[]>;
|
|
18
|
+
/**
|
|
19
|
+
* For each entry, replace string ids in `expand` field keys with the resolved Entry.
|
|
20
|
+
* Lookups are batched in parallel.
|
|
21
|
+
*/
|
|
22
|
+
export declare function expandReferences(entries: Entry[], expandKeys: string[], fetchEntity: (id: string) => Promise<Entry | null>): Promise<Entry[]>;
|
|
23
|
+
/**
|
|
24
|
+
* Cheap deep-equality fingerprint for change detection in subscriptions.
|
|
25
|
+
*/
|
|
26
|
+
export declare function fingerprint(value: unknown): string;
|
|
27
|
+
//# sourceMappingURL=internal.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"internal.d.ts","sourceRoot":"","sources":["../../src/cms/internal.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,KAAK,EAAe,MAAM,YAAY,CAAC;AAErD,MAAM,MAAM,OAAO,GAAG;IAAE,CAAC,EAAE,MAAM,CAAC;IAAC,CAAC,EAAE,MAAM,CAAC;IAAC,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,CAAA;CAAE,CAAC;AAC7E,MAAM,MAAM,SAAS,GAAG;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC;AAwBrD,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAElD;AAED,wBAAgB,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAE5C;AAED,wBAAgB,cAAc,CAAC,MAAM,EAAE,SAAS,EAAE,KAAK,EAAE,OAAO,EAAE,GAAG,KAAK,CAYzE;AAED,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,OAAO,EAAE,GAAG,GAAG,CAAC,MAAM,EAAE,OAAO,EAAE,CAAC,CAQ3E;AAED;;;GAGG;AACH,wBAAsB,gBAAgB,CACpC,OAAO,EAAE,KAAK,EAAE,EAChB,UAAU,EAAE,MAAM,EAAE,EACpB,WAAW,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,OAAO,CAAC,KAAK,GAAG,IAAI,CAAC,GACjD,OAAO,CAAC,KAAK,EAAE,CAAC,CA+BlB;AAED;;GAEG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAElD"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Scaffold generators for CMS consumer code.
|
|
3
|
+
*
|
|
4
|
+
* Used by the agent's `cms` tool (scaffold_consumer action) to write
|
|
5
|
+
* starter integration files instead of generating static data dumps.
|
|
6
|
+
*/
|
|
7
|
+
import type { Framework } from './types.js';
|
|
8
|
+
export type ScaffoldOptions = {
|
|
9
|
+
collection: string;
|
|
10
|
+
framework?: Framework;
|
|
11
|
+
/** Field keys to expand as references (e.g. ["author"]). */
|
|
12
|
+
expand?: string[];
|
|
13
|
+
/** Override the CMS server URL. Default: http://localhost:4096 */
|
|
14
|
+
url?: string;
|
|
15
|
+
};
|
|
16
|
+
export declare function scaffoldConsumer(opts: ScaffoldOptions): string;
|
|
17
|
+
export declare function scaffoldFilename(opts: ScaffoldOptions): string;
|
|
18
|
+
//# sourceMappingURL=scaffold.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scaffold.d.ts","sourceRoot":"","sources":["../../src/cms/scaffold.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAE5C,MAAM,MAAM,eAAe,GAAG;IAC5B,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,SAAS,CAAC;IACtB,4DAA4D;IAC5D,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;IAClB,kEAAkE;IAClE,GAAG,CAAC,EAAE,MAAM,CAAC;CACd,CAAC;AAwGF,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,eAAe,GAAG,MAAM,CAW9D;AAED,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,eAAe,GAAG,MAAM,CAW9D"}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Trellis CMS Client — public types
|
|
3
|
+
*
|
|
4
|
+
* @module trellis/cms
|
|
5
|
+
*/
|
|
6
|
+
export type EntryStatus = 'draft' | 'published';
|
|
7
|
+
export type Entry<T extends Record<string, unknown> = Record<string, unknown>> = {
|
|
8
|
+
/** Stable entity id (e.g. "blog_post:abc123" or "BlogPost:abc"). */
|
|
9
|
+
id: string;
|
|
10
|
+
/** Original type string as stored on the entity (e.g. "BlogPost"). */
|
|
11
|
+
type: string;
|
|
12
|
+
/** "draft" or "published". Missing cms_status fact is treated as "draft". */
|
|
13
|
+
status: EntryStatus;
|
|
14
|
+
/** All other facts as a flat field bag. Reference fields hold entity ids until expanded. */
|
|
15
|
+
fields: T;
|
|
16
|
+
};
|
|
17
|
+
export type ListOptions = {
|
|
18
|
+
/** Filter by status. Default: "published". Use "all" to include drafts. */
|
|
19
|
+
status?: 'all' | EntryStatus;
|
|
20
|
+
/** Field keys to resolve as references — string ids become nested Entry objects. */
|
|
21
|
+
expand?: string[];
|
|
22
|
+
/** Max results. Default: 100. */
|
|
23
|
+
limit?: number;
|
|
24
|
+
};
|
|
25
|
+
export type GetOptions = {
|
|
26
|
+
expand?: string[];
|
|
27
|
+
};
|
|
28
|
+
export type Unsubscribe = () => void;
|
|
29
|
+
export type ListSubscriber<T extends Record<string, unknown> = Record<string, unknown>> = (entries: Entry<T>[]) => void;
|
|
30
|
+
export type EntrySubscriber<T extends Record<string, unknown> = Record<string, unknown>> = (entry: Entry<T> | null) => void;
|
|
31
|
+
export type Collection = {
|
|
32
|
+
/** Normalized lowercase key (e.g. "blog_post"). */
|
|
33
|
+
key: string;
|
|
34
|
+
/** Human-readable label. */
|
|
35
|
+
label: string;
|
|
36
|
+
/** True if no explicit TypeSchema exists — schema is inferred from existing entries. */
|
|
37
|
+
inferred: boolean;
|
|
38
|
+
/** Total entry count (all statuses). */
|
|
39
|
+
count: number;
|
|
40
|
+
};
|
|
41
|
+
export type CmsClientOptions = {
|
|
42
|
+
/** Base URL of the Trellis-compatible HTTP server (e.g. opencode at "http://localhost:4096"). */
|
|
43
|
+
url: string;
|
|
44
|
+
/**
|
|
45
|
+
* Path prefix for store routes. Default: "/trellis/store" (matches opencode).
|
|
46
|
+
* Override only if pointing at a different server layout.
|
|
47
|
+
*/
|
|
48
|
+
basePath?: string;
|
|
49
|
+
/**
|
|
50
|
+
* Project directory for multi-instance backends (opencode requires this).
|
|
51
|
+
* If omitted, the request goes to the default instance.
|
|
52
|
+
*/
|
|
53
|
+
directory?: string;
|
|
54
|
+
/** Polling interval in ms for subscribe(). Minimum 500ms. Default: 2000. */
|
|
55
|
+
pollIntervalMs?: number;
|
|
56
|
+
/** Custom fetch implementation (for SSR / Node environments without global fetch). */
|
|
57
|
+
fetch?: typeof fetch;
|
|
58
|
+
/** Optional bearer token for authenticated routes. */
|
|
59
|
+
apiKey?: string;
|
|
60
|
+
};
|
|
61
|
+
export type Framework = 'vanilla' | 'react' | 'solid' | 'vue';
|
|
62
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/cms/types.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,MAAM,MAAM,WAAW,GAAG,OAAO,GAAG,WAAW,CAAC;AAEhD,MAAM,MAAM,KAAK,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,IAAI;IAC/E,oEAAoE;IACpE,EAAE,EAAE,MAAM,CAAC;IACX,sEAAsE;IACtE,IAAI,EAAE,MAAM,CAAC;IACb,6EAA6E;IAC7E,MAAM,EAAE,WAAW,CAAC;IACpB,4FAA4F;IAC5F,MAAM,EAAE,CAAC,CAAC;CACX,CAAC;AAEF,MAAM,MAAM,WAAW,GAAG;IACxB,2EAA2E;IAC3E,MAAM,CAAC,EAAE,KAAK,GAAG,WAAW,CAAC;IAC7B,oFAAoF;IACpF,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;IAClB,iCAAiC;IACjC,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,CAAC;AAEF,MAAM,MAAM,UAAU,GAAG;IACvB,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;CACnB,CAAC;AAEF,MAAM,MAAM,WAAW,GAAG,MAAM,IAAI,CAAC;AACrC,MAAM,MAAM,cAAc,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,IAAI,CACxF,OAAO,EAAE,KAAK,CAAC,CAAC,CAAC,EAAE,KAChB,IAAI,CAAC;AACV,MAAM,MAAM,eAAe,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,IAAI,CACzF,KAAK,EAAE,KAAK,CAAC,CAAC,CAAC,GAAG,IAAI,KACnB,IAAI,CAAC;AAEV,MAAM,MAAM,UAAU,GAAG;IACvB,mDAAmD;IACnD,GAAG,EAAE,MAAM,CAAC;IACZ,4BAA4B;IAC5B,KAAK,EAAE,MAAM,CAAC;IACd,wFAAwF;IACxF,QAAQ,EAAE,OAAO,CAAC;IAClB,wCAAwC;IACxC,KAAK,EAAE,MAAM,CAAC;CACf,CAAC;AAEF,MAAM,MAAM,gBAAgB,GAAG;IAC7B,iGAAiG;IACjG,GAAG,EAAE,MAAM,CAAC;IACZ;;;OAGG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB;;;OAGG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,4EAA4E;IAC5E,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,sFAAsF;IACtF,KAAK,CAAC,EAAE,OAAO,KAAK,CAAC;IACrB,sDAAsD;IACtD,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB,CAAC;AAEF,MAAM,MAAM,SAAS,GAAG,SAAS,GAAG,OAAO,GAAG,OAAO,GAAG,KAAK,CAAC"}
|
package/dist/db/index.js
CHANGED
|
@@ -1,14 +1,5 @@
|
|
|
1
1
|
// @bun
|
|
2
2
|
import"../index-c9h37r6h.js";
|
|
3
|
-
import {
|
|
4
|
-
importFile,
|
|
5
|
-
importRecords
|
|
6
|
-
} from "../index-skhn0agf.js";
|
|
7
|
-
import {
|
|
8
|
-
deploy
|
|
9
|
-
} from "../index-wt8rz4gn.js";
|
|
10
|
-
import"../index-bmyt7k8n.js";
|
|
11
|
-
import"../index-y6a4kj0p.js";
|
|
12
3
|
import {
|
|
13
4
|
ADMIN_ONLY,
|
|
14
5
|
ANONYMOUS,
|
|
@@ -27,6 +18,15 @@ import {
|
|
|
27
18
|
startServer,
|
|
28
19
|
verifyJwt
|
|
29
20
|
} from "../index-6n5dcebj.js";
|
|
21
|
+
import {
|
|
22
|
+
importFile,
|
|
23
|
+
importRecords
|
|
24
|
+
} from "../index-skhn0agf.js";
|
|
25
|
+
import {
|
|
26
|
+
deploy
|
|
27
|
+
} from "../index-wt8rz4gn.js";
|
|
28
|
+
import"../index-bmyt7k8n.js";
|
|
29
|
+
import"../index-y6a4kj0p.js";
|
|
30
30
|
import"../index-n9f2qyh5.js";
|
|
31
31
|
import"../index-k5b0xskw.js";
|
|
32
32
|
import {
|
package/dist/server/index.js
CHANGED
|
@@ -1,5 +1,23 @@
|
|
|
1
1
|
// @bun
|
|
2
2
|
import"../index-c9h37r6h.js";
|
|
3
|
+
import {
|
|
4
|
+
ADMIN_ONLY,
|
|
5
|
+
ANONYMOUS,
|
|
6
|
+
FULLY_PUBLIC,
|
|
7
|
+
GITHUB_PROVIDER,
|
|
8
|
+
GOOGLE_PROVIDER,
|
|
9
|
+
OWNER_ONLY,
|
|
10
|
+
PUBLIC_READ,
|
|
11
|
+
PermissionError,
|
|
12
|
+
PermissionRegistry,
|
|
13
|
+
SubscriptionManager,
|
|
14
|
+
buildOAuthUrl,
|
|
15
|
+
exchangeOAuthCode,
|
|
16
|
+
resolveAuth,
|
|
17
|
+
signJwt,
|
|
18
|
+
startServer,
|
|
19
|
+
verifyJwt
|
|
20
|
+
} from "../index-6n5dcebj.js";
|
|
3
21
|
import {
|
|
4
22
|
importFile,
|
|
5
23
|
importRecords
|
|
@@ -24,24 +42,6 @@ import {
|
|
|
24
42
|
runSpriteCopy,
|
|
25
43
|
runSpriteInteractive
|
|
26
44
|
} from "../index-y6a4kj0p.js";
|
|
27
|
-
import {
|
|
28
|
-
ADMIN_ONLY,
|
|
29
|
-
ANONYMOUS,
|
|
30
|
-
FULLY_PUBLIC,
|
|
31
|
-
GITHUB_PROVIDER,
|
|
32
|
-
GOOGLE_PROVIDER,
|
|
33
|
-
OWNER_ONLY,
|
|
34
|
-
PUBLIC_READ,
|
|
35
|
-
PermissionError,
|
|
36
|
-
PermissionRegistry,
|
|
37
|
-
SubscriptionManager,
|
|
38
|
-
buildOAuthUrl,
|
|
39
|
-
exchangeOAuthCode,
|
|
40
|
-
resolveAuth,
|
|
41
|
-
signJwt,
|
|
42
|
-
startServer,
|
|
43
|
-
verifyJwt
|
|
44
|
-
} from "../index-6n5dcebj.js";
|
|
45
45
|
import"../index-n9f2qyh5.js";
|
|
46
46
|
import"../index-xzym9w0m.js";
|
|
47
47
|
import {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "trellis",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.1.1",
|
|
4
4
|
"description": "Agentic State Engine — event-sourced causal graph with branching, decision traces, and realtime sync for AI-native applications",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -59,6 +59,11 @@
|
|
|
59
59
|
"bun": "./dist/links/index.js",
|
|
60
60
|
"import": "./dist/links/index.js"
|
|
61
61
|
},
|
|
62
|
+
"./cms": {
|
|
63
|
+
"types": "./dist/cms/index.d.ts",
|
|
64
|
+
"bun": "./dist/cms/index.js",
|
|
65
|
+
"import": "./dist/cms/index.js"
|
|
66
|
+
},
|
|
62
67
|
"./decisions": {
|
|
63
68
|
"types": "./dist/decisions/index.d.ts",
|
|
64
69
|
"bun": "./dist/decisions/index.js",
|
|
@@ -101,7 +106,7 @@
|
|
|
101
106
|
"cli": "bun run src/cli/index.ts",
|
|
102
107
|
"mcp": "bun run src/mcp/index.ts",
|
|
103
108
|
"mcp:docs": "bun run src/mcp/docs.ts",
|
|
104
|
-
"build": "bun build src/index.ts src/core/index.ts src/vcs/index.ts src/embeddings/index.ts src/links/index.ts src/decisions/index.ts src/server/index.ts src/client/index.ts src/react/index.ts src/db/index.ts src/cli/index.ts --outdir dist --target bun --splitting --format esm --root src --external @xenova/transformers --external @huggingface/transformers --external react && mkdir -p dist/ui && cp src/ui/client.html dist/ui/client.html && tsc -p tsconfig.build.json --emitDeclarationOnly --noEmit false --noEmitOnError false && bun run build:inspector",
|
|
109
|
+
"build": "bun build src/index.ts src/core/index.ts src/vcs/index.ts src/embeddings/index.ts src/links/index.ts src/decisions/index.ts src/server/index.ts src/client/index.ts src/react/index.ts src/db/index.ts src/cli/index.ts src/cms/index.ts --outdir dist --target bun --splitting --format esm --root src --external @xenova/transformers --external @huggingface/transformers --external react && mkdir -p dist/ui && cp src/ui/client.html dist/ui/client.html && tsc -p tsconfig.build.json --emitDeclarationOnly --noEmit false --noEmitOnError false && bun run build:inspector",
|
|
105
110
|
"build:inspector": "vite build --config vite.inspector.config.ts",
|
|
106
111
|
"test": "bun test test/core test/vcs test/git test/p2 test/p3 test/p4 test/p5 test/p6 test/p7 test/engine.test.ts",
|
|
107
112
|
"test:all": "bun test",
|