@graphenedata/cli 0.0.16 → 0.0.17

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (26) hide show
  1. package/README.md +65 -29
  2. package/dist/cli/{bigQuery-I3F46SC6.js → bigQuery-OQUNH3VT.js} +2 -2
  3. package/dist/cli/{chunk-QAXEOZ43.js → chunk-56K2FF57.js} +1 -1
  4. package/dist/cli/chunk-56K2FF57.js.map +7 -0
  5. package/dist/cli/{chunk-OVWODUTJ.js → chunk-TZTTALAV.js} +36 -17
  6. package/dist/cli/{chunk-OVWODUTJ.js.map → chunk-TZTTALAV.js.map} +3 -3
  7. package/dist/cli/cli.js +33 -6
  8. package/dist/cli/{clickhouse-ZN5AN2UL.js → clickhouse-S3BJSKND.js} +3 -2
  9. package/dist/cli/clickhouse-S3BJSKND.js.map +7 -0
  10. package/dist/cli/{duckdb-IYBIO5KJ.js → duckdb-TKVMONRK.js} +2 -2
  11. package/dist/cli/{serve2-TNN5EROW.js → serve2-S2LL4D4D.js} +7 -6
  12. package/dist/cli/{serve2-TNN5EROW.js.map → serve2-S2LL4D4D.js.map} +2 -2
  13. package/dist/cli/{snowflake-MOQB5GA4.js → snowflake-3VPDEYYP.js} +2 -2
  14. package/dist/skills/graphene/SKILL.md +7 -0
  15. package/dist/skills/graphene/references/model-gsql.md +19 -21
  16. package/dist/ui/component-utilities/enrich.ts +34 -4
  17. package/dist/ui/internal/LocalApp.svelte +29 -27
  18. package/dist/ui/internal/queryEngine.ts +13 -15
  19. package/dist/ui/internal/runSocket.ts +2 -5
  20. package/dist/ui/web.js +4 -2
  21. package/package.json +5 -1
  22. package/dist/cli/chunk-QAXEOZ43.js.map +0 -7
  23. package/dist/cli/clickhouse-ZN5AN2UL.js.map +0 -7
  24. /package/dist/cli/{bigQuery-I3F46SC6.js.map → bigQuery-OQUNH3VT.js.map} +0 -0
  25. /package/dist/cli/{duckdb-IYBIO5KJ.js.map → duckdb-TKVMONRK.js.map} +0 -0
  26. /package/dist/cli/{snowflake-MOQB5GA4.js.map → snowflake-3VPDEYYP.js.map} +0 -0
package/README.md CHANGED
@@ -25,23 +25,35 @@
25
25
 
26
26
  ## Why Graphene?
27
27
 
28
- In the future, we believe **most low-level data analytics work will be done by agents**, allowing humans to focus on insights and decision-making. However, today's tools weren't built with agents in mind:
28
+ Graphene is an everything-as-code analytics framework for SQL-based data exploration, visualization, and reporting. It is designed with coding agents in mind as the primary user persona.
29
29
 
30
- - They are GUI centric. Lots of actions can only be done via GUI and aren't accessible to external agents like Claude.
31
- - They focus on raising the floor at the expense of lowering the ceiling (limited viz types, simplified querying APIs).
32
- - They assume the human user has the tribal knowledge and business context necessary for analysis.
30
+ It provides two critical pieces that allow coding agents to do better data work:
33
31
 
34
- If we really want agents to be more productive with data, an entirely new toolset is needed.
32
+ 1. **A semantic layer**, which yields more accurate queries. GSQL combines the power of SQL with the governance of metrics and modeled joins.
33
+ 2. **A dashboard file type**, which yields more consistent and polished visuals compared to raw Python or Javascript.
35
34
 
36
- Graphene is:
35
+ **Design goals**
37
36
 
