@ahmednawaz/crank 0.2.7 → 0.3.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.
package/react.mjs CHANGED
@@ -1,22 +1,37 @@
1
1
  /**
2
- * Retune-style React entry — browser-only, Vite-bundlable (no Node/fs).
2
+ * Crank React entry — Retune-style: panel UI is **bundled with the app**, not
3
+ * fetched from companion :3344 (that broke Replit iframe previews).
3
4
  *
4
- * // main.tsx
5
5
  * import { Crank } from '@ahmednawaz/crank/react'
6
- * // or: import { Crank } from '@ahmednawaz/crank'
7
- *
8
6
  * <Crank />
9
7
  *
10
- * Mount near your app root. Injects companion `/dev-loader.js` once (same panel as the
11
- * optional Vite plugin). Requires the companion process: `npx @ahmednawaz/crank start`
12
- *
13
8
  * peerDependency: react >= 17
14
9
  */
15
10
 
16
11
  import { useEffect } from 'react';
12
+ import { bootCrankPanel } from './panel-boot.mjs';
17
13
 
18
14
  /**
19
- * @param {{ companionUrl?: string, url?: string, src?: string }} [props]
15
+ * Replit public companion host from the current preview hostname.
16
+ * @returns {string | null}
17
+ */
18
+ export function detectReplitCompanionUrl() {
19
+ if (typeof window === 'undefined') {
20
+ return null;
21
+ }
22
+ try {
23
+ const host = window.location.hostname || '';
24
+ if (!/\.replit\.dev$|\.repl\.co$|\.replit\.app$/.test(host)) {
25
+ return null;
26
+ }
27
+ return `https://3344-${host.replace(/^\d+-/, '')}`;
28
+ } catch {
29
+ return null;
30
+ }
31
+ }
32
+
33
+ /**
34
+ * @param {{ companionUrl?: string, url?: string, src?: string, force?: boolean }} [props]
20
35
  * @returns {string}
21
36
  */
22
37
  export function resolveCrankCompanionUrl(props = {}) {
@@ -40,10 +55,15 @@ export function resolveCrankCompanionUrl(props = {}) {
40
55
  } catch {
41
56
  // ignore
42
57
  }
43
- return 'http://localhost:3344';
58
+ const replit = detectReplitCompanionUrl();
59
+ if (replit) {
60
+ return replit;
61
+ }
62
+ return 'http://127.0.0.1:3344';
44
63
  }
45
64
 
46
65
  /**
66
+ * @deprecated Panel loads from the package bundle. Prefer omitting src.
47
67
  * @param {{ companionUrl?: string, url?: string, src?: string }} [props]
48
68
  * @returns {string}
49
69
  */
@@ -54,35 +74,70 @@ export function resolveCrankLoaderSrc(props = {}) {
54
74
  return `${resolveCrankCompanionUrl(props)}/dev-loader.js`;
55
75
  }
56
76
 
77
+ function isDevMode(force) {
78
+ if (force) {
79
+ return true;
80
+ }
81
+ try {
82
+ if (typeof import.meta !== 'undefined' && import.meta.env && import.meta.env.DEV) {
83
+ return true;
84
+ }
85
+ } catch {
86
+ // ignore
87
+ }
88
+ try {
89
+ if (typeof process !== 'undefined' && process.env && process.env.NODE_ENV === 'development') {
90
+ return true;
91
+ }
92
+ } catch {
93
+ // ignore
94
+ }
95
+ // Default on — Crank is a dev tool; force=false only hides when clearly production.
96
+ try {
97
+ if (typeof process !== 'undefined' && process.env && process.env.NODE_ENV === 'production') {
98
+ return false;
99
+ }
100
+ } catch {
101
+ // ignore
102
+ }
103
+ return true;
104
+ }
105
+
57
106
  /**
58
- * Retune-style one-liner. Renders null; injects the companion loader script on the client.
107
+ * Mount near app root (Retune pattern). Boots the bundled panel once.
59
108
  *
60
- * Do not set window.__CRANK_LOADER__ here `/dev-loader.js` owns that flag and loads the panel.
61
- *
62
- * @param {{ companionUrl?: string, url?: string, src?: string }} [props]
109
+ * @param {{ companionUrl?: string, url?: string, src?: string, force?: boolean }} [props]
63
110
  */
