chrome-jig 0.1.0 → 0.2.0

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 (45) hide show
  1. package/SKILL.md +1 -0
  2. package/dist/chrome/connection.js +2 -2
  3. package/dist/chrome/connection.js.map +1 -1
  4. package/dist/cli.js +51 -0
  5. package/dist/cli.js.map +1 -1
  6. package/dist/cljs/compiler.d.ts.map +1 -1
  7. package/dist/cljs/compiler.js +1 -0
  8. package/dist/cljs/compiler.js.map +1 -1
  9. package/dist/commands/install-nvim.d.ts +21 -0
  10. package/dist/commands/install-nvim.d.ts.map +1 -0
  11. package/dist/commands/install-nvim.js +127 -0
  12. package/dist/commands/install-nvim.js.map +1 -0
  13. package/dist/commands/serve.d.ts +16 -0
  14. package/dist/commands/serve.d.ts.map +1 -0
  15. package/dist/commands/serve.js +22 -0
  16. package/dist/commands/serve.js.map +1 -0
  17. package/dist/index.d.ts +9 -0
  18. package/dist/index.d.ts.map +1 -1
  19. package/dist/index.js +7 -0
  20. package/dist/index.js.map +1 -1
  21. package/dist/repl/repl.d.ts +8 -8
  22. package/dist/repl/repl.d.ts.map +1 -1
  23. package/dist/repl/repl.js +45 -131
  24. package/dist/repl/repl.js.map +1 -1
  25. package/dist/session/jsonrpc-protocol.d.ts +18 -0
  26. package/dist/session/jsonrpc-protocol.d.ts.map +1 -0
  27. package/dist/session/jsonrpc-protocol.js +59 -0
  28. package/dist/session/jsonrpc-protocol.js.map +1 -0
  29. package/dist/session/protocol.d.ts +47 -0
  30. package/dist/session/protocol.d.ts.map +1 -0
  31. package/dist/session/protocol.js +10 -0
  32. package/dist/session/protocol.js.map +1 -0
  33. package/dist/session/repl-protocol.d.ts +16 -0
  34. package/dist/session/repl-protocol.d.ts.map +1 -0
  35. package/dist/session/repl-protocol.js +101 -0
  36. package/dist/session/repl-protocol.js.map +1 -0
  37. package/dist/session/session.d.ts +44 -0
  38. package/dist/session/session.d.ts.map +1 -0
  39. package/dist/session/session.js +195 -0
  40. package/dist/session/session.js.map +1 -0
  41. package/editor/nvim/README.md +134 -0
  42. package/editor/nvim/lua/cjig/init.lua +226 -0
  43. package/editor/nvim/lua/cjig/rpc.lua +177 -0
  44. package/editor/nvim/lua/cjig/ui.lua +88 -0
  45. package/package.json +2 -1