38
- - [x] **Built for agents**. Everything is code, written only in languages that are prevalent in training data (SQL, Markdown, HTML). All actions are CLIs; nothing is trapped in a button.
39
- - [x] **High-ceiling**. Agents can create any visualization that's supported by ECharts, one of the most feature-complete visualization libraries. And Graphene's query language is as powerful as ANSI SQL, which supports 170+ functions, CTEs, subqueries, set operations, window functions, arrays, and more.
40
- - [x] **Optimized for agent context**. Graphene's SQL language contains a semantic layer which allows metrics and join relationships to be invoked in queries. When combined with [agent skills](https://agentskills.io/home) for general business context and best practices, agents perform at human levels of competency.
37
+ - Token efficiency. Languages are designed to be brief with minimal boilerplate.
38
+ - Agent ergonomics. Graphene is controlled entirely via CLI. All documentation is inside our agent skill.
39
+ - High ceilings. GSQL follows ANSI and supports over 170 functions; Graphene's visualizations support anything that can be expressed with ECharts.
40
+
41
+ ### Versus traditional BI
42
+
43
+ We believe coding agents coupled with an everything-as-code analytics stack beats traditional BI in several ways:
44
+
45
+ - Broad ecosystem of SOTA LLMs, harnesses, skills, and tools
46
+ - Leverage business-wide context from other tools or repos
47
+ - Perform end-to-end tasks across tools, where analytics is just one step
48
+ - More graceful change management and bulk refactors
49
+ - Easily promote/demote logic into or out of the semantic layer
50
+ - Version control and CI. Revert agent mistakes. Run tests on mission-critical dashboards.
51
+ - Tight, complete iteration loops. Agents can validate before running, view dashboards, and iterate locally
52
+ - Leverage continuous agents for self-healing codebases
41
53
 
42
54
  ### Open, forever
43
55
 
44
- Importantly, Graphene is **open**. You can use this project for internal purposes for free, forever, and aren't locked in to a contract with us. More details [below](#faq).
56
+ Graphene is free to use, forever. Your business logic lives in your repo and is never locked into a contract with us.
45
57
 
46
58
  ### Rich visualizations
47
59
 
@@ -49,16 +61,13 @@ Graphene pages support visualizations, input components for filtering and dynami
49
61
 
50
62
  <img alt="Graphene Screenshots" src="./assets/page_examples.png"/>
51
63
 
52
- ### Powerful workflows
64
+ ### Powerful, next-generation semantic layer
53
65
 
54
- When you deconstruct data analytics into code, CLIs, and coding agents, things that used to be hard become easy:
66
+ Traditional semantic layers give you governance at the expense of capability. They tend to expose niche query APIs that agents aren't familiar with.
55
67
 
