@malloy-publisher/create-malloy-package 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/LICENSE +21 -0
- package/README.md +347 -0
- package/dist/index.js +1869 -0
- package/package.json +62 -0
- package/templates/AGENTS.md +192 -0
- package/templates/model.custom.malloy +16 -0
- package/templates/model.default.malloy +44 -0
- package/templates/sales.csv +11 -0
package/package.json
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@malloy-publisher/create-malloy-package",
|
|
3
|
+
"description": "Scaffold a Malloy Publisher package and a local agent workspace, so one command takes you from nothing to an agent that can query your data.",
|
|
4
|
+
"version": "0.0.1",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"engines": {
|
|
8
|
+
"node": ">=20"
|
|
9
|
+
},
|
|
10
|
+
"bin": {
|
|
11
|
+
"create-malloy-package": "./dist/index.js"
|
|
12
|
+
},
|
|
13
|
+
"files": [
|
|
14
|
+
"dist",
|
|
15
|
+
"templates"
|
|
16
|
+
],
|
|
17
|
+
"keywords": [
|
|
18
|
+
"malloy",
|
|
19
|
+
"publisher",
|
|
20
|
+
"scaffold",
|
|
21
|
+
"create",
|
|
22
|
+
"agent",
|
|
23
|
+
"skills"
|
|
24
|
+
],
|
|
25
|
+
"repository": {
|
|
26
|
+
"type": "git",
|
|
27
|
+
"url": "https://github.com/malloydata/publisher.git",
|
|
28
|
+
"directory": "packages/create-malloy-package"
|
|
29
|
+
},
|
|
30
|
+
"publishConfig": {
|
|
31
|
+
"access": "public"
|
|
32
|
+
},
|
|
33
|
+
"scripts": {
|
|
34
|
+
"build": "bun build src/index.ts --outdir dist --target node --format esm --packages external && node -e \"require('fs').chmodSync('dist/index.js', 0o755)\"",
|
|
35
|
+
"prepack": "bun run build",
|
|
36
|
+
"prepublishOnly": "bun run scripts/check-pack.ts",
|
|
37
|
+
"check-pack": "bun run build && bun run scripts/check-pack.ts",
|
|
38
|
+
"pretypecheck": "cd ../skills && bun run build",
|
|
39
|
+
"typecheck": "bunx tsc --noEmit",
|
|
40
|
+
"lint": "bunx eslint ./src ./scripts --fix",
|
|
41
|
+
"format": "bunx prettier --write --parser typescript '{src,scripts,tests}/**/*.ts'",
|
|
42
|
+
"pretest": "cd ../skills && bun run build",
|
|
43
|
+
"test": "bun test src",
|
|
44
|
+
"pretest:e2e": "cd ../skills && bun run build",
|
|
45
|
+
"test:e2e": "bun test --timeout 180000 tests/e2e"
|
|
46
|
+
},
|
|
47
|
+
"dependencies": {
|
|
48
|
+
"@malloy-publisher/skills": "^0.1.0",
|
|
49
|
+
"commander": "^12.1.0"
|
|
50
|
+
},
|
|
51
|
+
"devDependencies": {
|
|
52
|
+
"@eslint/js": "^8.57.0",
|
|
53
|
+
"@malloydata/malloy": "^0.0.422",
|
|
54
|
+
"@modelcontextprotocol/sdk": "^1.13.2",
|
|
55
|
+
"@types/bun": "^1.2.21",
|
|
56
|
+
"@types/node": "^24.10.0",
|
|
57
|
+
"@typescript-eslint/eslint-plugin": "^8.16.0",
|
|
58
|
+
"@typescript-eslint/parser": "^8.16.0",
|
|
59
|
+
"eslint": "^8.50.0",
|
|
60
|
+
"typescript": "^5.3.3"
|
|
61
|
+
}
|
|
62
|
+
}
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
# {{title}}
|
|
2
|
+
|
|
3
|
+
This directory is a [Malloy Publisher](https://github.com/malloydata/publisher)
|
|
4
|
+
workspace. It serves Malloy semantic models over a web UI, a REST API, and an MCP
|
|
5
|
+
endpoint. If you are an AI agent working here, this is what you can do and how to
|
|
6
|
+
start.
|
|
7
|
+
|
|
8
|
+
## Start the server first
|
|
9
|
+
|
|
10
|
+
Everything below talks to a running server, so start it before anything else:
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
{{startCommand}}
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
That is meant to run Publisher on http://localhost:{{port}} (web UI and REST) with the
|
|
17
|
+
MCP endpoint on http://localhost:{{mcpPort}}/mcp, and to mount this workspace's
|
|
18
|
+
packages in watch mode so model edits recompile. All three of those come from flags in
|
|
19
|
+
the command above (`--port`, `--mcp_port`, `--watch-env`), and where that command is an
|
|
20
|
+
`npm` script, from the script `package.json` runs for it. Read them there before you
|
|
21
|
+
trust anything below: different ports move every URL in this file, a `--server_root` or
|
|
22
|
+
`--config` pointing outside this directory serves a different workspace entirely, and
|
|
23
|
+
no `--watch-env` means an edit on disk is not picked up at all until you reload the
|
|
24
|
+
package. The server settles all of it on boot, printing
|
|
25
|
+
`Publisher server listening at http://<address>:<port>` and a
|
|
26
|
+
`MCP server listening at ...` line beside it, so read those two lines rather than
|
|
27
|
+
taking this paragraph's word for it.
|
|
28
|
+
|
|
29
|
+
Where watch mode is on, a recompile that *fails* fails quietly, and not always in the
|
|
30
|
+
same way. It may leave the last version that compiled serving, so a query keeps
|
|
31
|
+
returning the old model's rows; it may instead make the source vanish, so every query
|
|
32
|
+
fails with `Reference to undefined object '<source>'`. Either way the failure goes only
|
|
33
|
+
to the server's stdout, which you are not reading if you started it in the background.
|
|
34
|
+
So a query that succeeds after an edit is not proof the edit compiled, and the rows you
|
|
35
|
+
get back may be the old model's. Compile-check the edit with `malloy_compile`, or
|
|
36
|
+
reload with `malloy_reloadPackage` afterwards (both described below); either one
|
|
37
|
+
reports the failure that watch mode swallowed.
|
|
38
|
+
|
|
39
|
+
Poll until it reports serving rather than assuming a fixed wait; the first run
|
|
40
|
+
downloads the server, so it can take a minute:
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
curl -s http://localhost:{{port}}/api/v0/status
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
Read two things out of that response before you trust it, because any Publisher
|
|
47
|
+
holding this port answers it the same way:
|
|
48
|
+
|
|
49
|
+
- `operationalState` is `serving`, and
|
|
50
|
+
- the environment's `location` is a path inside this workspace directory, and its
|
|
51
|
+
`packages` list contains the package you are working on.
|
|
52
|
+
|
|
53
|
+
If the location points somewhere else, the ports were already taken. {{port}} and
|
|
54
|
+
{{mcpPort}} are Publisher's defaults, so another workspace's server may hold them.
|
|
55
|
+
The server exits when it cannot bind, but the last lines on screen are the green
|
|
56
|
+
`Service marked as ready` and `Environment store successfully initialized` from the
|
|
57
|
+
startup work that did finish; the `EADDRINUSE` line is further up. Both workspaces
|
|
58
|
+
also name their environment `default`, so nothing in the response looks wrong except
|
|
59
|
+
that path. Scroll up for `EADDRINUSE`, then either stop the other server or move
|
|
60
|
+
this one onto free ports:
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
{{portOverrideCommand}}
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
Every URL below then uses the port from that command in place of {{port}}. The MCP
|
|
67
|
+
endpoint moves with it, which also means editing the `url` in this workspace's MCP
|
|
68
|
+
config and reconnecting, so prefer stopping the other server when you can. That second
|
|
69
|
+
pair of ports is another guess at a free pair, not a checked answer, so run the same
|
|
70
|
+
`location` and `packages` check above against the new port before you trust it.
|
|
71
|
+
|
|
72
|
+
The two flags belong to that one boot and nothing remembers them. Every later boot of
|
|
73
|
+
this workspace needs them spelled out again, in the same shape as the command above:
|
|
74
|
+
that includes the reset command further down, which without them goes back to
|
|
75
|
+
{{port}} and {{mcpPort}} and collides with the same server all over again.
|
|
76
|
+
|
|
77
|
+
New ports only help against a server on a *different* workspace. Two servers cannot
|
|
78
|
+
share this one: the second binds its free ports and then sits at `initializing`
|
|
79
|
+
forever, because the first holds the lock on `publisher.db`. One server per
|
|
80
|
+
workspace, always.
|
|
81
|
+
|
|
82
|
+
Before you trust the endpoint to be private, read the `--host` in the command you
|
|
83
|
+
are actually running, which is the start block above (and, where that block is an
|
|
84
|
+
`npm` script, the script `package.json` runs for it). Neither the REST API nor the
|
|
85
|
+
MCP endpoint authenticates anything, so that bind address is the only thing between
|
|
86
|
+
this data and the rest of the network. `--host 127.0.0.1`, written with a space
|
|
87
|
+
before the address, binds loopback: this machine and nothing else. Publisher's own
|
|
88
|
+
default is `0.0.0.0`, so a command with no `--host` at all, or one that names
|
|
89
|
+
`0.0.0.0` or a LAN address, is serving every machine on the network. To do that
|
|
90
|
+
deliberately, put something that authenticates in front of it first.
|
|
91
|
+
|
|
92
|
+
Then check that command against what the server itself reports, because the command
|
|
93
|
+
is not the last word. On boot it prints
|
|
94
|
+
`Publisher server listening at http://<address>:<port>` and
|
|
95
|
+
`MCP server listening at ...`, and those two lines are the addresses it really bound.
|
|
96
|
+
An unknown flag is accepted without a word of complaint, so a mistyped `--hostt` is
|
|
97
|
+
dropped in silence and the bind falls back to Publisher's own `0.0.0.0` while the
|
|
98
|
+
command still reads as loopback. `--host=127.0.0.1` fails in exactly that way and is
|
|
99
|
+
the easier one to write by accident: Publisher reads `--host` and its address as two
|
|
100
|
+
separate arguments, so the joined `=` form matches no flag it knows, is dropped
|
|
101
|
+
without a word, and the bind falls back to `0.0.0.0`. It is the same log you scroll
|
|
102
|
+
through hunting `EADDRINUSE`, so read the listening lines while you are in there.
|
|
103
|
+
|
|
104
|
+
One more restart trap: if a package is added to `publisher.config.json` after the
|
|
105
|
+
server has booted once, a plain restart will not serve it. Publisher reads that file
|
|
106
|
+
only while `publisher_data/` holds no database; from the second boot on its own
|
|
107
|
+
database wins, so the new package is registered, reported by the scaffolder, and
|
|
108
|
+
never mounted, with nothing in the log to say why. Boot once with the reset command,
|
|
109
|
+
which is the start command above plus `--init`, to rebuild the persisted state from
|
|
110
|
+
the config, then go back to the normal start:
|
|
111
|
+
|
|
112
|
+
```bash
|
|
113
|
+
{{resetCommand}}
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
Stop the running server first. It wants the ports and the `publisher.db` the live
|
|
117
|
+
one already holds, so with the server up it fails twice over: `EADDRINUSE` on both
|
|
118
|
+
ports, then `Could not set lock on file "publisher.db"` (which names the PID holding
|
|
119
|
+
it). If you moved the ports above, add the same `--port` and `--mcp_port` flags to
|
|
120
|
+
this command as well, spelled exactly the way the port-override command above spells
|
|
121
|
+
them, `--` separator and all where that command carries one; nothing carries them
|
|
122
|
+
over for you. Note also that `--init` clears persisted runtime state, so materialized
|
|
123
|
+
tables and saved themes go with it; it rebuilds from `publisher.config.json`, never
|
|
124
|
+
from the database it replaces.
|
|
125
|
+
{{packageSection}}
|
|
126
|
+
## Query the data (MCP or REST)
|
|
127
|
+
|
|
128
|
+
Two interfaces reach the same models with the same governance.
|
|
129
|
+
|
|
130
|
+
MCP, for an interactive agent: the tools are `malloy_getContext` (discover
|
|
131
|
+
environments, packages, sources, and fields), `malloy_executeQuery` (run a view or
|
|
132
|
+
ad-hoc Malloy and get JSON back), `malloy_compile` (compile-check a snippet of Malloy
|
|
133
|
+
against the model, so it takes the snippet as a required `source` argument; it reports
|
|
134
|
+
the model file's own errors too),
|
|
135
|
+
`malloy_reloadPackage` (pick up on-disk model edits with no restart, and surface a
|
|
136
|
+
watch-mode recompile that failed), and
|
|
137
|
+
`malloy_searchDocs`. {{mcpNote}}
|
|
138
|
+
|
|
139
|
+
REST, for a script or a check that does not need an agent: every model is queryable
|
|
140
|
+
at `POST /api/v0/environments/<env>/packages/<package>/models/<model>/query`. This
|
|
141
|
+
lists what the server has loaded:
|
|
142
|
+
|
|
143
|
+
```bash
|
|
144
|
+
curl -s http://localhost:{{port}}/api/v0/environments/{{envName}}/packages
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
That list is what this workspace serves. This file is not: it describes at most one
|
|
148
|
+
package, the one the scaffolder created on the run that wrote this file. A workspace
|
|
149
|
+
scaffolded more than once serves more than that, so take the packages, sources and
|
|
150
|
+
views you do not see here from the list above and from `malloy_getContext`, never
|
|
151
|
+
from their absence here.
|
|
152
|
+
|
|
153
|
+
If you started this server yourself in this session, your `malloy_*` tools will not
|
|
154
|
+
appear however long you wait: an MCP client fixes its tool list when it connects, so
|
|
155
|
+
it never saw a server that did not yet exist. You cannot reconnect yourself. Say so,
|
|
156
|
+
and {{reconnectNote}} Do not quietly switch to curl instead and call it done: it
|
|
157
|
+
looks like it is working while hiding a problem the user can clear in seconds, and
|
|
158
|
+
it gives up the grounded discovery, compile checks, and reload the tools exist to
|
|
159
|
+
provide. If the user would rather keep going without reconnecting, the REST endpoint
|
|
160
|
+
above runs the same models.
|
|
161
|
+
|
|
162
|
+
## Skills ({{skillsCount}} installed)
|
|
163
|
+
|
|
164
|
+
{{skillsNote}}
|
|
165
|
+
|
|
166
|
+
The names below are that guidance broken out by task. Load them as installed skills
|
|
167
|
+
where the count above says they are here, and pull the same names as MCP prompts from
|
|
168
|
+
the endpoint above where it says they are not.
|
|
169
|
+
|
|
170
|
+
Start with `malloy-getting-started`. Use `malloy-modeling` to build or change the
|
|
171
|
+
model, `malloy-analysis` to answer questions, and `malloy-review` to check Malloy for
|
|
172
|
+
correctness.
|
|
173
|
+
|
|
174
|
+
Read the gotchas before you write, not after you fail: `malloy-gotchas-modeling` for
|
|
175
|
+
sources, dimensions, measures and joins, `malloy-gotchas-queries` for views and
|
|
176
|
+
queries, and `malloy-gotchas-rendering` for chart and formatting tags. They hold the
|
|
177
|
+
traps that cost the most time on a first model, including the two that a column of
|
|
178
|
+
real data usually springs:
|
|
179
|
+
|
|
180
|
+
- A column named `date`, `hour`, `number`, `source`, `type`, `count` or another
|
|
181
|
+
Malloy reserved word has to be backticked, or it fails with cascading errors on
|
|
182
|
+
lines that look unrelated.
|
|
183
|
+
- `avg(score::number)`, which `malloy-modeling` gives as a quick reminder, is only
|
|
184
|
+
right on a clean column. `::number` is a strict cast, so a column carrying `'NA'`,
|
|
185
|
+
`'N/A'`, `''` or `'-'` compiles and then throws at query time
|
|
186
|
+
(`Could not convert string 'NA' to DOUBLE`). The fix is
|
|
187
|
+
`avg(nullif(score, 'NA')::number)`. Where the two skills disagree,
|
|
188
|
+
`malloy-gotchas-modeling` is the correct one.
|
|
189
|
+
|
|
190
|
+
When something does not compile, reach for `malloy-debug` rather than guessing at the
|
|
191
|
+
error: it covers reading Malloy's messages, and why fixing the first error usually
|
|
192
|
+
clears the rest of a 20-error cascade.
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
// {{sourceName}}: a starter Malloy model over {{dataPath}}, served by Publisher.
|
|
2
|
+
//
|
|
3
|
+
// This model was scaffolded from a data file whose columns are not known ahead of
|
|
4
|
+
// time, so it starts with just a row count. Build it out for your columns: group_by
|
|
5
|
+
// a column to break the count down, and define measures like
|
|
6
|
+
// `total_amount is amount.sum()`. Validate edits with malloy_compile and run new
|
|
7
|
+
// views by name with malloy_reloadPackage. The malloy-modeling skill walks through
|
|
8
|
+
// turning a raw table into a semantic model.
|
|
9
|
+
|
|
10
|
+
source: {{sourceName}} is duckdb.table('{{dataPath}}') extend {
|
|
11
|
+
measure: record_count is count()
|
|
12
|
+
|
|
13
|
+
view: overview is {
|
|
14
|
+
aggregate: record_count
|
|
15
|
+
}
|
|
16
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
// {{sourceName}}: a starter Malloy model over local CSV data, served by Publisher.
|
|
2
|
+
//
|
|
3
|
+
// The source reads data/sales.csv through the package's built-in DuckDB sandbox:
|
|
4
|
+
// no credentials and no build step. DuckDB auto-detects the header row and column
|
|
5
|
+
// types. Edit this file and the dev server recompiles it, because it boots in
|
|
6
|
+
// watch mode; AGENTS.md carries the command that starts it for this workspace.
|
|
7
|
+
// An agent can validate a change with malloy_compile and run new views by name
|
|
8
|
+
// with malloy_reloadPackage.
|
|
9
|
+
|
|
10
|
+
source: {{sourceName}} is duckdb.table('data/sales.csv') extend {
|
|
11
|
+
primary_key: id
|
|
12
|
+
|
|
13
|
+
measure: record_count is count()
|
|
14
|
+
measure: total_amount is amount.sum()
|
|
15
|
+
measure: total_quantity is quantity.sum()
|
|
16
|
+
|
|
17
|
+
# bar_chart
|
|
18
|
+
view: by_category is {
|
|
19
|
+
group_by: category
|
|
20
|
+
aggregate: total_amount
|
|
21
|
+
order_by: total_amount desc
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
# bar_chart
|
|
25
|
+
view: by_region is {
|
|
26
|
+
group_by: region
|
|
27
|
+
aggregate: total_amount
|
|
28
|
+
order_by: total_amount desc
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
# line_chart
|
|
32
|
+
view: sales_by_month is {
|
|
33
|
+
group_by: order_month is order_date.month
|
|
34
|
+
aggregate: total_amount
|
|
35
|
+
order_by: order_month
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
view: overview is {
|
|
39
|
+
aggregate:
|
|
40
|
+
record_count
|
|
41
|
+
total_amount
|
|
42
|
+
total_quantity
|
|
43
|
+
}
|
|
44
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
id,order_date,region,category,product,quantity,amount
|
|
2
|
+
1,2024-01-05,West,Electronics,Wireless Mouse,2,49.98
|
|
3
|
+
2,2024-01-12,East,Furniture,Standing Desk,1,299.00
|
|
4
|
+
3,2024-01-20,West,Electronics,USB-C Hub,3,89.97
|
|
5
|
+
4,2024-02-03,South,Apparel,Running Shoes,1,120.00
|
|
6
|
+
5,2024-02-15,East,Furniture,Office Chair,2,340.00
|
|
7
|
+
6,2024-02-27,North,Electronics,Mechanical Keyboard,1,110.00
|
|
8
|
+
7,2024-03-08,West,Apparel,Rain Jacket,2,180.00
|
|
9
|
+
8,2024-03-19,South,Furniture,Bookshelf,1,150.00
|
|
10
|
+
9,2024-03-25,North,Electronics,Webcam,4,240.00
|
|
11
|
+
10,2024-04-02,East,Apparel,Wool Socks,5,75.00
|