64
111
  export function Crank(props = {}) {
65
- const src = resolveCrankLoaderSrc(props);
66
-
67
112
  useEffect(() => {
68
113
  if (typeof document === 'undefined') {
69
114
  return undefined;
70
115
  }
71
- if (document.querySelector('script[data-crank-loader="1"]')) {
116
+ if (!isDevMode(props.force)) {
117
+ return undefined;
118
+ }
119
+ if (document.getElementById('vp-shell') || document.getElementById('vp-crank-reopen')) {
72
120
  return undefined;
73
121
  }
74
- // Panel already live (bookmarklet / prior mount)
75
- if (window.__CRANK_LOADER__ && document.getElementById('vp-shell')) {
122
+
123
+ // Legacy: explicit src still supported (companion-hosted loader)
124
+ if (props.src) {
125
+ if (document.querySelector('script[data-crank-loader="1"]')) {
126
+ return undefined;
127
+ }
128
+ const script = document.createElement('script');
129
+ script.src = props.src.includes('?') ? props.src : `${props.src}?t=${Date.now()}`;
130
+ script.async = true;
131
+ script.dataset.crankLoader = '1';
132
+ document.documentElement.appendChild(script);
76
133
  return undefined;
77
134
  }
78
135
 
79
- const script = document.createElement('script');
80
- script.src = src.includes('?') ? src : `${src}?t=${Date.now()}`;
81
- script.async = true;
82
- script.dataset.crankLoader = '1';
83
- document.documentElement.appendChild(script);
136
+ bootCrankPanel({
137
+ companionUrl: resolveCrankCompanionUrl(props)
138
+ });
84
139
  return undefined;
85
- }, [src]);
140
+ }, [props.companionUrl, props.url, props.src, props.force]);
86
141
 
87
142
  return null;
88
143
  }
package/skill/SKILL.md CHANGED
@@ -30,7 +30,7 @@ When the user asks to start Crank, **run**:
30
30
  npx @ahmednawaz/crank@latest
31
31
  ```
32
32
 
33
- (one-shot: setup + inject + start). Never put `CRANK_TEAM_TOKEN` in the command. On Replit, Secrets only; do not edit `.replit`.
33
+ (one-shot: setup + inject + start). Then **restart the Vite / dev server** so `<Crank />` mounts the **bundled** panel (same-origin — not a script from :3344). Never put `CRANK_TEAM_TOKEN` in the command. On Replit, Secrets only; do not edit `.replit`.
34
34
 
35
35
  After companion is up: **`crank_get_environment`**. Apply copy via MCP queue only.
36
36
 
package/vite.js CHANGED
@@ -2,30 +2,55 @@
2
2
 
3
3
  /**
4
4
  * Vite / Rolldown-vite / Vitest-compatible plugin.
5
- * Injects Crank into the app during `vite` / `npm run dev` — no React component needed.
6
5
  *
7
- * // vite.config.ts
8
- * import { crank } from '@ahmednawaz/crank/vite'
9
- * // or: import { crank } from 'crank/vite'
10
- * export default { plugins: [crank()] }
6
+ * - Proxies `/@crank/*` → companion (same-origin; required on Replit iframe previews)
7
+ * - Optionally injects the loader into index.html (skip when using React `<Crank />`)
11
8
  *
12
- * Prefer this for Vite apps (React, Vue, Svelte, vanilla). The panel is a script overlay,
13
- * not a component in your React tree.
9
+ * import { crank } from '@ahmednawaz/crank/vite'
10
+ * export default { plugins: [crank()] } // Vite-only apps
11
+ * export default { plugins: [crank({ inject: false })] } // React + <Crank />
14
12
  */
15
13
 
16
- const { resolveCompanionLoaderUrl } = require('./lib/loader-url');
14
+ const { resolveCompanionLoaderUrl, resolveCompanionProxyTarget } = require('./lib/loader-url');
15
+
16
+ const PROXY_PREFIX = '/@crank';
17
17
 
