@stevezhou/sisu 0.3.13 → 0.3.15

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
@@ -13,7 +13,7 @@ sisu login
13
13
  sisu
14
14
  ```
15
15
 
16
- `npm install -g` is a small JS package. postinstall fetches the stamped SiSu TUI pager for **this package version** into `~/.sisu/bin` when a prebuilt exists. GitHub Release tags ship `darwin-arm64`, `linux-x64`, and `linux-arm64`. `darwin-x64` is opt-in (`workflow_dispatch` with `platforms` containing `darwin-x64`) and often missing; platforms without a binary, or a missing GitHub Release asset, keep the Node TUI.
16
+ Postinstall links `sisu` into `~/.local/bin` (Debian/login PATH) and prints an `export PATH=...` line if this shell still cannot see the command. `npm install -g` is a small JS package. It also fetches the stamped SiSu TUI pager for **this package version** into `~/.sisu/bin` when a prebuilt exists. GitHub Release tags ship `darwin-arm64`, `linux-x64`, and `linux-arm64`. `darwin-x64` is opt-in (`workflow_dispatch` with `platforms` containing `darwin-x64`) and often missing; platforms without a binary, or a missing GitHub Release asset, keep the Node TUI.
17
17
 
18
18
  Requires Node.js 20 or newer. `npx sisu` works without a global install.
19
19
 
package/dist/tui.js CHANGED
@@ -305,7 +305,9 @@ async function runTui(io, deps = {}) {
305
305
  });
306
306
  });
307
307
  if (deps.spawnGrokPager || ((0, launch_1.findGrokBuildBinary)() && process.stdout.isTTY)) {
308
- // Login handoff: pager exits 10 → host web login at most once → respawn.
308
+ // Login handoff: pager exits 10 → host web login at most once → respawn
309
+ // grok-pager. Never fall through to the Node TUI while the grok binary ran.
310
+ let retriedWithSession = false;
309
311
  while (true) {
310
312
  const code = await spawnOnce();
311
313
  if (code === null)
@@ -313,19 +315,23 @@ async function runTui(io, deps = {}) {
313
315
  if (code !== exports.SISU_LOGIN_EXIT_CODE)
314
316
  return code;
315
317
  restoreInteractiveTerminal(io);
316
- if (auth() || openedBrowserLogin) {
317
- io.write('sisu: session already saved; not opening another login page.\n');
318
- break;
319
- }
320
- try {
321
- const email = await startWebLogin((line) => io.write(`${line}\n`));
322
- io.write(`logged in as ${email}\n`);
318
+ if (!auth() && !openedBrowserLogin) {
319
+ try {
320
+ const email = await startWebLogin((line) => io.write(`${line}\n`));
321
+ io.write(`logged in as ${email}\n`);
322
+ }
323
+ catch (error) {
324
+ io.write(`${error instanceof Error ? error.message : String(error)}\n`);
325
+ io.write('login failed — run `sisu login`\n');
326
+ return 1;
327
+ }
328
+ continue;
323
329
  }
324
- catch (error) {
325
- io.write(`${error instanceof Error ? error.message : String(error)}\n`);
326
- io.write('login failed — run `sisu login`\n');
327
- return 1;
330
+ if (retriedWithSession) {
331
+ io.write('sisu: grok pager still requesting login after a saved session.\n');
332
+ return exports.SISU_LOGIN_EXIT_CODE;
328
333
  }
334
+ retriedWithSession = true;
329
335
  }
330
336
  }
331
337
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stevezhou/sisu",
3
- "version": "0.3.13",
3
+ "version": "0.3.15",
4
4
  "description": "SiSu CLI — 思溯 / SiSu · 思有所溯",
5
5
  "license": "UNLICENSED",
6
6
  "homepage": "https://www.sisu.chat",
@@ -26,7 +26,8 @@
26
26
  "NOTICE",
27
27
  "third_party/grok-build",
28
28
  "scripts/postinstall.js",
29
- "scripts/install-pager.js"
29
+ "scripts/install-pager.js",
30
+ "scripts/ensure-cli-path.js"
30
31
  ],
31
32
  "publishConfig": {
32
33
  "access": "public"
@@ -0,0 +1,107 @@
1
+ #!/usr/bin/env node
2
+ /** After `npm i -g`, put `sisu` on a PATH users actually have.
3
+ * npm's global bin is often missing from Debian/login PATH.
4
+ */
5
+ const fs = require('fs')
6
+ const os = require('os')
7
+ const path = require('path')
8
+
9
+ function pathDirs(pathEnv = process.env.PATH || '') {
10
+ return pathEnv.split(path.delimiter).filter(Boolean)
11
+ }
12
+
13
+ function pathContains(dir, pathEnv = process.env.PATH || '') {
14
+ const target = path.resolve(dir)
15
+ return pathDirs(pathEnv).some((entry) => {
16
+ try {
17
+ return path.resolve(entry) === target
18
+ } catch {
19
+ return false
20
+ }
21
+ })
22
+ }
23
+
24
+ function globalSisuBin(options = {}) {
25
+ const name = options.platform === 'win32' ? 'sisu.cmd' : 'sisu'
26
+ const prefix = options.prefix || process.env.npm_config_prefix || ''
27
+ const fromPrefix = prefix
28
+ ? (options.platform === 'win32' ? path.join(prefix, name) : path.join(prefix, 'bin', name))
29
+ : ''
30
+ const fromLayout = path.resolve(
31
+ options.packageRoot || path.join(__dirname, '..'),
32
+ '..',
33
+ '..',
34
+ options.platform === 'win32' ? name : path.join('bin', name),
35
+ )
36
+ const exists = options.exists || ((file) => fs.existsSync(file))
37
+ for (const candidate of [fromPrefix, fromLayout]) {
38
+ if (candidate && exists(candidate)) return candidate
39
+ }
40
+ return fromPrefix || fromLayout
41
+ }
42
+
43
+ function userLocalBin(home = os.homedir()) {
44
+ return path.join(home, '.local', 'bin')
45
+ }
46
+
47
+ function ensureUserShim(target, options = {}) {
48
+ if ((options.platform || process.platform) === 'win32') return null
49
+ if (!target) return null
50
+ const dir = userLocalBin(options.home || os.homedir())
51
+ const dest = path.join(dir, 'sisu')
52
+ fs.mkdirSync(dir, { recursive: true, mode: 0o755 })
53
+ try {
54
+ if (fs.lstatSync(dest)) fs.unlinkSync(dest)
55
+ } catch {
56
+ // missing
57
+ }
58
+ fs.symlinkSync(path.resolve(target), dest)
59
+ try {
60
+ fs.chmodSync(dest, 0o755)
61
+ } catch {
62
+ // symlink mode follows the target on most POSIX
63
+ }
64
+ return dest
65
+ }
66
+
67
+ function pathHint(npmBin, localBin, pathEnv = process.env.PATH || '') {
68
+ const dirs = []
69
+ if (localBin && !pathContains(localBin, pathEnv)) dirs.push(localBin)
70
+ if (npmBin && !pathContains(npmBin, pathEnv)) dirs.push(npmBin)
71
+ if (!dirs.length) return ''
72
+ return `export PATH="${dirs.join(':')}:$PATH" && hash -r`
73
+ }
74
+
75
+ function installCliPath(options = {}) {
76
+ const writes = options.write || ((text) => process.stdout.write(text))
77
+ const platform = options.platform || process.platform
78
+ const bin = globalSisuBin(options)
79
+ const npmBinDir = bin ? path.dirname(bin) : ''
80
+ let shim = null
81
+ try {
82
+ shim = ensureUserShim(bin, options)
83
+ } catch (error) {
84
+ writes(`sisu: could not link ~/.local/bin/sisu (${error instanceof Error ? error.message : String(error)})\n`)
85
+ }
86
+ const localDir = shim ? path.dirname(shim) : userLocalBin(options.home)
87
+ const hint = pathHint(npmBinDir, localDir, options.pathEnv)
88
+ if (bin) writes(`sisu: command -> ${shim || bin}\n`)
89
+ if (hint) {
90
+ writes('sisu: if `sisu` is not found in this shell, run:\n')
91
+ writes(` ${hint}\n`)
92
+ }
93
+ return { bin, shim, hint }
94
+ }
95
+
96
+ module.exports = {
97
+ ensureUserShim,
98
+ globalSisuBin,
99
+ installCliPath,
100
+ pathContains,
101
+ pathHint,
102
+ userLocalBin,
103
+ }
104
+
105
+ if (require.main === module) {
106
+ installCliPath()
107
+ }
@@ -3,6 +3,13 @@
3
3
  * Never fail `npm install` — Node TUI still works without the binary.
4
4
  */
5
5
  const { installPager } = require('./install-pager')
6
+ const { installCliPath } = require('./ensure-cli-path')
7
+
8
+ try {
9
+ installCliPath()
10
+ } catch (error) {
11
+ process.stdout.write(`sisu: path setup skipped (${error instanceof Error ? error.message : String(error)})\n`)
12
+ }
6
13
 
7
14
  installPager().then(
8
15
  (result) => {