56
- - Promote metrics from pages into the model, or demote metrics out of the model back into pages
57
- - Bulk refactors in a single atomic commit/PR
58
- - Ability to use extensive skill/MCP ecosystem to augment agent behavior
59
- - Iterate on a dashboard (edit, run, view) without needing to push up to some API or open a SaaS portal
60
- - Validate SQL and page syntax instantaneously as you type
61
- - Set up a recurring agent that de-bloats, consolidates your model over time
68
+ GSQL's goal is to bring governance _without_ sacrificing capability. It behaves like regular SQL—with CTEs, subqueries, window functions, set operators, and more—but also adds in the concepts of measures and modeled joins from semantic layers.
69
+
70
+ GSQL is inspired by [Malloy](https://github.com/malloydata/malloy), from the creator of LookML Lloyd Tabb, but implements it as good old SQL for agent familiarity.
62
71
 
63
72
  ## Get started
64
73
 
@@ -71,13 +80,10 @@ Once your project is set up, simply start the dev server via `npm exec graphene
71
80
 
72
81
  ## How it works
73
82
 
74
- A Graphene project can either be a standalone repo or a directory within a larger codebase (such as dbt). It is comprised of:
75
-
76
- - **Semantic models**, via .gsql files. GSQL is both a modeling language and a query language, in the same way that SQL has both DDL and DML.
77
- - **Pages**, via .md files. Pages are typically used for dashboards, but can also contain notebook-style narratives, documentation, and other visual content.
78
-
79
83
  Graphene itself is a CLI which can be installed via npm (or pnpm, yarn, etc.). The CLI can run and compile GSQL queries, render pages in the browser, check syntax, print screenshots, and more.
80
84
 
85
+ A Graphene project can either be a standalone repo or a directory within a larger codebase (such as dbt). It is comprised of _semantic models_ via .gsql files and _pages_ via .md files.
86
+
81
87
  ### GSQL and Graphene markdown
82
88
 
83
89
  Semantic models are defined like so:
@@ -127,12 +133,42 @@ Graphene's entire documentation ships as an agent skill in the Graphene npm pack
127
133
 
128
134
  ## FAQ
129
135
 
130
- <details>
131
- <summary><b>So does everyone have to use git and a coding agent to use Graphene for BI?</b></summary>
132
- If you just want to use this project and nothing more, yes. Our managed service, Graphene Cloud, offers a Slack agent, MCP server, and browser-based SaaS experience.
136
+ <details><summary><b>Why coding agents?</b></summary>
137
+ <br/>
138
+ Context, mostly. If your BI stack lives in a folder right next to the rest of your company’s data and code, an agent can make smarter decisions on what it should be analyzing and why it matters.
139
+ <br/><br/>
140
+ The reverse is also true. If you’re working on building out some new feature or a recommendation for a new client, the agent doing that work can ground it’s approach in real data and past analytical insights.
141
+ <br/><br/>
142
+ Lock-in is the other big reason we hear. Folks post-SaaS are wary of having their data and dashboards locked away in some proprietary tool, or forced to use a single LLM provider. With Graphene, you own all the files in your repo. You can use whatever agent or LLM you’d like.
143
+ </details>
144
+
145
+ <details><summary><b>How do you make money?</b></summary>
146
+ <br/>
147
+ We’re building out Graphene Cloud as a turnkey solution to host the dashboards and reports your agent builds. We also host an MCP server and Slack bot that you can use for quick questions. If you'd like to pilot it, contact us <a href="https://graphenedata.com/contact-us">here</a>.
133
148
  </details>
134
149
 
135
- <details>
136
- <summary><b>What software license does this use?</b></summary>
137
- Graphene is licensed under the Elastic License 2.0 which allows you to use it for internal use cases for free, forever. If you would like to use the Graphene Cloud services above, or if you would like a commercial license, please contact us <a href="https://graphenedata.com/contact-us">here</a>.
150
+ <details><summary><b>So does everyone have to use git and a coding agent to use Graphene for BI?</b></summary>
151
+ <br/>
152
+ If you just want to use this project and nothing more, yes. Our managed service, Graphene Cloud, offers a Slack agent, MCP server, and browser-based SaaS experience.
153
+ </details>
154
+
155
+ <details><summary><b>Is my data team out of a job?</b></summary>
156
+ <br/>
157
+ No, but we think the nature of the work is going to change. Instead of manually building out reports, data experts are going to be shaping the skills, models, and tools that the rest of their team uses to answer data questions.
158
+ <br/><br/>
159
+ Data teams are going to be focused on guiding agents on how to approach the trickiest and most nebulous data questions at a company. Questions that still require a data expert’s taste to get a good solution.
160
+ </details>
161
+
162
+ <details><summary><b>Can’t I just vibe code dashboards?</b></summary>
163
+ <br/>
164
+ You could! In fact a lot of the folks we’ve talked to have started down this route. The main problem you’ll run into is consistency. The look and feel of your dashboards and reports are all over the place, and in the worst case they end up using different formula to compute the same key metric.
165
+ <br/><br/>
166
+ GSQL codifies metrics into deterministic objects you can directly invoke in queries. Not only does this ensure that every use of "EBITDA" will be the same, the metadata tags we attach can hint our charts into formatting data correctly.
167
+ <br/><br/>
168
+ Graphene raises the floor so that pages you generate with the help of an agent look beautiful by default, so you can move faster with less tokens.
169
+ </details>
170
+
171
+ <details><summary><b>What software license does this use?</b></summary>
172
+ <br/>
173
+ Graphene is licensed under the Elastic License 2.0 which allows you to use it for internal use cases for free, forever. If you would like to build your own commercial application with Graphene, please contact us <a href="https://graphenedata.com/contact-us">here</a>.
138
174
  </details>
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  config
3
- } from "./chunk-QAXEOZ43.js";
3
+ } from "./chunk-56K2FF57.js";
4
4
 
5
5
  // connections/bigQuery.ts
6
6
  import { BigQuery, BigQueryDate, BigQueryTimestamp } from "@google-cloud/bigquery";
@@ -72,4 +72,4 @@ var BigQueryConnection = class {
72
72
  export {
73
73
  BigQueryConnection
74
74
  };
75
- //# sourceMappingURL=bigQuery-I3F46SC6.js.map
75
+ //# sourceMappingURL=bigQuery-OQUNH3VT.js.map
@@ -50,4 +50,4 @@ export {
50
50
  setGlobalConfig,
51
51
  loadConfig
52
52
  };
53
- //# sourceMappingURL=chunk-QAXEOZ43.js.map
53
+ //# sourceMappingURL=chunk-56K2FF57.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../lang/config.ts"],
4
+ "sourcesContent": ["import {existsSync} from 'node:fs'\nimport {readFile} from 'node:fs/promises'\nimport path from 'path'\n\nexport interface Config {\n root: string\n dialect: string\n defaultNamespace?: string\n ignoredFiles: string[]\n telemetry?: boolean\n port?: number\n host?: string\n envFile: string[] // array of paths where we can look for the env file\n\n bigquery?: {\n projectId?: string\n keyPath?: string\n }\n\n snowflake?: {\n account: string\n username: string\n privateKeyPath: string\n schema?: string\n database?: string\n }\n\n clickhouse?: {\n url?: string\n username?: string\n database?: string\n requestTimeout?: number\n }\n\n duckdb?: {\n path?: string\n }\n}\n\nexport type ConfigInput = Omit<Config, 'root' | 'dialect' | 'ignoredFiles' | 'envFile'> & {\n root?: string\n dialect?: Config['dialect']\n ignoredFiles?: Config['ignoredFiles']\n envFile?: string | string[]\n namespace?: string\n}\n\nexport let config: Config = {dialect: 'duckdb', root: ''} as Config\n\nexport function setGlobalConfig(cfg: ConfigInput) {\n Object.keys(config).forEach(key => delete config[key])\n Object.assign(config, normalizeConfig(cfg))\n}\n\nexport function normalizeConfig(input: ConfigInput, defaultRoot = process.cwd()): Config {\n let cfg = {...input}\n if (cfg.namespace && !cfg.defaultNamespace) cfg.defaultNamespace = cfg.namespace\n\n let dialect = cfg.dialect || 'duckdb'\n if (cfg.bigquery) dialect = 'bigquery'\n else if (cfg.snowflake) dialect = 'snowflake'\n else if (cfg.clickhouse) dialect = 'clickhouse'\n else if (cfg.duckdb) dialect = 'duckdb'\n let envFile = ['.env']\n if (Array.isArray(cfg.envFile)) envFile = cfg.envFile\n else if (cfg.envFile) envFile = [cfg.envFile]\n\n return {\n ...cfg,\n dialect,\n root: path.resolve(cfg.root || defaultRoot),\n port: cfg.port || Number(process.env.GRAPHENE_PORT) || 4000,\n ignoredFiles: cfg.ignoredFiles || ['**/agents.md', '**/claude.md'],\n envFile,\n } as Config\n}\n\n// Read graphene config from the nearest parent package.json.\nexport async function loadConfig(dir: string, envLoader: (envFiles: string[]) => void): Promise<Config> {\n // seek upwards from dir looking for package.json\n let configDir = path.resolve(dir)\n while (!existsSync(path.join(configDir, 'package.json'))) {\n let parent = path.dirname(configDir)\n if (parent == configDir) throw new Error(`No package.json found in ${path.resolve(dir)} or its parents`)\n configDir = parent\n }\n\n let txt = await readFile(path.join(configDir, 'package.json'), 'utf8')\n let graphene = JSON.parse(txt).graphene\n if (!graphene || typeof graphene != 'object' || Array.isArray(graphene)) {\n throw new Error(`No graphene config found in ${path.join(configDir, 'package.json')}`)\n }\n\n // config can provide 1 or more env files that Graphene should load. Default to just `.env`\n let envFiles = Array.isArray(graphene.envFile) ? graphene.envFile : [graphene.envFile || '.env']\n envLoader(envFiles.map(file => path.resolve(configDir, file)))\n\n let cfg = normalizeConfig({...graphene, root: configDir}, configDir)\n return cfg\n}\n"],
5
+ "mappings": ";AAAA,SAAQ,kBAAiB;AACzB,SAAQ,gBAAe;AACvB,OAAO,UAAU;AA6CV,IAAI,SAAiB,EAAC,SAAS,UAAU,MAAM,GAAE;AAEjD,SAAS,gBAAgB,KAAkB;AAChD,SAAO,KAAK,MAAM,EAAE,QAAQ,SAAO,OAAO,OAAO,GAAG,CAAC;AACrD,SAAO,OAAO,QAAQ,gBAAgB,GAAG,CAAC;AAC5C;AAEO,SAAS,gBAAgB,OAAoB,cAAc,QAAQ,IAAI,GAAW;AACvF,MAAI,MAAM,EAAC,GAAG,MAAK;AACnB,MAAI,IAAI,aAAa,CAAC,IAAI,iBAAkB,KAAI,mBAAmB,IAAI;AAEvE,MAAI,UAAU,IAAI,WAAW;AAC7B,MAAI,IAAI,SAAU,WAAU;AAAA,WACnB,IAAI,UAAW,WAAU;AAAA,WACzB,IAAI,WAAY,WAAU;AAAA,WAC1B,IAAI,OAAQ,WAAU;AAC/B,MAAI,UAAU,CAAC,MAAM;AACrB,MAAI,MAAM,QAAQ,IAAI,OAAO,EAAG,WAAU,IAAI;AAAA,WACrC,IAAI,QAAS,WAAU,CAAC,IAAI,OAAO;AAE5C,SAAO;AAAA,IACL,GAAG;AAAA,IACH;AAAA,IACA,MAAM,KAAK,QAAQ,IAAI,QAAQ,WAAW;AAAA,IAC1C,MAAM,IAAI,QAAQ,OAAO,QAAQ,IAAI,aAAa,KAAK;AAAA,IACvD,cAAc,IAAI,gBAAgB,CAAC,gBAAgB,cAAc;AAAA,IACjE;AAAA,EACF;AACF;AAGA,eAAsB,WAAW,KAAa,WAA0D;AAEtG,MAAI,YAAY,KAAK,QAAQ,GAAG;AAChC,SAAO,CAAC,WAAW,KAAK,KAAK,WAAW,cAAc,CAAC,GAAG;AACxD,QAAI,SAAS,KAAK,QAAQ,SAAS;AACnC,QAAI,UAAU,UAAW,OAAM,IAAI,MAAM,4BAA4B,KAAK,QAAQ,GAAG,CAAC,iBAAiB;AACvG,gBAAY;AAAA,EACd;AAEA,MAAI,MAAM,MAAM,SAAS,KAAK,KAAK,WAAW,cAAc,GAAG,MAAM;AACrE,MAAI,WAAW,KAAK,MAAM,GAAG,EAAE;AAC/B,MAAI,CAAC,YAAY,OAAO,YAAY,YAAY,MAAM,QAAQ,QAAQ,GAAG;AACvE,UAAM,IAAI,MAAM,+BAA+B,KAAK,KAAK,WAAW,cAAc,CAAC,EAAE;AAAA,EACvF;AAGA,MAAI,WAAW,MAAM,QAAQ,SAAS,OAAO,IAAI,SAAS,UAAU,CAAC,SAAS,WAAW,MAAM;AAC/F,YAAU,SAAS,IAAI,UAAQ,KAAK,QAAQ,WAAW,IAAI,CAAC,CAAC;AAE7D,MAAI,MAAM,gBAAgB,EAAC,GAAG,UAAU,MAAM,UAAS,GAAG,SAAS;AACnE,SAAO;AACT;",
6
+ "names": []
7
+ }
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  config
3
- } from "./chunk-QAXEOZ43.js";
3
+ } from "./chunk-56K2FF57.js";
4
4
 
5
5
  // ../lang/types.ts
6
6
  var SCALAR_TYPE_ALIASES = {
@@ -12409,7 +12409,7 @@ function normalizeState(state, fallback) {
12409
12409
  // telemetry/index.ts
12410
12410
  var DEFAULT_TELEMETRY_ENDPOINT = "https://app.graphenedata.com/cli-telemetry";
12411
12411
  var SAFE_FLAG_NAMES = {
12412
- run: { chart: ["--chart", "-c"], query: ["--query", "-q"] },
12412
+ run: { chart: ["--chart", "-c"], input: ["--input"], query: ["--query", "-q"] },
12413
12413
  serve: { bg: ["--bg"] }
12414
12414
  };
12415
12415
  var CliTelemetry = class {
@@ -12527,7 +12527,7 @@ async function pathExists(filePath) {
12527
12527
  import { readFileSync } from "fs";
12528
12528
  async function getConnection() {
12529
12529
  if (config.dialect === "bigquery") {
12530
- let mod = await importConnection(() => import("./bigQuery-I3F46SC6.js"), "@google-cloud/bigquery", "BigQuery");
12530
+ let mod = await importConnection(() => import("./bigQuery-OQUNH3VT.js"), "@google-cloud/bigquery", "BigQuery");
12531
12531
  let options = {};
12532
12532
  if (process.env.GOOGLE_CREDENTIALS_CONTENT) {
12533
12533
  let parsed = JSON.parse(process.env.GOOGLE_CREDENTIALS_CONTENT);
@@ -12538,10 +12538,10 @@ async function getConnection() {
12538
12538
  }
12539
12539
  return new mod.BigQueryConnection(options);
12540
12540
  } else if (config.dialect === "duckdb") {
12541
- let mod = await importConnection(() => import("./duckdb-IYBIO5KJ.js"), "@duckdb/node-api", "DuckDB");
12541
+ let mod = await importConnection(() => import("./duckdb-TKVMONRK.js"), "@duckdb/node-api", "DuckDB");
12542
12542
  return new mod.DuckDBConnection({});
12543
12543
  } else if (config.dialect === "clickhouse") {
12544
- let mod = await importConnection(() => import("./clickhouse-ZN5AN2UL.js"), "@clickhouse/client", "ClickHouse");
12544
+ let mod = await importConnection(() => import("./clickhouse-S3BJSKND.js"), "@clickhouse/client", "ClickHouse");
12545
12545
  let url = config.clickhouse?.url || process.env.CLICKHOUSE_URL;
12546
12546
  let username = config.clickhouse?.username || process.env.CLICKHOUSE_USERNAME;
12547
12547
  let password = process.env.CLICKHOUSE_PASSWORD;
@@ -12550,10 +12550,11 @@ async function getConnection() {
12550
12550
  url,
12551
12551
  username,
12552
12552
  password,
12553
- database: config.clickhouse?.database || config.defaultNamespace || "default"
12553
+ database: config.clickhouse?.database || config.defaultNamespace || "default",
12554
+ requestTimeout: config.clickhouse?.requestTimeout
12554
12555
  });
12555
12556
  } else if (config.dialect === "snowflake") {
12556
- let mod = await importConnection(() => import("./snowflake-MOQB5GA4.js"), "snowflake-sdk", "Snowflake");
12557
+ let mod = await importConnection(() => import("./snowflake-3VPDEYYP.js"), "snowflake-sdk", "Snowflake");
12557
12558
  return new mod.SnowflakeConnection({
12558
12559
  privateKeyPath: process.env.SNOWFLAKE_PRI_KEY_PATH,
12559
12560
  privateKey: process.env.SNOWFLAKE_PRI_KEY,
@@ -12599,7 +12600,6 @@ async function runQuery(sql, params) {
12599
12600
  import fs5 from "fs-extra";
12600
12601
  import { readFileSync as readFileSync2 } from "node:fs";
12601
12602
  import { styleText as styleText2 } from "node:util";
12602
- import os2 from "os";
12603
12603
  import path7 from "path";
12604
12604
  import { WebSocketServer } from "ws";
12605
12605
 
@@ -12638,7 +12638,7 @@ async function runMdFile(options) {
12638
12638
  printDiagnostics(result.diagnostics, log);
12639
12639
  return false;
12640
12640
  }
12641
- let resp = await sendSocketRequest({ mdFile, action: "check", chart: options.chart, log });
12641
+ let resp = await sendSocketRequest({ mdFile, action: "check", chart: options.chart, inputs: options.inputs, log });
12642
12642
  if (!resp) return false;
12643
12643
  let errors = Array.from(resp.errors || []);
12644
12644
  let chartNotFound = !!options.chart && !resp.screenshot;
@@ -12657,9 +12657,11 @@ async function runMdFile(options) {
12657
12657
  log("Warning: Queries were still loading when the screenshot was taken");
12658
12658
  }
12659
12659
  if (resp?.screenshot) {
12660
- let filename = `graphene-screenshot-${(/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-")}.png`;
12661
- let screenshotPath = path7.join(os2.tmpdir(), filename);
12660
+ let filename = `${(/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-")}.png`;
12661
+ let screenshotDir = path7.join(getGrapheneCache(config.root), "screenshots");
12662
+ let screenshotPath = path7.join(screenshotDir, filename);
12662
12663
  let base64Data = resp.screenshot.replace(/^data:image\/png;base64,/, "");
12664
+ await fs5.ensureDir(screenshotDir);
12663
12665
  await fs5.writeFile(screenshotPath, base64Data, "base64");
12664
12666
  log("Screenshot saved to", screenshotPath);
12665
12667
  }
@@ -12686,9 +12688,9 @@ async function listMdFileQueries(mdArg, telemetry, log = console.log) {
12686
12688
  else componentIds.forEach((componentId) => log(componentId));
12687
12689
  return true;
12688
12690
  }
12689
- async function runNamedQueryFromMd(mdAbsolutePath, queryName, telemetry) {
12691
+ async function runNamedQueryFromMd(mdAbsolutePath, queryName, options = {}) {
12690
12692
  let files = await loadWorkspace(process.cwd(), false, config.ignoredFiles);
12691
- telemetry?.event("workspace_scanned", { command: "run", ...getWorkspaceScanCounts(files) });
12693
+ options.telemetry?.event("workspace_scanned", { command: "run", ...getWorkspaceScanCounts(files) });
12692
12694
  let mdRelativePath = path7.relative(process.cwd(), mdAbsolutePath);
12693
12695
  let mdContents = await fs5.promises.readFile(mdAbsolutePath, "utf-8");
12694
12696
  let result = analyzeWorkspace({ config, files: files.filter((file) => file.path != mdRelativePath).concat({ path: mdRelativePath, contents: mdContents, kind: "md" }) }, mdRelativePath);
@@ -12705,14 +12707,21 @@ async function runNamedQueryFromMd(mdAbsolutePath, queryName, telemetry) {
12705
12707
  }
12706
12708
  let input = getFile2(queryResult, "input.md");
12707
12709
  if (!input?.queries.length) return false;
12708
- let sql = toSql(input.queries[input.queries.length - 1]);
12710
+ let sql;
12711
+ try {
12712
+ sql = toSql(input.queries[input.queries.length - 1], options.inputs || {});
12713
+ } catch (err) {
12714
+ console.error(err instanceof Error ? err.message : String(err));
12715
+ return false;
12716
+ }
12709
12717
  let res = await runQuery(sql);
12710
12718
  printTable(res.rows);
12711
12719
  return true;
12712
12720
  }
12713
- async function sendSocketRequest({ mdFile, action, chart, log }) {
12721
+ async function sendSocketRequest({ mdFile, action, chart, inputs, log }) {
12714
12722
  let pageUrl = "/" + mdFile.replace(/\.md$/, "").replace(/^\//, "").replace(/\\/g, "/");
12715
12723
  if (pageUrl === "/index") pageUrl = "/";
12724
+ pageUrl = appendInputsToUrl(pageUrl, inputs);
12716
12725
  if (process.env.NODE_ENV !== "test" && !await isServerRunning()) {
12717
12726
  log("Starting Graphene server...");
12718
12727
  await runServeInBackground();
@@ -12739,6 +12748,16 @@ async function sendSocketRequest({ mdFile, action, chart, log }) {
12739
12748
  }
12740
12749
  return resp;
12741
12750
  }
12751
+ function appendInputsToUrl(pageUrl, inputs = {}) {
12752
+ let search = new URLSearchParams();
12753
+ Object.entries(inputs).forEach(([name, value]) => {
12754
+ if (Array.isArray(value)) value.forEach((item) => search.append(name, item));
12755
+ else search.append(name, value);
12756
+ });
12757
+ let rendered = search.toString();
12758
+ if (!rendered) return pageUrl;
12759
+ return `${pageUrl}?${rendered}`;
12760
+ }
12742
12761
  async function fetchSocketRequest({ host, pageUrl, action, chart }) {
12743
12762
  let abort = new AbortController();
12744
12763
  let timeout = setTimeout(() => abort.abort(), 3e4);
@@ -12777,7 +12796,7 @@ async function proxyRunRequest(req, res) {
12777
12796
  res.end(JSON.stringify({ error: "no_tab" }));
12778
12797
  return;
12779
12798
  }
12780
- conn.socket.send(JSON.stringify({ type: "check", action, chart, requestId: id }));
12799
+ conn.socket.send(JSON.stringify({ action, chart, requestId: id }));
12781
12800
  pendingRequests[id] = { response: res };
12782
12801
  }
12783
12802
  function toWorkspaceFiles(analysis) {
@@ -12846,4 +12865,4 @@ export {
12846
12865
  runNamedQueryFromMd,
12847
12866
  runVitePlugin
12848
12867
  };
12849
- //# sourceMappingURL=chunk-OVWODUTJ.js.map
12868
+ //# sourceMappingURL=chunk-TZTTALAV.js.map