@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/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