18
18
  function crank(options = {}) {
19
- const src = resolveCompanionLoaderUrl(options);
19
+ const inject = options.inject !== false;
20
+ const target = resolveCompanionProxyTarget(options);
21
+ const src =
22
+ options.src ||
23
+ `${PROXY_PREFIX}/dev-loader.js`;
20
24
 
21
25
  return {
22
26
  name: 'crank-dev-loader',
23
27
  apply: 'serve',
28
+ config() {
29
+ return {
30
+ server: {
31
+ proxy: {
32
+ [PROXY_PREFIX]: {
33
+ target,
34
+ changeOrigin: true,
35
+ ws: true,
36
+ rewrite: (p) => p.replace(new RegExp(`^${PROXY_PREFIX}`), '') || '/'
37
+ }
38
+ }
39
+ }
40
+ };
41
+ },
24
42
  transformIndexHtml(html) {
25
- if (String(html).includes('/dev-loader.js') || String(html).includes('crank/dev-loader')) {
43
+ if (!inject) {
44
+ return html;
45
+ }
46
+ if (
47
+ String(html).includes('/dev-loader.js') ||
48
+ String(html).includes('crank/dev-loader') ||
49
+ String(html).includes('/@crank/')
50
+ ) {
26
51
  return html;
27
52
  }
28
- const tag = `<script src="${src}" defer></script>`;
53
+ const tag = `<script src="${src}?t=${Date.now()}" defer></script>`;
29
54
  if (html.includes('</head>')) {
30
55
  return html.replace('</head>', ` ${tag}\n</head>`);
31
56
  }
@@ -37,5 +62,6 @@ function crank(options = {}) {
37
62
  module.exports = {
38
63
  crank,
39
64
  crankDevLoader: crank,
40
- resolveCompanionLoaderUrl
65
+ resolveCompanionLoaderUrl,
66
+ PROXY_PREFIX
41
67
  };
package/panel/demo.html DELETED
@@ -1,62 +0,0 @@
1
- <!doctype html>
2
- <html lang="en">
3
- <head>
4
- <meta charset="utf-8" />
5
- <title>Crank panel demo</title>
6
- <style>
7
- :root {
8
- --ink: #1a1a1a;
9
- --muted: #555;
10
- --accent: #0f766e;
11
- --bg: #f4f6f5;
12
- }
13
- body {
14
- margin: 0;
15
- font: 16px/1.5 ui-sans-serif, system-ui, sans-serif;
16
- color: var(--ink);
17
- background:
18
- radial-gradient(circle at 10% 0%, #d9eee9 0%, transparent 45%),
19
- radial-gradient(circle at 90% 10%, #e8eef2 0%, transparent 40%),
20
- var(--bg);
21
- min-height: 100vh;
22
- }
23
- main {
24
- max-width: 42rem;
25
- margin: 0 auto;
26
- padding: 3rem 1.25rem;
27
- }
28
- .card {
29
- background: #fff;
30
- border: 1px solid #d8d8d8;
31
- border-radius: 12px;
32
- padding: 1.5rem;
33
- }
34
- h1 { margin: 0 0 0.5rem; font-size: 1.75rem; }
35
- p { margin: 0 0 1rem; color: var(--muted); }
36
- button {
37
- border: 0;
38
- background: var(--accent);
39
- color: #fff;
40
- padding: 0.55rem 0.95rem;
41
- border-radius: 8px;
42
- font-weight: 600;
43
- cursor: pointer;
44
- }
45
- .hint { font-size: 0.9rem; margin-top: 1.25rem; }
46
- code { background: #eee; padding: 0.1em 0.35em; border-radius: 4px; }
47
- </style>
48
- </head>
49
- <body>
50
- <main>
51
- <div id="hero" class="card">
52
- <h1>Utilize our robust platform today</h1>
53
- <p>Seamless fleet insights for every operator.</p>
54
- <button type="button">Get started</button>
55
- </div>
56
- <p class="hint">
57
- Start the companion (<code>npm run companion</code>), load the Crank bookmarklet,
58
- click <strong>Select</strong>, then click this card’s outer <code>div</code>.
59
- </p>
60
- </main>
61
- </body>
62
- </html>