@@ -0,0 +1,195 @@
1
+ /**
2
+ * Session core — shared method dispatch between all protocol adapters.
3
+ *
4
+ * Owns the readline loop, method routing, and connection reference.
5
+ * Protocol adapters own parsing and formatting.
6
+ */
7
+ import * as readline from 'node:readline';
8
+ import { evaluate } from '../commands/eval.js';
9
+ import { evaluateCljs } from '../commands/cljs-eval.js';
10
+ import { listTabs, selectTab } from '../commands/tabs.js';
11
+ import { inject } from '../commands/inject.js';
12
+ import { isProtocolError } from './protocol.js';
13
+ /** Built-in method handlers */
14
+ export const builtinMethods = {
15
+ async eval(params, connection) {
16
+ const code = String(params.code ?? '');
17
+ if (!code)
18
+ throw new Error('Missing param: code');
19
+ const lang = String(params.lang ?? 'js');
20
+ if (lang === 'cljs') {
21
+ const result = await evaluateCljs(connection, code);
22
+ if (!result.success)
23
+ throw new Error(result.error);
24
+ return result.value;
25
+ }
26
+ if (lang !== 'js') {
27
+ throw new Error(`Unknown lang: ${lang}. Supported: js, cljs`);
28
+ }
29
+ const result = await evaluate(connection, code);
30
+ if (!result.success)
31
+ throw new Error(result.error);
32
+ return result.value;
33
+ },
34
+ async 'cljs-eval'(params, connection, config) {
35
+ return builtinMethods.eval({ ...params, lang: 'cljs' }, connection, config);
36
+ },
37
+ async tabs(_params, connection) {
38
+ const tabs = await listTabs(connection);
39
+ return { tabs, formatted: formatTabs(tabs) };
40
+ },
41
+ async selectTab(params, connection) {
42
+ const pattern = String(params.pattern ?? '');
43
+ if (!pattern)
44
+ throw new Error('Missing param: pattern');
45
+ const tab = await selectTab(connection, pattern);
46
+ if (!tab)
47
+ throw new Error(`No tab matching: ${pattern}`);
48
+ return { tab, formatted: `Switched to: ${tab.title}\n ${tab.url}` };
49
+ },
50
+ async inject(params, connection, config) {
51
+ const ref = String(params.ref ?? '');
52
+ if (!ref)
53
+ throw new Error('Missing param: ref');
54
+ const result = await inject(connection, config, ref);
55
+ if (!result.success)
56
+ throw new Error(result.error);
57
+ const lines = [`Injected: ${result.url}`];
58
+ if (result.windowApi)
59
+ lines.push(`API available as: window.${result.windowApi}`);
60
+ if (result.quickStart)
61
+ lines.push(`Try: ${result.quickStart}`);
62
+ return { ...result, formatted: lines.join('\n') };
63
+ },
64
+ async reload(_params, connection) {
65
+ await connection.reload();
66
+ return { formatted: 'Reloaded' };
67
+ },
68
+ };
69
+ function formatTabs(tabs) {
70
+ if (tabs.length === 0)
71
+ return 'No tabs open';
72
+ const lines = ['\nOpen tabs:\n'];
73
+ for (const tab of tabs) {
74
+ const marker = tab.isCurrent ? '→ ' : ' ';
75
+ lines.push(`${marker}[${tab.index}] ${tab.title}`);
76
+ lines.push(` ${tab.url}\n`);
77
+ }
78
+ return lines.join('\n');
79
+ }
80
+ export class Session {
81
+ options;
82
+ rl = null;
83
+ running = false;
84
+ methods;
85
+ pending = 0;
86
+ drainResolve = null;
87
+ constructor(options) {
88
+ this.options = options;
89
+ this.methods = { ...builtinMethods };
90
+ }
91
+ /** Register a custom method handler */
92
+ register(method, handler) {
93
+ this.methods[method] = handler;
94
+ }
95
+ async start() {
96
+ this.running = true;
97
+ const input = this.options.input ?? process.stdin;
98
+ const output = this.options.output ?? process.stdout;
99
+ const rlOptions = {
100
+ input: input,
101
+ output: output,
102
+ terminal: !!this.options.prompt,
103
+ };
104
+ if (this.options.prompt)
105
+ rlOptions.prompt = this.options.prompt;
106
+ if (this.options.completer)
107
+ rlOptions.completer = this.options.completer;
108
+ this.rl = readline.createInterface(rlOptions);
109
+ if (this.options.prompt)
110
+ this.rl.prompt();
111
+ this.rl.on('line', async (line) => {
112
+ this.pending++;
113
+ try {
114
+ await this.handleLine(line, output);
115
+ }
116
+ finally {
117
+ this.pending--;
118
+ if (this.pending === 0 && this.drainResolve) {
119
+ this.drainResolve();
120
+ }
121
+ }
122
+ if (this.running && this.options.prompt) {
123
+ this.rl?.prompt();
124
+ }
125
+ });
126
+ if (this.options.prompt) {
127
+ this.rl.on('SIGINT', () => {
128
+ this.write(output, '\n(Use .exit or Ctrl+D to quit)');
129
+ this.rl?.prompt();
130
+ });
131
+ }
132
+ return new Promise((resolve) => {
133
+ this.rl?.on('close', async () => {
134
+ if (this.pending > 0) {
135
+ await new Promise(r => { this.drainResolve = r; });
136
+ }
137
+ this.running = false;
138
+ this.options.onExit?.();
139
+ resolve();
140
+ });
141
+ });
142
+ }
143
+ stop() {
144
+ this.running = false;
145
+ this.rl?.close();
146
+ }
147
+ /** Access the readline interface (for REPL prompt control) */
148
+ getReadline() {
149
+ return this.rl;
150
+ }
151
+ async handleLine(line, output) {
152
+ const { protocol, connection, config } = this.options;
153
+ // Try protocol-local handling first (REPL: .help, .watch, etc.)
154
+ if (protocol.handleLocal && this.options.localContext) {
155
+ const localCtx = {
156
+ ...this.options.localContext,
157
+ print: (msg) => this.write(output, msg),
158
+ };
159
+ const localResult = await protocol.handleLocal(line, localCtx);
160
+ if (localResult !== null) {
161
+ if (localResult)
162
+ this.write(output, localResult);
163
+ return;
164
+ }
165
+ }
166
+ // Parse the input
167
+ const parsed = protocol.parse(line);
168
+ if (parsed === null)
169
+ return; // blank line or skip
170
+ if (isProtocolError(parsed)) {
171
+ this.write(output, protocol.formatError(null, parsed.code, parsed.message));
172
+ return;
173
+ }
174
+ // Dispatch to method handler
175
+ const handler = this.methods[parsed.method];
176
+ if (!handler) {
177
+ this.write(output, protocol.formatError(parsed, -32601, `Unknown method: ${parsed.method}`));
178
+ return;
179
+ }
180
+ try {
181
+ const result = await handler(parsed.params, connection, config);
182
+ const formatted = protocol.formatResult(parsed, result);
183
+ if (formatted)
184
+ this.write(output, formatted);
185
+ }
186
+ catch (err) {
187
+ const message = err instanceof Error ? err.message : String(err);
188
+ this.write(output, protocol.formatError(parsed, -32603, message));
189
+ }
190
+ }
191
+ write(output, data) {
192
+ output.write(data + '\n');
193
+ }
194
+ }
195
+ //# sourceMappingURL=session.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"session.js","sourceRoot":"","sources":["../../src/session/session.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,134 @@
1
+ # cjig.nvim
2
+
3
+ Neovim plugin for evaluating JavaScript/ClojureScript in Chrome via `cjig serve --stdio`.
4
+
5
+ Select code, eval in browser, see result in a floating window.
6
+
7
+ ## Requirements
8
+
9
+ - Neovim >= 0.9
10
+ - `cjig` installed and on `$PATH`
11
+ - Chrome running with debugging enabled (`cjig launch`)
12
+
13
+ ## Installation
14
+
15
+ Run once (and again after upgrading cjig or switching Node versions):
16
+
17
+ ```bash
18
+ cjig install-nvim
19
+ ```
20
+
21
+ This creates a stable symlink at `$XDG_DATA_HOME/cjig/editors/nvim` (default: `~/.local/share/cjig/editors/nvim`) pointing to the plugin inside the installed package. The command prints setup snippets — add the appropriate one to your Neovim config.
22
+
23
+ ### lazy.nvim
24
+
25
+ Create `lua/plugins/cjig.lua`:
26
+
27
+ ```lua
28
+ return { dir = "~/.local/share/cjig/editors/nvim" }
29
+ ```
30
+
31
+ ### packer
32
+
33
+ ```lua
34
+ use { "~/.local/share/cjig/editors/nvim" }
35
+ ```
36
+
37
+ ### Manual (init.lua)
38
+
39
+ ```lua
40
+ vim.opt.runtimepath:prepend(vim.fn.expand("~/.local/share/cjig/editors/nvim"))
41
+ require("cjig").setup()
42
+ ```
43
+
44
+ ### Uninstall
45
+
46
+ ```bash
47
+ cjig uninstall-nvim
48
+ ```
49
+
50
+ Then remove the corresponding line from your Neovim config.
51
+
52
+ ## Commands
53
+
54
+ | Command | Mode | Description |
55
+ |---|---|---|
56
+ | `:CjigConnect` | n | Spawn `cjig serve --stdio` subprocess |
57
+ | `:CjigDisconnect` | n | Kill the subprocess |
58
+ | `:CjigEval` | v | Eval visual selection in browser |
59
+ | `:CjigEvalForm` | n | Eval treesitter form at cursor |
60
+ | `:CjigTabs` | n | List open browser tabs |
61
+ | `:CjigInject <name>` | n | Inject a harness script by name |
62
+ | `:CjigReload` | n | Reload current browser tab |
63
+
64
+ ## Default Keymaps
65
+
66
+ All under `<leader>c` (configurable via `opts.leader`):
67
+
68
+ | Key | Mode | Action |
69
+ |---|---|---|
70
+ | `<leader>ce` | v | Eval selection |
71
+ | `<leader>cf` | n | Eval form at cursor |
72
+ | `<leader>ct` | n | List tabs |
73
+ | `<leader>cr` | n | Reload tab |
74
+ | `<leader>cc` | n | Connect |
75
+ | `<leader>cd` | n | Disconnect |
76
+
77
+ Disable with `require("cjig").setup({ keymaps = false })`.
78
+
79
+ ## Options
80
+
81
+ ```lua
82
+ require("cjig").setup({
83
+ keymaps = true, -- set false to skip default keymaps
84
+ leader = "<leader>c", -- keymap prefix
85
+ })
86
+ ```
87
+
88
+ ## Auto-connect
89
+
90
+ `:CjigEval` and `:CjigEvalForm` auto-connect on first use.
91
+ Explicit `:CjigConnect` is optional.
92
+
93
+ ## Language Detection
94
+
95
+ - `clojure` filetype -> sends `lang: "cljs"` (compiled via squint-cljs)
96
+ - Everything else -> `lang: "js"`
97
+
98
+ ## Treesitter Form Selection
99
+
100
+ `:CjigEvalForm` walks up the treesitter tree from the cursor to find the nearest evaluable node:
101
+
102
+ - `expression_statement` (trailing `;` stripped)
103
+ - `variable_declaration` / `lexical_declaration`
104
+ - `function_declaration`
105
+ - `call_expression`
106
+ - `assignment_expression`
107
+
108
+ ## Walkthrough
109
+
110
+ ```bash
111
+ # Terminal: launch Chrome and serve examples
112
+ cjig launch
113
+ pnpm serve:examples
114
+ ```
115
+
116
+ In Neovim:
117
+
118
+ ```
119
+ :CjigConnect " connects to Chrome on localhost:9222
120
+ :CjigTabs " floating window lists tabs
121
+
122
+ " Open a scratch .js file, type:
123
+ " document.title
124
+ " Visual select the line, then:
125
+ :'<,'>CjigEval " floating window shows page title
126
+
127
+ " Type: FX.burst(50)
128
+ " Place cursor on it:
129
+ :CjigEvalForm " particles appear in browser
130
+
131
+ :CjigInject fancy-demo " injects harness
132
+ :CjigReload " reloads tab
133
+ :CjigDisconnect " kills subprocess
134
+ ```
@@ -0,0 +1,226 @@
1
+ --- cjig.nvim — eval-from-editor via JSON-RPC over stdio.
2
+ ---
3
+ --- Commands: CjigConnect, CjigDisconnect, CjigEval, CjigEvalForm,
4
+ --- CjigTabs, CjigInject, CjigReload
5
+
6
+ local rpc = require("cjig.rpc")
7
+ local ui = require("cjig.ui")
8
+
9
+ local M = {}
10
+
11
+ -- Evaluable treesitter node types
12
+ local EVAL_NODES = {
13
+ expression_statement = true,
14
+ variable_declaration = true,
15
+ lexical_declaration = true,
16
+ function_declaration = true,
17
+ call_expression = true,
18
+ assignment_expression = true,
19
+ }
20
+
21
+ --- Detect language from filetype.
22
+ --- @return string "js" or "cljs"
23
+ local function detect_lang()
24
+ if vim.bo.filetype == "clojure" then
25
+ return "cljs"
26
+ end
27
+ return "js"
28
+ end
29
+
30
+ --- Get visual selection text.
31
+ --- @return string
32
+ local function get_visual_selection()
33
+ local _, srow, scol, _ = unpack(vim.fn.getpos("'<"))
34
+ local _, erow, ecol, _ = unpack(vim.fn.getpos("'>"))
35
+
36
+ if srow > erow or (srow == erow and scol > ecol) then
37
+ srow, erow = erow, srow
38
+ scol, ecol = ecol, scol
39
+ end
40
+
41
+ local lines = vim.api.nvim_buf_get_lines(0, srow - 1, erow, false)
42
+ if #lines == 0 then return "" end
43
+
44
+ if #lines == 1 then
45
+ lines[1] = lines[1]:sub(scol, ecol)
46
+ else
47
+ lines[1] = lines[1]:sub(scol)
48
+ lines[#lines] = lines[#lines]:sub(1, ecol)
49
+ end
50
+
51
+ return table.concat(lines, "\n")
52
+ end
53
+
54
+ --- Walk up treesitter tree to find nearest evaluable node.
55
+ --- @return string|nil
56
+ local function get_treesitter_form()
57
+ local ok, ts_utils = pcall(require, "nvim-treesitter.ts_utils")
58
+ local node
59
+
60
+ if ok then
61
+ node = ts_utils.get_node_at_cursor()
62
+ else
63
+ -- Fallback: use built-in treesitter
64
+ node = vim.treesitter.get_node()
65
+ end
66
+
67
+ if not node then return nil end
68
+
69
+ -- Walk up to nearest evaluable node
70
+ while node do
71
+ if EVAL_NODES[node:type()] then
72
+ local sr, sc, er, ec = node:range()
73
+ local lines = vim.api.nvim_buf_get_text(0, sr, sc, er, ec, {})
74
+ local text = table.concat(lines, "\n")
75
+
76
+ -- Strip trailing semicolon from expression statements
77
+ if node:type() == "expression_statement" then
78
+ text = text:gsub(";%s*$", "")
79
+ end
80
+
81
+ return text
82
+ end
83
+ node = node:parent()
84
+ end
85
+
86
+ return nil
87
+ end
88
+
89
+ --- Eval code string in browser, show result in float.
90
+ --- @param code string
91
+ local function eval_and_show(code)
92
+ if code == "" then
93
+ ui.show_error("No code to evaluate")
94
+ return
95
+ end
96
+
97
+ local lang = detect_lang()
98
+
99
+ rpc.ensure_connected(function()
100
+ vim.schedule(function()
101
+ rpc.request("eval", { code = code, lang = lang }, function(result)
102
+ vim.schedule(function()
103
+ ui.show_result(result)
104
+ end)
105
+ end, function(err)
106
+ vim.schedule(function()
107
+ ui.show_error(err)
108
+ end)
109
+ end)
110
+ end)
111
+ end)
112
+ end
113
+
114
+ --- Register all user commands.
115
+ local function register_commands()
116
+ vim.api.nvim_create_user_command("CjigConnect", function()
117
+ rpc.connect()
118
+ end, { desc = "Connect to Chrome via cjig serve" })
119
+
120
+ vim.api.nvim_create_user_command("CjigDisconnect", function()
121
+ rpc.disconnect()
122
+ end, { desc = "Disconnect from cjig" })
123
+
124
+ vim.api.nvim_create_user_command("CjigEval", function()
125
+ local code = get_visual_selection()
126
+ eval_and_show(code)
127
+ end, { range = true, desc = "Eval visual selection in browser" })
128
+
129
+ vim.api.nvim_create_user_command("CjigEvalForm", function()
130
+ local code = get_treesitter_form()
131
+ if not code then
132
+ ui.show_error("No evaluable form at cursor")
133
+ return
134
+ end
135
+ eval_and_show(code)
136
+ end, { desc = "Eval treesitter form at cursor" })
137
+
138
+ vim.api.nvim_create_user_command("CjigTabs", function()
139
+ rpc.ensure_connected(function()
140
+ vim.schedule(function()
141
+ rpc.request("tabs", {}, function(result)
142
+ vim.schedule(function()
143
+ if result and result.formatted then
144
+ ui.show_result(result.formatted)
145
+ else
146
+ ui.show_result(result)
147
+ end
148
+ end)
149
+ end, function(err)
150
+ vim.schedule(function()
151
+ ui.show_error(err)
152
+ end)
153
+ end)
154
+ end)
155
+ end)
156
+ end, { desc = "List browser tabs" })
157
+
158
+ vim.api.nvim_create_user_command("CjigInject", function(opts)
159
+ local ref = opts.args
160
+ if ref == "" then
161
+ ui.show_error("Usage: :CjigInject <name>")
162
+ return
163
+ end
164
+ rpc.ensure_connected(function()
165
+ vim.schedule(function()
166
+ rpc.request("inject", { ref = ref }, function(result)
167
+ vim.schedule(function()
168
+ if result and result.formatted then
169
+ vim.notify("cjig: " .. result.formatted, vim.log.levels.INFO)
170
+ else
171
+ ui.show_result(result)
172
+ end
173
+ end)
174
+ end, function(err)
175
+ vim.schedule(function()
176
+ ui.show_error(err)
177
+ end)
178
+ end)
179
+ end)
180
+ end)
181
+ end, { nargs = 1, desc = "Inject a harness script" })
182
+
183
+ vim.api.nvim_create_user_command("CjigReload", function()
184
+ rpc.ensure_connected(function()
185
+ vim.schedule(function()
186
+ rpc.request("reload", {}, function(_)
187
+ vim.schedule(function()
188
+ vim.notify("cjig: Reloaded", vim.log.levels.INFO)
189
+ end)
190
+ end, function(err)
191
+ vim.schedule(function()
192
+ ui.show_error(err)
193
+ end)
194
+ end)
195
+ end)
196
+ end)
197
+ end, { desc = "Reload current browser tab" })
198
+ end
199
+
200
+ --- Setup the plugin with optional config.
201
+ --- @param opts? table
202
+ function M.setup(opts)
203
+ opts = opts or {}
204
+
205
+ register_commands()
206
+
207
+ -- Default keymaps (disable with opts.keymaps = false)
208
+ if opts.keymaps ~= false then
209
+ local leader = opts.leader or "<leader>c"
210
+
211
+ vim.keymap.set("v", leader .. "e", ":<C-u>CjigEval<CR>",
212
+ { silent = true, desc = "cjig: eval selection" })
213
+ vim.keymap.set("n", leader .. "f", "<cmd>CjigEvalForm<CR>",
214
+ { silent = true, desc = "cjig: eval form" })
215
+ vim.keymap.set("n", leader .. "t", "<cmd>CjigTabs<CR>",
216
+ { silent = true, desc = "cjig: list tabs" })
217
+ vim.keymap.set("n", leader .. "r", "<cmd>CjigReload<CR>",
218
+ { silent = true, desc = "cjig: reload tab" })
219
+ vim.keymap.set("n", leader .. "c", "<cmd>CjigConnect<CR>",
220
+ { silent = true, desc = "cjig: connect" })
221
+ vim.keymap.set("n", leader .. "d", "<cmd>CjigDisconnect<CR>",
222
+ { silent = true, desc = "cjig: disconnect" })
223
+ end
224
+ end
225
+
226
+ return M