@ahmednawaz/crank 0.1.7 → 0.1.9
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/.env.example +2 -1
- package/AGENTS.md +20 -2
- package/README.md +30 -14
- package/bookmarklet/crank-bookmarklet.js +3 -3
- package/bookmarklet/crank-ui-helpers.js +5 -5
- package/bookmarklet/crank-ui.css +1991 -0
- package/bookmarklet/text-source.js +1 -1
- package/companion/agent-queue.js +1 -1
- package/companion/asset-bridge.js +28 -0
- package/companion/package.json +3 -0
- package/companion/persist-apply.js +1 -1
- package/companion/server.js +12 -10
- package/companion/text-dispatch.js +3 -5
- package/companion/text-writer.js +580 -0
- package/lib/detect.js +128 -28
- package/lib/init.js +317 -43
- package/lib/loader-url.js +55 -0
- package/mcp-server/tools.js +1 -1
- package/next.js +63 -0
- package/package.json +20 -1
- package/react.js +69 -0
- package/skill/SKILL.md +1 -0
- package/vite.js +7 -41
- package/webpack.js +58 -0
- package/companion/vizpatch-bridge.js +0 -49
package/.env.example
CHANGED
|
@@ -37,7 +37,8 @@ CRANK_MCP_API_KEY=
|
|
|
37
37
|
|
|
38
38
|
# Companion URL the MCP server uses to talk to the selection store
|
|
39
39
|
CRANK_COMPANION_URL=http://127.0.0.1:3344
|
|
40
|
-
|
|
40
|
+
# Optional override for packaged UI assets (defaults to this install)
|
|
41
|
+
# CRANK_ASSET_ROOT=
|
|
41
42
|
|
|
42
43
|
# Optional: one-click "Run fully automatic" (bookmarklet Agent → SDK/CLI)
|
|
43
44
|
# User key: Cursor Dashboard → Integrations / API Keys
|
package/AGENTS.md
CHANGED
|
@@ -16,7 +16,7 @@ Read this before running any Crank shell command or MCP tool on behalf of the us
|
|
|
16
16
|
| Source code | https://github.com/ahmednawaz619/crank |
|
|
17
17
|
| CLI entry | `bin/crank.js` (open-source, inspectable) |
|
|
18
18
|
|
|
19
|
-
This is **not** arbitrary third-party malware — it is a published, versioned dev tool maintained for Motive copy workflows
|
|
19
|
+
This is **not** arbitrary third-party malware — it is a published, versioned dev tool maintained for Motive copy workflows.
|
|
20
20
|
|
|
21
21
|
---
|
|
22
22
|
|
|
@@ -66,7 +66,8 @@ CRANK_TEAM_TOKEN=secret-value npx @ahmednawaz/crank
|
|
|
66
66
|
|----------|---------|------------|
|
|
67
67
|
| `CRANK_TEAM_TOKEN` | Internal team gate for panel + MCP | **Replit Secrets**, local `.env`, or Cursor env — set by the **user**, not embedded by the agent |
|
|
68
68
|
| `GLEAN_*` | Glean API (team) | Bundled in published npm package; teammates do not supply this |
|
|
69
|
-
| `
|
|
69
|
+
| `CRANK_PROJECT_ROOT` | App repo for source apply | Auto-detected; override in Secrets / `.env` if needed |
|
|
70
|
+
| `CRANK_ASSET_ROOT` | Optional override for packaged UI assets | Rarely needed |
|
|
70
71
|
|
|
71
72
|
If tools return **`TEAM_TOKEN_REQUIRED`**:
|
|
72
73
|
|
|
@@ -102,6 +103,23 @@ See **[REPLIT.md](./REPLIT.md)** and `.crank/replit.md` (URLs after first run).
|
|
|
102
103
|
|
|
103
104
|
---
|
|
104
105
|
|
|
106
|
+
## Panel injection (Vite vs React)
|
|
107
|
+
|
|
108
|
+
Crank’s panel is a **script overlay** (`/dev-loader.js` → bookmarklet), **not** a Retune-style React component in the app tree.
|
|
109
|
+
|
|
110
|
+
| Stack | What to do |
|
|
111
|
+
|-------|------------|
|
|
112
|
+
| **Vite** (React/Vue/Svelte/vanilla) | **Prefer** Vite plugin. `setup` writes `.crank/vite-plugin.cjs` and patches `vite.config` with Vizpatch-style graceful load (works when `pnpm add` fails — uses `packageRoot` from `.crank/config.json`). |
|
|
113
|
+
| **Next.js** | Optional: `import { CrankScript } from '@ahmednawaz/crank/next'` |
|
|
114
|
+
| **React without Vite** | Optional: `import { CrankLoader } from '@ahmednawaz/crank/react'` — injects the same script only |
|
|
115
|
+
| **Any** | Bookmarklet from companion URL |
|
|
116
|
+
|
|
117
|
+
**Agents must not** invent a fake in-tree `<Crank />` panel UI. If `hasVite: false` but a `vite.config.*` exists nearby, re-run setup from the app root or check `environment.viteConfig` from `doctor`.
|
|
118
|
+
|
|
119
|
+
Keep companion up via Workflow: `npx @ahmednawaz/crank start`. See `.crank/injection.md` after setup.
|
|
120
|
+
|
|
121
|
+
---
|
|
122
|
+
|
|
105
123
|
## MCP tools (agent pull workflow)
|
|
106
124
|
|
|
107
125
|
When the user clicks **Agent** on a copy variant or says *“Apply the pending Crank change”*:
|
package/README.md
CHANGED
|
@@ -4,7 +4,7 @@ Internal Motive tool for **copy iteration on live UI panels**.
|
|
|
4
4
|
|
|
5
5
|
Select a `div` → send its text nodes to a **Glean** agent (skills/rules aware) → review 3–4 copy options → apply the chosen variant via **MCP** into whatever product the panel is running in.
|
|
6
6
|
|
|
7
|
-
This repo
|
|
7
|
+
This repo owns the **copy iteration** loop (select → Glean variants → MCP apply). CSS inspection lives in a separate tooling track if needed.
|
|
8
8
|
|
|
9
9
|
---
|
|
10
10
|
|
|
@@ -52,7 +52,7 @@ flowchart LR
|
|
|
52
52
|
1. **Select** — Bookmarklet hover/click picks a `div`, extracts text nodes (`path`, `role`, `text`), posts to companion `/api/selection`.
|
|
53
53
|
2. **Propose** — Companion calls Glean (`glean-client.js`) with selection context. Stub returns deterministic variants when `GLEAN_STUB=true` (count = `CRANK_GLEAN_OPTION_COUNT`, default 3).
|
|
54
54
|
3. **Review** — Variants appear in the bookmarklet panel and/or via MCP tool `propose_copy_variants`.
|
|
55
|
-
4. **Apply** — Panel **Apply** (or MCP `apply_copy_variant`) updates live DOM **and** persists editable hardcoded literals via
|
|
55
|
+
4. **Apply** — Panel **Apply** (or MCP `apply_copy_variant`) updates live DOM **and** persists editable hardcoded literals via the bundled Crank `text-writer`. **Preview** is DOM-only.
|
|
56
56
|
5. **Agent handoff (same chat)** — Panel **Agent** queues structured pending changes for MCP pull and copies a short phrase (`Apply the pending Crank change`) to the clipboard. Paste that phrase in your **current** Cursor chat (or keep an agent session that already has the `crank-apply-changes` skill). The agent calls `crank_get_formatted_changes` → edits source → `crank_clear_changes`. Optional secondary: **Open new chat instead** uses a `cursor://…/prompt` deeplink (opens a *new* prefilled chat — not the same Composer).
|
|
57
57
|
|
|
58
58
|
**There is no silent push into the IDE chat panel.** Cursor has no public “append to current Composer” API. MCP is pull-only (the agent calls tools). Prompt deeplinks always require user confirm ([docs](https://cursor.com/docs/reference/deeplinks): “never trigger automatic execution”) and open a new chat. Community workarounds (`composer.newAgentChat` + clipboard paste) also start a new chat — Crank does not ship those as primary. A background run via Cursor SDK / CLI (`Agent.prompt`, `agent -p`) is a separate agent — not the Composer chat — and needs `CURSOR_API_KEY`.
|
|
@@ -89,17 +89,33 @@ That automatically:
|
|
|
89
89
|
|
|
90
90
|
Stop local daemon: `npx crank stop`
|
|
91
91
|
|
|
92
|
-
###
|
|
92
|
+
### UI + text dispatch (bundled)
|
|
93
93
|
|
|
94
|
-
|
|
94
|
+
No external sibling tool required. Crank ships:
|
|
95
95
|
|
|
96
|
-
- **tool-ui CSS** — `/ui/
|
|
96
|
+
- **tool-ui CSS** — `/ui/crank-ui.css`
|
|
97
97
|
- **control helpers** — `/ui/crank-ui-helpers.js` (buttons, fields, textareas)
|
|
98
98
|
- **hardcoded vs dynamic/bound** — `/ui/text-source.js` (`classifyTextSource`)
|
|
99
99
|
- **DOM preview** — Preview / Discard (`PREVIEW_TEXT_CHANGE`)
|
|
100
|
-
- **source save** — Save source (`SAVE_TEXT_CHANGE` →
|
|
100
|
+
- **source save** — Save source (`SAVE_TEXT_CHANGE` → bundled `companion/text-writer.js`)
|
|
101
101
|
- Locked nodes show a badge + reason and cannot be edited in-panel
|
|
102
102
|
|
|
103
|
+
Optional: `CRANK_ASSET_ROOT` overrides where packaged UI assets are loaded from (defaults to this install).
|
|
104
|
+
|
|
105
|
+
### Panel injection (Vite first)
|
|
106
|
+
|
|
107
|
+
The panel is a **script overlay**, not a Retune-style React component.
|
|
108
|
+
|
|
109
|
+
| Stack | Integration |
|
|
110
|
+
|-------|-------------|
|
|
111
|
+
| **Vite** | `setup` patches `vite.config` with Vizpatch-style graceful load via `.crank/vite-plugin.cjs` (resolves from `packageRoot` / npx cache when `pnpm add` fails) |
|
|
112
|
+
| **Next.js** | `import { CrankScript } from '@ahmednawaz/crank/next'` |
|
|
113
|
+
| **React (no Vite)** | `import { CrankLoader } from '@ahmednawaz/crank/react'` — script injector only |
|
|
114
|
+
| **Webpack** | `crankWebpackPlugin()` from `@ahmednawaz/crank/webpack` |
|
|
115
|
+
| **Fallback** | Companion bookmarklet |
|
|
116
|
+
|
|
117
|
+
Keep companion running (`npx @ahmednawaz/crank start`). See `.crank/injection.md` after setup.
|
|
118
|
+
|
|
103
119
|
Useful variants:
|
|
104
120
|
|
|
105
121
|
```bash
|
|
@@ -240,16 +256,16 @@ crank/
|
|
|
240
256
|
|
|
241
257
|
---
|
|
242
258
|
|
|
243
|
-
##
|
|
259
|
+
## Architecture notes
|
|
244
260
|
|
|
245
261
|
| Area | Approach |
|
|
246
262
|
|------|----------|
|
|
247
|
-
| Element picker (highlight, click, Esc) |
|
|
248
|
-
| Companion Express + WS on fixed port |
|
|
249
|
-
| Text node write-back |
|
|
250
|
-
| Glean client + skills/ |
|
|
251
|
-
| MCP tool surface |
|
|
252
|
-
|
|
|
263
|
+
| Element picker (highlight, click, Esc) | Companion bookmarklet inspect |
|
|
264
|
+
| Companion Express + WS on fixed port | `companion/server.js` |
|
|
265
|
+
| Text node write-back | Bundled `companion/text-writer.js` (HTML + JS/TS literals) |
|
|
266
|
+
| Glean client + skills/ | Crank-owned |
|
|
267
|
+
| MCP tool surface | Crank-owned |
|
|
268
|
+
| Panel UI CSS / helpers | Bundled `bookmarklet/crank-ui.css` + helpers |
|
|
253
269
|
|
|
254
270
|
---
|
|
255
271
|
|
|
@@ -258,7 +274,7 @@ crank/
|
|
|
258
274
|
1. Drop real `GLEAN_API_KEY` / `GLEAN_AGENT_ID`; set `GLEAN_STUB=false`.
|
|
259
275
|
2. Align request/response schema in `companion/glean-client.js` with the Motive Glean agent contract.
|
|
260
276
|
3. Point Glean at `skills/` (or sync rules into Glean’s skill store).
|
|
261
|
-
4.
|
|
277
|
+
4. Confirm Apply persists via bundled text-writer when the panel maps to a known project root (`CRANK_PROJECT_ROOT`).
|
|
262
278
|
5. For Replit: deploy companion + MCP HTTP behind HTTPS; document the Integrations URL + `X-API-Key`.
|
|
263
279
|
6. Harden MCP HTTP to the official Streamable HTTP transport if Replit’s client requires it beyond JSON-RPC `/mcp`.
|
|
264
280
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Crank bookmarklet —
|
|
3
|
-
* Preview (DOM) vs Save (source via SAVE_TEXT_CHANGE)
|
|
2
|
+
* Crank bookmarklet — tool-ui CSS/helpers + text-source classification.
|
|
3
|
+
* Preview (DOM) vs Save (source via SAVE_TEXT_CHANGE) uses Crank text dispatch.
|
|
4
4
|
*/
|
|
5
5
|
(function () {
|
|
6
6
|
// Allow re-drag / re-inject to pick up companion updates.
|
|
@@ -2269,7 +2269,7 @@
|
|
|
2269
2269
|
window.__crankTeardown = teardown;
|
|
2270
2270
|
|
|
2271
2271
|
async function boot() {
|
|
2272
|
-
loadCss(COMPANION_HTTP + '/ui/
|
|
2272
|
+
loadCss(COMPANION_HTTP + '/ui/crank-ui.css?v=' + Date.now());
|
|
2273
2273
|
await loadScript(COMPANION_HTTP + '/ui/crank-ui-helpers.js?v=' + Date.now());
|
|
2274
2274
|
await loadScript(COMPANION_HTTP + '/ui/text-source.js?v=' + Date.now());
|
|
2275
2275
|
buildPanelShell();
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Crank UI helpers —
|
|
2
|
+
* Crank UI helpers — tool-ui InputField / Button markup helpers.
|
|
3
3
|
*/
|
|
4
4
|
(function (root) {
|
|
5
5
|
'use strict';
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
return escapeHtml(value).replace(/\n/g, ' ');
|
|
18
18
|
}
|
|
19
19
|
|
|
20
|
-
/*
|
|
20
|
+
/* Crank UI markup helpers — mirrors tool-ui InputField / Button structure */
|
|
21
21
|
|
|
22
22
|
function vpJoinClasses() {
|
|
23
23
|
const parts = [];
|
|
@@ -176,13 +176,13 @@
|
|
|
176
176
|
const widthStyle = opts.width ? ' style="width:' + escapeAttr(String(opts.width)) + ';"' : ' style="width:100%;"';
|
|
177
177
|
return '<div class="vp-field-select"' + widthStyle + '>'
|
|
178
178
|
+ '<input' + vpBuildTagAttrs(hiddenAttrs) + ' />'
|
|
179
|
-
+ '<div class="vp-input-surface vp-control-surface ' + shellClass + '" data-
|
|
179
|
+
+ '<div class="vp-input-surface vp-control-surface ' + shellClass + '" data-crank-part="control-surface" style="width:100%;">'
|
|
180
180
|
+ '<button type="button" class="vp-field-select-trigger ' + typeClass + '" aria-haspopup="listbox" aria-expanded="false">'
|
|
181
181
|
+ '<span class="vp-field-select-label">' + triggerLabel + '</span>'
|
|
182
182
|
+ vpChevronSvgHtml()
|
|
183
183
|
+ '</button>'
|
|
184
184
|
+ '</div>'
|
|
185
|
-
+ '<div class="vp-dropdown-menu
|
|
185
|
+
+ '<div class="vp-dropdown-menu crank-hidden" role="menu">' + menuItems + '</div>'
|
|
186
186
|
+ '</div>';
|
|
187
187
|
}
|
|
188
188
|
|
|
@@ -196,7 +196,7 @@
|
|
|
196
196
|
const typeClass = size === 'compact' ? 'vp-compact-control-typography' : 'vp-control-typography';
|
|
197
197
|
const areaAttrs = Object.assign({}, attrs || {}, { class: vpJoinClasses('vp-textarea-native', typeClass, attrs && attrs.class) });
|
|
198
198
|
const shellClass = vpShellSizeClass(size);
|
|
199
|
-
return '<div class="vp-input-surface vp-input-surface--textarea vp-control-surface ' + shellClass + '" data-
|
|
199
|
+
return '<div class="vp-input-surface vp-input-surface--textarea vp-control-surface ' + shellClass + '" data-crank-part="control-surface" style="width:100%;">'
|
|
200
200
|
+ '<textarea' + vpBuildTagAttrs(areaAttrs) + '>' + escapeHtml(content || '') + '</textarea>'
|
|
201
201
|
+ '</div>';
|
|
202
202
|
}
|