cc-prompter 0.2.0 → 0.3.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/README.md CHANGED
@@ -1,8 +1,16 @@
1
1
  # CC Prompter
2
2
 
3
- **Shift+Alt 点击页面元素 → Claude Code 直接改代码 → 页面自动刷新**
3
+ **哪里不对点哪里,So easy!**
4
+
5
+ 一个 Vite / Next.js 插件,将 [Claude Code CLI](https://docs.anthropic.com/en/docs/claude-code) 接入你的前端开发工作流。通过可视化元素定位 + 常驻 PTY 会话,实现「点击 → 描述 → 代码自动修改 → HMR 刷新」的闭环。
6
+
7
+ > 🎯 **精确定位修改点** — 不再需要费劲描述「左上角那个蓝色按钮」或者截图了,直接点击页面元素,源码路径、行列号自动带上。
8
+ >
9
+ > 💰 **节省 tokens 神器** — 不需要让 Claude 反复探索项目结构、搜索文件、猜测位置,一个点击搞定所有上下文。
10
+ >
11
+ > ⚡ **所见即所得** — 改完自动 HMR 刷新,边看边改,开发体验拉满。
4
12
 
5
- 一个 Vite 插件,将 [Claude Code CLI](https://docs.anthropic.com/en/docs/claude-code) 接入你的前端开发工作流。通过可视化元素定位 + 常驻 PTY 会话,实现「点击 描述 → 代码自动修改 → HMR 刷新」的闭环。
13
+ **Shift+Alt 点击页面元素 Claude Code 直接改代码页面自动刷新**
6
14
 
7
15
  ![CC Prompter Demo](./demo.png)
8
16
 
@@ -44,7 +52,7 @@ npm install @homebridge/node-pty-prebuilt-multiarch --ignore-scripts
44
52
 
45
53
  > 前提:本地需要已安装 [Claude Code CLI](https://docs.anthropic.com/en/docs/claude-code)(终端执行 `claude` 命令可用)。插件仅在 dev 模式生效,`npm run build` 不会注入任何代码。
46
54
 
47
- ### 配置
55
+ ### 配置(Vite)
48
56
 
49
57
  在 `vite.config.ts` 中添加插件(一行配置即可):
50
58
 
@@ -61,17 +69,50 @@ export default defineConfig({
61
69
  });
62
70
  ```
63
71
 
64
- > `ccPromptPlugin()` 内部自动完成三件事:
72
+ ### 配置(Next.js)
73
+
74
+ 在 `next.config.js` 中使用 `withCcPrompt` 包装:
75
+
76
+ ```javascript
77
+ const { withCcPrompt } = require('cc-prompter/next');
78
+
79
+ module.exports = withCcPrompt()({
80
+ // ... 正常 Next.js 配置
81
+ });
82
+ ```
83
+
84
+ > `withCcPrompt()` 内部自动完成三件事:
65
85
  > 1. 集成 `code-inspector-plugin`(编译时给 DOM 打上源码路径标签)
66
86
  > 2. 启动 Express sidecar 服务(管理 Claude PTY 会话)
67
- > 3. 注入交互脚本(监听 Shift+Alt 点击,弹出面板)
87
+ > 3. 通过 webpack entry 注入交互脚本(监听 Shift+Alt 点击,弹出面板)
88
+ >
89
+ > 支持 Next.js Pages Router + App Router(webpack 模式)。Turbopack 暂不支持。
90
+
91
+ ### 配置(Webpack)
92
+
93
+ 在 `webpack.config.js` 中添加插件(适用于任意 webpack 项目):
94
+
95
+ ```javascript
96
+ const { CcPromptWebpackPlugin } = require('cc-prompter/webpack');
97
+
98
+ module.exports = {
99
+ plugins: [
100
+ new CcPromptWebpackPlugin(), // ← 仅 dev 模式生效
101
+ // ... 其他插件
102
+ ],
103
+ };
104
+ ```
105
+
106
+ > `CcPromptWebpackPlugin` 是一个标准 webpack Plugin,自动完成与 Next.js 版相同的三件事。
107
+ > 默认在 `NODE_ENV !== 'production'` 时激活,也可手动传 `dev: true/false`。
68
108
 
69
109
  ### 一键安装(给 Claude Code 用)
70
110
 
71
111
  复制下面这行,粘贴给 Claude Code,它会自动帮你完成安装和配置:
72
112
 
73
113
  ```
74
- 帮我安装 cc-prompter 并在 vite.config.ts 中添加 ccPromptPlugin() 插件配置,这是一个 Vite 插件,用于集成 Claude Code CLI 和 code-inspector 元素定位功能。安装方法:
114
+ 帮我安装 cc-prompter 并配置。这是一个 Vite/Next.js 插件,用于集成 Claude Code CLI 和 code-inspector 元素定位功能。
115
+ 安装方法:
75
116
  - macOS / Linux: 直接 npm install cc-prompter 即可
76
117
  - Windows: npm install cc-prompter 后,还需要安装 PTY 原生模块:
77
118
  1. cd node_modules/cc-prompter
@@ -80,9 +121,17 @@ export default defineConfig({
80
121
  4. 解压到 node_modules/@homebridge/node-pty-prebuilt-multiarch/build/Release/
81
122
  如果 GitHub 下载超时,使用 ghfast.top 镜像。
82
123
 
83
- 配置方法:在 vite.config.ts 的 plugins 数组中添加 ccPromptPlugin(),示例:
124
+ Vite 配置方法:在 vite.config.ts 的 plugins 数组中添加 ccPromptPlugin(),示例:
84
125
  import { ccPromptPlugin } from 'cc-prompter';
85
126
  plugins: [ccPromptPlugin(), react()]
127
+
128
+ Next.js 配置方法:在 next.config.js 中使用 withCcPrompt 包装,示例:
129
+ const { withCcPrompt } = require('cc-prompter/next');
130
+ module.exports = withCcPrompt()({ /* next config */ });
131
+
132
+ Webpack 配置方法:在 webpack.config.js 中添加 CcPromptWebpackPlugin,示例:
133
+ const { CcPromptWebpackPlugin } = require('cc-prompter/webpack');
134
+ plugins: [new CcPromptWebpackPlugin()]
86
135
  ```
87
136
 
88
137
  ## 怎么用
@@ -133,7 +182,7 @@ Claude 正在生成时,点击输入框旁边的 **Stop** 按钮,发送 Escap
133
182
  - ⚡ **流式渲染** — Markdown 实时渲染,工具调用按序展示,进度指示
134
183
  - ⏹ **随时中断** — Stop 按钮打断 Claude 生成,会话不销毁
135
184
  - 🖼 **灵活面板** — 浮动 iframe,拖拽移动、边缘缩放、快捷键召回
136
- - 🔌 **零配置** — 一行 `ccPromptPlugin()` 搞定所有集成
185
+ - 🔌 **零配置** — 一行配置搞定所有集成(Vite / Next.js 均支持)
137
186
  - 🏭 **零侵入** — 仅 dev 模式生效,生产构建不注入任何代码
138
187
 
139
188
  ## 构建工具兼容性
@@ -142,10 +191,11 @@ Claude 正在生成时,点击输入框旁边的 **Stop** 按钮,发送 Escap
142
191
 
143
192
  | 构建工具 | CC Prompter 状态 | 说明 |
144
193
  |----------|-----------------|------|
145
- | **Vite** | ✅ 完整支持 | 默认配置 |
146
- | Webpack | 🔜 计划中 | code-inspector 已支持,需适配 sidecar 启动逻辑 |
194
+ | **Vite** | ✅ 完整支持 | `ccPromptPlugin()` — 默认配置 |
195
+ | **Webpack** | 完整支持 | `CcPromptWebpackPlugin` 通用 webpack 插件 |
196
+ | **Next.js (webpack)** | ✅ 完整支持 | `withCcPrompt()` — Pages + App Router |
197
+ | Next.js (Turbopack) | 🔜 计划中 | code-inspector 已支持 |
147
198
  | esbuild | 🔜 计划中 | code-inspector 已支持 |
148
- | Turbopack | 🔜 计划中 | code-inspector 已支持 |
149
199
  | Mako | 🔜 计划中 | code-inspector 已支持 |
150
200
 
151
201
  > `code-inspector-plugin` 本身已支持上述五种构建工具。CC Prompter 的 sidecar 和脚本注入部分与构建工具无关,只需适配各工具的插件注册方式即可扩展。
@@ -166,18 +216,26 @@ interface CcPromptOptions {
166
216
  ### 示例
167
217
 
168
218
  ```typescript
169
- // 自定义端口
219
+ // Vite — 自定义端口
170
220
  ccPromptPlugin({ port: 4000 })
171
221
 
172
- // 禁用内置 code-inspector(你自己配置)
222
+ // Vite — 禁用内置 code-inspector(你自己配置)
173
223
  ccPromptPlugin({ inspector: false })
174
224
  ```
175
225
 
226
+ ```javascript
227
+ // Next.js — 自定义端口
228
+ module.exports = withCcPrompt({ port: 4000 })({ /* next config */ })
229
+
230
+ // Next.js — 禁用 code-inspector
231
+ module.exports = withCcPrompt({ inspector: false })({ /* next config */ })
232
+ ```
233
+
176
234
  ## 架构
177
235
 
178
236
  ```text
179
237
  ┌─────────────────────────────────────────────────┐
180
- Vite Dev Server
238
+ Vite / Next.js Dev Server
181
239
  │ │
182
240
  │ ┌──────────┐ ┌───────────┐ ┌────────────┐ │
183
241
  │ │ React │ │ inject.js│ │ panel.html│ │
@@ -199,7 +257,8 @@ ccPromptPlugin({ inspector: false })
199
257
 
200
258
  | 组件 | 技术 | 说明 |
201
259
  |------|------|------|
202
- | vite-plugin | TypeScript | 组合 code-inspector + sidecar 启动 + 脚本注入 |
260
+ | vite-plugin | TypeScript | Vite 集成:code-inspector + sidecar + 脚本注入 |
261
+ | next-plugin | TypeScript | Next.js 集成:webpack entry + DefinePlugin 注入 |
203
262
  | sidecar | Express | 管理 PTY session 生命周期,REST API + SSE 流 |
204
263
  | pty-session | node-pty | 管理 Claude CLI 进程,PTY 输出 + JSONL 双通道解析 |
205
264
  | panel.html | 纯 HTML/CSS/JS | iframe 面板 UI,多 session tab,Markdown 渲染 |
@@ -0,0 +1,17 @@
1
+ /**
2
+ * CC Prompter — Webpack Client Entry
3
+ *
4
+ * This file is prepended to Next.js webpack client entries.
5
+ * At build time, webpack DefinePlugin replaces:
6
+ * __CC_PROMPTER_INJECT_SCRIPT__ → inject.js content (string)
7
+ * __CC_PROMPTER_PORT__ → configured sidecar port (number)
8
+ */
9
+ if (typeof window !== 'undefined') {
10
+ // Pass the start port so inject.js can probe the sidecar
11
+ window.__CC_PROMPTER_START_PORT__ = __CC_PROMPTER_PORT__;
12
+
13
+ // Inject the cc-prompter script into the page
14
+ var script = document.createElement('script');
15
+ script.textContent = __CC_PROMPTER_INJECT_SCRIPT__;
16
+ document.head.appendChild(script);
17
+ }
package/dist/index.cjs CHANGED
@@ -5,6 +5,9 @@ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
5
  var __getOwnPropNames = Object.getOwnPropertyNames;
6
6
  var __getProtoOf = Object.getPrototypeOf;
7
7
  var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __esm = (fn, res) => function __init() {
9
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
10
+ };
8
11
  var __export = (target, all) => {
9
12
  for (var name in all)
10
13
  __defProp(target, name, { get: all[name], enumerable: true });
@@ -27,10 +30,48 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
27
30
  ));
28
31
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
32
 
33
+ // src/assets.ts
34
+ var assets_exports = {};
35
+ __export(assets_exports, {
36
+ getInjectScript: () => getInjectScript,
37
+ getPanelHtml: () => getPanelHtml
38
+ });
39
+ function assetPath(filename) {
40
+ const here = (0, import_path.join)(_dirname, filename);
41
+ if ((0, import_fs.existsSync)(here)) return here;
42
+ const parent = (0, import_path.join)(_dirname, "..", filename);
43
+ if ((0, import_fs.existsSync)(parent)) return parent;
44
+ throw new Error(
45
+ `[cc-prompter] Asset not found: ${filename}
46
+ Tried: ${here}
47
+ Tried: ${parent}
48
+ __dirname: ${_dirname}`
49
+ );
50
+ }
51
+ function getPanelHtml() {
52
+ return (0, import_fs.readFileSync)(assetPath("panel.html"), "utf8");
53
+ }
54
+ function getInjectScript() {
55
+ return (0, import_fs.readFileSync)(assetPath("inject.js"), "utf8");
56
+ }
57
+ var import_fs, import_path, import_url2, import_meta2, _dirname;
58
+ var init_assets = __esm({
59
+ "src/assets.ts"() {
60
+ "use strict";
61
+ import_fs = require("fs");
62
+ import_path = require("path");
63
+ import_url2 = require("url");
64
+ import_meta2 = {};
65
+ _dirname = typeof __dirname !== "undefined" ? __dirname : (0, import_path.dirname)((0, import_url2.fileURLToPath)(import_meta2.url));
66
+ }
67
+ });
68
+
30
69
  // src/index.ts
31
70
  var index_exports = {};
32
71
  __export(index_exports, {
33
- ccPromptPlugin: () => ccPromptPlugin
72
+ CcPromptWebpackPlugin: () => CcPromptWebpackPlugin,
73
+ ccPromptPlugin: () => ccPromptPlugin,
74
+ withCcPrompt: () => withCcPrompt
34
75
  });
35
76
  module.exports = __toCommonJS(index_exports);
36
77
 
@@ -712,32 +753,8 @@ var PtySession = class extends import_events.EventEmitter {
712
753
  }
713
754
  };
714
755
 
715
- // src/assets.ts
716
- var import_fs = require("fs");
717
- var import_path = require("path");
718
- var import_url2 = require("url");
719
- var import_meta2 = {};
720
- var _dirname = typeof __dirname !== "undefined" ? __dirname : (0, import_path.dirname)((0, import_url2.fileURLToPath)(import_meta2.url));
721
- function assetPath(filename) {
722
- const here = (0, import_path.join)(_dirname, filename);
723
- if ((0, import_fs.existsSync)(here)) return here;
724
- const parent = (0, import_path.join)(_dirname, "..", filename);
725
- if ((0, import_fs.existsSync)(parent)) return parent;
726
- throw new Error(
727
- `[cc-prompter] Asset not found: ${filename}
728
- Tried: ${here}
729
- Tried: ${parent}
730
- __dirname: ${_dirname}`
731
- );
732
- }
733
- function getPanelHtml() {
734
- return (0, import_fs.readFileSync)(assetPath("panel.html"), "utf8");
735
- }
736
- function getInjectScript() {
737
- return (0, import_fs.readFileSync)(assetPath("inject.js"), "utf8");
738
- }
739
-
740
756
  // src/sidecar.ts
757
+ init_assets();
741
758
  var SessionManager = class {
742
759
  sessions = /* @__PURE__ */ new Map();
743
760
  counter = 0;
@@ -946,10 +963,18 @@ function startSidecar(projectRoot, options) {
946
963
  res.json(session.getHistory());
947
964
  });
948
965
  const server = (0, import_http.createServer)(app);
966
+ let actualPort = startPort;
967
+ app.get("/__cc-port", (_req, res) => {
968
+ const addr = server.address();
969
+ const port = addr && typeof addr === "object" ? addr.port : actualPort;
970
+ res.setHeader("Access-Control-Allow-Origin", "*");
971
+ res.end(String(port));
972
+ });
949
973
  const MAX_PORT = startPort + 10;
950
974
  function tryListen(port) {
951
975
  return new Promise((resolve2, reject) => {
952
976
  server.listen(port, () => {
977
+ actualPort = port;
953
978
  console.log(`[cc-prompter] Sidecar running on http://localhost:${port}`);
954
979
  resolve2(server);
955
980
  });
@@ -973,6 +998,7 @@ function startSidecar(projectRoot, options) {
973
998
  }
974
999
 
975
1000
  // src/vite-plugin.ts
1001
+ init_assets();
976
1002
  function ccPromptPlugin(options) {
977
1003
  const enableInspector = options?.inspector !== false;
978
1004
  let projectRoot = process.cwd();
@@ -1045,8 +1071,158 @@ function ccPromptPlugin(options) {
1045
1071
  if (inspectorPlugin) plugins.unshift(inspectorPlugin);
1046
1072
  return plugins;
1047
1073
  }
1074
+
1075
+ // src/webpack-plugin.ts
1076
+ var import_path2 = require("path");
1077
+ var import_url3 = require("url");
1078
+ var import_code_inspector_plugin2 = require("code-inspector-plugin");
1079
+ init_assets();
1080
+ var import_meta3 = {};
1081
+ var _dirname2 = typeof __dirname !== "undefined" ? __dirname : (0, import_path2.dirname)((0, import_url3.fileURLToPath)(import_meta3.url));
1082
+ var CcPromptWebpackPlugin = class {
1083
+ options;
1084
+ sidecarServer = null;
1085
+ sidecarStarted = false;
1086
+ cleanedUp = false;
1087
+ constructor(options) {
1088
+ this.options = options || {};
1089
+ }
1090
+ apply(compiler) {
1091
+ const isDev = this.options.dev !== void 0 ? this.options.dev : process.env.NODE_ENV !== "production";
1092
+ if (!isDev) return;
1093
+ const startPort = this.options.port || 3456;
1094
+ const clientEntryPath = (0, import_path2.join)(_dirname2, "client-entry.js");
1095
+ if (!this.sidecarStarted) {
1096
+ this.sidecarStarted = true;
1097
+ const projectRoot = this.options.root || process.cwd();
1098
+ this.sidecarServer = startSidecar(projectRoot, { startPort });
1099
+ }
1100
+ compiler.hooks.thisCompilation.tap("CcPromptWebpackPlugin", () => {
1101
+ if (!this.cleanedUp) {
1102
+ this.setupCleanup();
1103
+ }
1104
+ });
1105
+ if (this.options.inspector !== false) {
1106
+ const inspectorPlugin = (0, import_code_inspector_plugin2.codeInspectorPlugin)({
1107
+ bundler: "webpack",
1108
+ behavior: {
1109
+ locate: false,
1110
+ copy: false
1111
+ },
1112
+ hideDomPathAttr: true,
1113
+ hideConsole: true
1114
+ });
1115
+ if (inspectorPlugin && typeof inspectorPlugin.apply === "function") {
1116
+ inspectorPlugin.apply(compiler);
1117
+ } else if (Array.isArray(inspectorPlugin)) {
1118
+ for (const p of inspectorPlugin) {
1119
+ if (p && typeof p.apply === "function") p.apply(compiler);
1120
+ }
1121
+ }
1122
+ }
1123
+ const webpack = require("webpack");
1124
+ const injectScript = getInjectScript();
1125
+ new webpack.DefinePlugin({
1126
+ "__CC_PROMPTER_INJECT_SCRIPT__": JSON.stringify(injectScript),
1127
+ "__CC_PROMPTER_PORT__": JSON.stringify(startPort)
1128
+ }).apply(compiler);
1129
+ const originalEntry = compiler.options.entry;
1130
+ compiler.options.entry = async () => {
1131
+ const entries = typeof originalEntry === "function" ? await originalEntry() : originalEntry;
1132
+ if (typeof entries === "string") {
1133
+ return { main: [clientEntryPath, entries] };
1134
+ }
1135
+ if (Array.isArray(entries)) {
1136
+ return { main: [clientEntryPath, ...entries] };
1137
+ }
1138
+ if (typeof entries === "object") {
1139
+ for (const key in entries) {
1140
+ if (Array.isArray(entries[key])) {
1141
+ entries[key].unshift(clientEntryPath);
1142
+ } else if (typeof entries[key] === "string") {
1143
+ entries[key] = [clientEntryPath, entries[key]];
1144
+ }
1145
+ }
1146
+ }
1147
+ return entries;
1148
+ };
1149
+ }
1150
+ setupCleanup() {
1151
+ const cleanup = () => {
1152
+ if (this.cleanedUp) return;
1153
+ this.cleanedUp = true;
1154
+ if (this.sidecarServer) {
1155
+ this.sidecarServer.close();
1156
+ this.sidecarServer = null;
1157
+ }
1158
+ };
1159
+ process.on("SIGTERM", () => {
1160
+ cleanup();
1161
+ process.exit(0);
1162
+ });
1163
+ process.on("SIGINT", () => {
1164
+ cleanup();
1165
+ process.exit(0);
1166
+ });
1167
+ process.on("exit", cleanup);
1168
+ }
1169
+ };
1170
+
1171
+ // src/next-plugin.ts
1172
+ var import_meta4 = {};
1173
+ function withCcPrompt(options) {
1174
+ return function(nextConfig = {}) {
1175
+ return Object.assign({}, nextConfig, {
1176
+ webpack(config, context) {
1177
+ if (context.dev && !context.isServer) {
1178
+ const plugin = new CcPromptWebpackPlugin({
1179
+ ...options,
1180
+ dev: true
1181
+ });
1182
+ plugin.apply(config._compiler || { options: config, hooks: {} });
1183
+ const startPort = options?.port || 3456;
1184
+ const webpack = require("webpack");
1185
+ if (options?.inspector !== false) {
1186
+ const { codeInspectorPlugin: codeInspectorPlugin3 } = require("code-inspector-plugin");
1187
+ config.plugins = config.plugins || [];
1188
+ config.plugins.push(codeInspectorPlugin3({
1189
+ bundler: "webpack",
1190
+ behavior: { locate: false, copy: false },
1191
+ hideDomPathAttr: true,
1192
+ hideConsole: true
1193
+ }));
1194
+ }
1195
+ const { getInjectScript: getInjectScript2 } = (init_assets(), __toCommonJS(assets_exports));
1196
+ config.plugins.push(new webpack.DefinePlugin({
1197
+ "__CC_PROMPTER_INJECT_SCRIPT__": JSON.stringify(getInjectScript2()),
1198
+ "__CC_PROMPTER_PORT__": JSON.stringify(startPort)
1199
+ }));
1200
+ const { join: join4, dirname: dirname4 } = require("path");
1201
+ const _dirname3 = typeof __dirname !== "undefined" ? __dirname : dirname4(require("url").fileURLToPath(import_meta4.url));
1202
+ const clientEntryPath = join4(_dirname3, "client-entry.js");
1203
+ const originalEntry = config.entry;
1204
+ config.entry = async () => {
1205
+ const entries = typeof originalEntry === "function" ? await originalEntry() : originalEntry;
1206
+ for (const key in entries) {
1207
+ if (Array.isArray(entries[key])) {
1208
+ entries[key].unshift(clientEntryPath);
1209
+ }
1210
+ }
1211
+ return entries;
1212
+ };
1213
+ }
1214
+ if (typeof nextConfig.webpack === "function") {
1215
+ return nextConfig.webpack(config, context);
1216
+ }
1217
+ return config;
1218
+ }
1219
+ });
1220
+ };
1221
+ }
1048
1222
  // Annotate the CommonJS export names for ESM import in node:
1049
1223
  0 && (module.exports = {
1050
- ccPromptPlugin
1224
+ CcPromptWebpackPlugin,
1225
+ ccPromptPlugin,
1226
+ withCcPrompt
1051
1227
  });
1052
1228
  //# sourceMappingURL=index.cjs.map