@kw-yeh/vibeflow 4.0.2 → 4.0.4

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
@@ -82,6 +82,20 @@ vibeflow doctor
82
82
  npm i -g @kw-yeh/vibeflow@latest
83
83
  ```
84
84
 
85
+ ### 安裝時出現 `EACCES: permission denied`
86
+
87
+ 代表 npm 的全域目錄屬於系統管理員(從 nodejs.org 下載安裝檔裝 Node 時常見)。建議不要用 `sudo`,改成讓全域套件裝在你自己的家目錄:
88
+
89
+ ```bash
90
+ mkdir -p ~/.npm-global
91
+ npm config set prefix ~/.npm-global
92
+ echo 'export PATH="$HOME/.npm-global/bin:$PATH"' >> ~/.zshrc
93
+ source ~/.zshrc
94
+ npm i -g @kw-yeh/vibeflow@latest
95
+ ```
96
+
97
+ 用 Homebrew(`brew install node`)或 [nvm](https://github.com/nvm-sh/nvm) 裝的 Node 本來就不需要 `sudo`。已經用 `sudo` 裝好也能正常使用;若 `vibeflow doctor` 回報終端機模組沒有執行權限,照它列出的 `sudo chmod +x …` 指令執行一次即可。
98
+
85
99
  ---
86
100
 
87
101
  ## 常用指令
@@ -0,0 +1,57 @@
1
+ import { createRequire as __vfCreateRequire } from 'node:module';
2
+ const require = __vfCreateRequire(import.meta.url);
3
+
4
+ // packages/core/src/pty-helper.ts
5
+ import fs from "fs";
6
+ import path from "path";
7
+ import { createRequire } from "module";
8
+ function nodePtyRoot() {
9
+ try {
10
+ return path.dirname(createRequire(import.meta.url).resolve("node-pty/package.json"));
11
+ } catch {
12
+ return null;
13
+ }
14
+ }
15
+ function ensurePtySpawnHelper(root = nodePtyRoot()) {
16
+ if (process.platform === "win32" || !root) return [];
17
+ const candidates = [
18
+ path.join(root, "build", "Release", "spawn-helper"),
19
+ path.join(root, "prebuilds", `${process.platform}-${process.arch}`, "spawn-helper")
20
+ ];
21
+ const failures = [];
22
+ for (const file of candidates) {
23
+ let stat;
24
+ try {
25
+ stat = fs.statSync(file);
26
+ } catch {
27
+ continue;
28
+ }
29
+ try {
30
+ fs.accessSync(file, fs.constants.X_OK);
31
+ continue;
32
+ } catch {
33
+ }
34
+ try {
35
+ fs.chmodSync(file, stat.mode & 4095 | 73);
36
+ } catch (err) {
37
+ const uid = process.getuid?.();
38
+ failures.push({ path: file, error: err.message, ownedByOther: uid !== void 0 && stat.uid !== uid });
39
+ }
40
+ }
41
+ return failures;
42
+ }
43
+ function spawnHelperFixCommand(failures) {
44
+ const sudo = failures.some((f) => f.ownedByOther) ? "sudo " : "";
45
+ return `${sudo}chmod +x ${failures.map((f) => `"${f.path}"`).join(" ")}`;
46
+ }
47
+
48
+ // packages/cli/src/postinstall.ts
49
+ try {
50
+ const failures = ensurePtySpawnHelper();
51
+ if (failures.length > 0) {
52
+ console.warn(`[vibeflow] node-pty spawn-helper \u7121\u6CD5\u8A2D\u70BA\u53EF\u57F7\u884C\uFF0C\u7D42\u7AEF\u6A5F\u6703\u7121\u6CD5\u555F\u52D5\u3002\u8ACB\u57F7\u884C\uFF1A
53
+ ${spawnHelperFixCommand(failures)}`);
54
+ }
55
+ } catch (err) {
56
+ console.warn(`[vibeflow] postinstall \u7565\u904E\uFF1A${err.message}`);
57
+ }
package/dist/vibeflow.mjs CHANGED
@@ -257,6 +257,11 @@ var init_pty = __esm({
257
257
  scrollback(key) {
258
258
  return this.scrollbacks.get(key) ?? null;
259
259
  }
260
+ async isScrolledBack() {
261
+ return false;
262
+ }
263
+ async scrollToBottom() {
264
+ }
260
265
  shutdown() {
261
266
  for (const key of Array.from(this.sessions.keys())) this.killNow(key);
262
267
  }
@@ -416,6 +421,20 @@ var init_tmux_backend = __esm({
416
421
  scrollback() {
417
422
  return null;
418
423
  }
424
+ /** The attached client sits on the alternate screen, so history is tmux copy-mode. */
425
+ async isScrolledBack(key) {
426
+ const { code: code2, stdout } = await this.tmux(
427
+ "display-message",
428
+ "-p",
429
+ "-t",
430
+ `=${this.sessionName(key)}:`,
431
+ "#{pane_in_mode}"
432
+ );
433
+ return code2 === 0 && stdout.trim() === "1";
434
+ }
435
+ async scrollToBottom(key) {
436
+ await this.tmux("send-keys", "-t", `=${this.sessionName(key)}:`, "-X", "cancel");
437
+ }
419
438
  attachArgs(key) {
420
439
  return ["-L", TMUX_SOCKET, "-f", this.confPath, "attach-session", "-t", `=${this.sessionName(key)}`];
421
440
  }
@@ -1840,6 +1859,10 @@ function createBridge(t) {
1840
1859
  resize: (sessionKey, cols, rows) => t.send("pty:resize", { sessionKey, cols, rows }),
1841
1860
  /** Kill a task's PTY session (tears down the session and its watchers). */
1842
1861
  kill: (sessionKey) => t.send("pty:kill", sessionKey),
1862
+ /** Whether the session's own history view (tmux copy-mode) is off the live output. */
1863
+ isScrolledBack: (sessionKey) => t.invoke("pty:scrolled", sessionKey),
1864
+ /** Return the session's own history view to the live output. */
1865
+ scrollToBottom: (sessionKey) => t.invoke("pty:scroll-bottom", sessionKey),
1843
1866
  /** Whether the task's pinned Claude conversation already exists on disk. */
1844
1867
  sessionExists: (taskId) => t.invoke("claude:sessionExists", taskId),
1845
1868
  /**
@@ -1899,9 +1922,9 @@ function ensurePtySpawnHelper(root = nodePtyRoot()) {
1899
1922
  ];
1900
1923
  const failures = [];
1901
1924
  for (const file of candidates) {
1902
- let mode;
1925
+ let stat;
1903
1926
  try {
1904
- mode = fs4.statSync(file).mode;
1927
+ stat = fs4.statSync(file);
1905
1928
  } catch {
1906
1929
  continue;
1907
1930
  }
@@ -1911,13 +1934,18 @@ function ensurePtySpawnHelper(root = nodePtyRoot()) {
1911
1934
  } catch {
1912
1935
  }
1913
1936
  try {
1914
- fs4.chmodSync(file, mode & 4095 | 73);
1937
+ fs4.chmodSync(file, stat.mode & 4095 | 73);
1915
1938
  } catch (err) {
1916
- failures.push({ path: file, error: err.message });
1939
+ const uid = process.getuid?.();
1940
+ failures.push({ path: file, error: err.message, ownedByOther: uid !== void 0 && stat.uid !== uid });
1917
1941
  }
1918
1942
  }
1919
1943
  return failures;
1920
1944
  }
1945
+ function spawnHelperFixCommand(failures) {
1946
+ const sudo = failures.some((f) => f.ownedByOther) ? "sudo " : "";
1947
+ return `${sudo}chmod +x ${failures.map((f) => `"${f.path}"`).join(" ")}`;
1948
+ }
1921
1949
 
1922
1950
  // packages/cli/src/task-command.ts
1923
1951
  import { parseArgs } from "node:util";
@@ -4936,6 +4964,9 @@ function createCore({ sessions, bus, version }) {
4936
4964
  sessions.resize(requireSessionKey(p.sessionKey), p.cols, p.rows);
4937
4965
  },
4938
4966
  "pty:kill": (sessionKey) => teardownSession(requireSessionKey(sessionKey)),
4967
+ // History a frontend cannot scroll itself (tmux copy-mode).
4968
+ "pty:scrolled": (sessionKey) => sessions.isScrolledBack(requireSessionKey(sessionKey)),
4969
+ "pty:scroll-bottom": (sessionKey) => sessions.scrollToBottom(requireSessionKey(sessionKey)),
4939
4970
  // Does the task's pinned Claude conversation exist on disk? Used to decide
4940
4971
  // whether selecting a task may auto-resume (session present) or should
4941
4972
  // leave the interactive terminal for manual commands.
@@ -5641,8 +5672,8 @@ async function checkNodePty() {
5641
5672
  id: "node-pty",
5642
5673
  name: "node-pty",
5643
5674
  level: "fail",
5644
- detail: `spawn-helper \u6C92\u6709\u57F7\u884C\u6B0A\u9650\uFF1A${unrepaired[0].error}`,
5645
- fix: unrepaired.map((f) => `chmod +x "${f.path}"`).join(" && ")
5675
+ detail: unrepaired.some((f) => f.ownedByOther) ? "spawn-helper \u6C92\u6709\u57F7\u884C\u6B0A\u9650\uFF0C\u800C\u4E14\u6A94\u6848\u5C6C\u65BC\u5176\u4ED6\u4F7F\u7528\u8005\uFF08\u901A\u5E38\u662F\u7528 sudo \u5168\u57DF\u5B89\u88DD\uFF09\uFF0CVibeFlow \u7121\u6CD5\u81EA\u884C\u4FEE\u6B63" : `spawn-helper \u6C92\u6709\u57F7\u884C\u6B0A\u9650\uFF1A${unrepaired[0].error}`,
5676
+ fix: spawnHelperFixCommand(unrepaired)
5646
5677
  };
5647
5678
  }
5648
5679
  try {
@@ -1 +1 @@
1
- <!DOCTYPE html><html><head><meta charSet="utf-8" data-next-head=""/><meta name="viewport" content="width=device-width" data-next-head=""/><link rel="icon" href="/favicon.ico" sizes="any" data-next-head=""/><link rel="icon" href="/logo.svg" type="image/svg+xml" data-next-head=""/><link rel="apple-touch-icon" href="/apple-touch-icon.png" data-next-head=""/><title data-next-head="">404: This page could not be found</title><link rel="preload" href="/_next/static/css/edcc6abb24dfd492.css" as="style"/><link rel="stylesheet" href="/_next/static/css/edcc6abb24dfd492.css" data-n-g=""/><noscript data-n-css=""></noscript><script defer="" noModule="" src="/_next/static/chunks/polyfills-42372ed130431b0a.js"></script><script src="/_next/static/chunks/webpack-c8aa5df0425f6b7b.js" defer=""></script><script src="/_next/static/chunks/framework-b92a367cf1ad4bf7.js" defer=""></script><script src="/_next/static/chunks/main-ec7fd4c371d06729.js" defer=""></script><script src="/_next/static/chunks/pages/_app-320afeed07d86516.js" defer=""></script><script src="/_next/static/chunks/pages/_error-8f65b0f8749f42d2.js" defer=""></script><script src="/_next/static/2aQtkEcIfRS2Nbk-qwUwY/_buildManifest.js" defer=""></script><script src="/_next/static/2aQtkEcIfRS2Nbk-qwUwY/_ssgManifest.js" defer=""></script></head><body><div id="__next"><div style="font-family:system-ui,&quot;Segoe UI&quot;,Roboto,Helvetica,Arial,sans-serif,&quot;Apple Color Emoji&quot;,&quot;Segoe UI Emoji&quot;;height:100vh;text-align:center;display:flex;flex-direction:column;align-items:center;justify-content:center"><div style="line-height:48px"><style>body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}</style><h1 class="next-error-h1" style="display:inline-block;margin:0 20px 0 0;padding-right:23px;font-size:24px;font-weight:500;vertical-align:top">404</h1><div style="display:inline-block"><h2 style="font-size:14px;font-weight:400;line-height:28px">This page could not be found<!-- -->.</h2></div></div></div></div><script id="__NEXT_DATA__" type="application/json">{"props":{"pageProps":{"statusCode":404}},"page":"/_error","query":{},"buildId":"2aQtkEcIfRS2Nbk-qwUwY","nextExport":true,"isFallback":false,"gip":true,"scriptLoader":[]}</script></body></html>
1
+ <!DOCTYPE html><html><head><meta charSet="utf-8" data-next-head=""/><meta name="viewport" content="width=device-width" data-next-head=""/><link rel="icon" href="/favicon.ico" sizes="any" data-next-head=""/><link rel="icon" href="/logo.svg" type="image/svg+xml" data-next-head=""/><link rel="apple-touch-icon" href="/apple-touch-icon.png" data-next-head=""/><title data-next-head="">404: This page could not be found</title><link rel="preload" href="/_next/static/css/5576c1267923e491.css" as="style"/><link rel="stylesheet" href="/_next/static/css/5576c1267923e491.css" data-n-g=""/><noscript data-n-css=""></noscript><script defer="" noModule="" src="/_next/static/chunks/polyfills-42372ed130431b0a.js"></script><script src="/_next/static/chunks/webpack-c8aa5df0425f6b7b.js" defer=""></script><script src="/_next/static/chunks/framework-b92a367cf1ad4bf7.js" defer=""></script><script src="/_next/static/chunks/main-ec7fd4c371d06729.js" defer=""></script><script src="/_next/static/chunks/pages/_app-fde992b12936b886.js" defer=""></script><script src="/_next/static/chunks/pages/_error-8f65b0f8749f42d2.js" defer=""></script><script src="/_next/static/zygbxZW3DHGve5whWS7yR/_buildManifest.js" defer=""></script><script src="/_next/static/zygbxZW3DHGve5whWS7yR/_ssgManifest.js" defer=""></script></head><body><div id="__next"><div style="font-family:system-ui,&quot;Segoe UI&quot;,Roboto,Helvetica,Arial,sans-serif,&quot;Apple Color Emoji&quot;,&quot;Segoe UI Emoji&quot;;height:100vh;text-align:center;display:flex;flex-direction:column;align-items:center;justify-content:center"><div style="line-height:48px"><style>body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}</style><h1 class="next-error-h1" style="display:inline-block;margin:0 20px 0 0;padding-right:23px;font-size:24px;font-weight:500;vertical-align:top">404</h1><div style="display:inline-block"><h2 style="font-size:14px;font-weight:400;line-height:28px">This page could not be found<!-- -->.</h2></div></div></div></div><script id="__NEXT_DATA__" type="application/json">{"props":{"pageProps":{"statusCode":404}},"page":"/_error","query":{},"buildId":"zygbxZW3DHGve5whWS7yR","nextExport":true,"isFallback":false,"gip":true,"scriptLoader":[]}</script></body></html>
package/dist/web/404.html CHANGED
@@ -1 +1 @@
1
- <!DOCTYPE html><html><head><meta charSet="utf-8" data-next-head=""/><meta name="viewport" content="width=device-width" data-next-head=""/><link rel="icon" href="/favicon.ico" sizes="any" data-next-head=""/><link rel="icon" href="/logo.svg" type="image/svg+xml" data-next-head=""/><link rel="apple-touch-icon" href="/apple-touch-icon.png" data-next-head=""/><title data-next-head="">404: This page could not be found</title><link rel="preload" href="/_next/static/css/edcc6abb24dfd492.css" as="style"/><link rel="stylesheet" href="/_next/static/css/edcc6abb24dfd492.css" data-n-g=""/><noscript data-n-css=""></noscript><script defer="" noModule="" src="/_next/static/chunks/polyfills-42372ed130431b0a.js"></script><script src="/_next/static/chunks/webpack-c8aa5df0425f6b7b.js" defer=""></script><script src="/_next/static/chunks/framework-b92a367cf1ad4bf7.js" defer=""></script><script src="/_next/static/chunks/main-ec7fd4c371d06729.js" defer=""></script><script src="/_next/static/chunks/pages/_app-320afeed07d86516.js" defer=""></script><script src="/_next/static/chunks/pages/_error-8f65b0f8749f42d2.js" defer=""></script><script src="/_next/static/2aQtkEcIfRS2Nbk-qwUwY/_buildManifest.js" defer=""></script><script src="/_next/static/2aQtkEcIfRS2Nbk-qwUwY/_ssgManifest.js" defer=""></script></head><body><div id="__next"><div style="font-family:system-ui,&quot;Segoe UI&quot;,Roboto,Helvetica,Arial,sans-serif,&quot;Apple Color Emoji&quot;,&quot;Segoe UI Emoji&quot;;height:100vh;text-align:center;display:flex;flex-direction:column;align-items:center;justify-content:center"><div style="line-height:48px"><style>body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}</style><h1 class="next-error-h1" style="display:inline-block;margin:0 20px 0 0;padding-right:23px;font-size:24px;font-weight:500;vertical-align:top">404</h1><div style="display:inline-block"><h2 style="font-size:14px;font-weight:400;line-height:28px">This page could not be found<!-- -->.</h2></div></div></div></div><script id="__NEXT_DATA__" type="application/json">{"props":{"pageProps":{"statusCode":404}},"page":"/_error","query":{},"buildId":"2aQtkEcIfRS2Nbk-qwUwY","nextExport":true,"isFallback":false,"gip":true,"scriptLoader":[]}</script></body></html>
1
+ <!DOCTYPE html><html><head><meta charSet="utf-8" data-next-head=""/><meta name="viewport" content="width=device-width" data-next-head=""/><link rel="icon" href="/favicon.ico" sizes="any" data-next-head=""/><link rel="icon" href="/logo.svg" type="image/svg+xml" data-next-head=""/><link rel="apple-touch-icon" href="/apple-touch-icon.png" data-next-head=""/><title data-next-head="">404: This page could not be found</title><link rel="preload" href="/_next/static/css/5576c1267923e491.css" as="style"/><link rel="stylesheet" href="/_next/static/css/5576c1267923e491.css" data-n-g=""/><noscript data-n-css=""></noscript><script defer="" noModule="" src="/_next/static/chunks/polyfills-42372ed130431b0a.js"></script><script src="/_next/static/chunks/webpack-c8aa5df0425f6b7b.js" defer=""></script><script src="/_next/static/chunks/framework-b92a367cf1ad4bf7.js" defer=""></script><script src="/_next/static/chunks/main-ec7fd4c371d06729.js" defer=""></script><script src="/_next/static/chunks/pages/_app-fde992b12936b886.js" defer=""></script><script src="/_next/static/chunks/pages/_error-8f65b0f8749f42d2.js" defer=""></script><script src="/_next/static/zygbxZW3DHGve5whWS7yR/_buildManifest.js" defer=""></script><script src="/_next/static/zygbxZW3DHGve5whWS7yR/_ssgManifest.js" defer=""></script></head><body><div id="__next"><div style="font-family:system-ui,&quot;Segoe UI&quot;,Roboto,Helvetica,Arial,sans-serif,&quot;Apple Color Emoji&quot;,&quot;Segoe UI Emoji&quot;;height:100vh;text-align:center;display:flex;flex-direction:column;align-items:center;justify-content:center"><div style="line-height:48px"><style>body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}</style><h1 class="next-error-h1" style="display:inline-block;margin:0 20px 0 0;padding-right:23px;font-size:24px;font-weight:500;vertical-align:top">404</h1><div style="display:inline-block"><h2 style="font-size:14px;font-weight:400;line-height:28px">This page could not be found<!-- -->.</h2></div></div></div></div><script id="__NEXT_DATA__" type="application/json">{"props":{"pageProps":{"statusCode":404}},"page":"/_error","query":{},"buildId":"zygbxZW3DHGve5whWS7yR","nextExport":true,"isFallback":false,"gip":true,"scriptLoader":[]}</script></body></html>