@sharpe-jupyter/connect 0.4.3 → 0.4.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.
Files changed (2) hide show
  1. package/dist/index.js +116 -36
  2. package/package.json +2 -1
package/dist/index.js CHANGED
@@ -2,6 +2,8 @@
2
2
 
3
3
  // src/index.tsx
4
4
  import { render } from "ink";
5
+ import { readFileSync, writeFileSync, mkdirSync as mkdirSync4, existsSync as existsSync5 } from "fs";
6
+ import { join as join5 } from "path";
5
7
 
6
8
  // src/app.tsx
7
9
  import { useState, useEffect, useRef, useCallback } from "react";
@@ -458,20 +460,7 @@ function App({ connectionCode: connectionCode2, port: port2 }) {
458
460
  const proc = isUvProject() ? startJupyterWithUv(port2, getUvPath()) : startJupyter(port2);
459
461
  jupyterProc.current = proc;
460
462
  didWeSpawnJupyter.current = true;
461
- const pipeStream = (stream) => {
462
- if (!stream) return;
463
- let buf = "";
464
- stream.on("data", (data) => {
465
- buf += data.toString();
466
- const lines = buf.split("\n");
467
- buf = lines.pop() ?? "";
468
- for (const line of lines) {
469
- pushProcessLog(line);
470
- const msg = parseJupyterLine(line);
471
- if (msg) pushEvent(msg);
472
- }
473
- });
474
- };
463
+ const pipeStream = createStreamPiper(pushProcessLog, pushEvent);
475
464
  pipeStream(proc.stderr);
476
465
  pipeStream(proc.stdout);
477
466
  proc.on("error", () => {
@@ -566,7 +555,13 @@ function App({ connectionCode: connectionCode2, port: port2 }) {
566
555
  const proc = startJupyterWithUv(port2, uvPath);
567
556
  jupyterProc.current = proc;
568
557
  didWeSpawnJupyter.current = true;
569
- const spawnError = await waitForJupyterHealthy(proc, port2, pushEvent, pushProcessLog, () => cancelled);
558
+ const spawnError = await waitForJupyterHealthy(
559
+ proc,
560
+ port2,
561
+ pushEvent,
562
+ pushProcessLog,
563
+ () => cancelled
564
+ );
570
565
  if (cancelled) return;
571
566
  const errorInfo = handleJupyterSpawnError(spawnError, port2, processLogsRef.current);
572
567
  if (errorInfo) {
@@ -652,7 +647,13 @@ function App({ connectionCode: connectionCode2, port: port2 }) {
652
647
  const proc = startJupyter(port2);
653
648
  jupyterProc.current = proc;
654
649
  didWeSpawnJupyter.current = true;
655
- const spawnError = await waitForJupyterHealthy(proc, port2, pushEvent, pushProcessLog, () => cancelled);
650
+ const spawnError = await waitForJupyterHealthy(
651
+ proc,
652
+ port2,
653
+ pushEvent,
654
+ pushProcessLog,
655
+ () => cancelled
656
+ );
656
657
  if (cancelled) return;
657
658
  const errorInfo = handleJupyterSpawnError(spawnError, port2, processLogsRef.current);
658
659
  if (errorInfo) {
@@ -945,24 +946,12 @@ function waitForJupyterHealthy(proc, port2, pushEvent, pushLog, isCancelled) {
945
946
  done(`signal-${signal}`);
946
947
  }
947
948
  });
948
- const pipeOutput = (stream) => {
949
- if (!stream) return;
950
- let buf = "";
951
- stream.on("data", (data) => {
952
- buf += data.toString();
953
- const lines = buf.split("\n");
954
- buf = lines.pop() ?? "";
955
- for (const line of lines) {
956
- pushLog(line);
957
- if (line.includes("Address already in use")) {
958
- done("port-conflict");
959
- return;
960
- }
961
- const msg = parseJupyterLine(line);
962
- if (msg) pushEvent(msg);
963
- }
964
- });
965
- };
949
+ const pipeOutput = createStreamPiper(pushLog, pushEvent, (line) => {
950
+ if (line.includes("Address already in use")) {
951
+ done("port-conflict");
952
+ return true;
953
+ }
954
+ });
966
955
  pipeOutput(proc.stderr);
967
956
  pipeOutput(proc.stdout);
968
957
  let attempts = 0;
@@ -981,6 +970,23 @@ function waitForJupyterHealthy(proc, port2, pushEvent, pushLog, isCancelled) {
981
970
  }, 1e3);
982
971
  });
983
972
  }
973
+ function createStreamPiper(pushLog, pushEvent, onLine) {
974
+ return (stream) => {
975
+ if (!stream) return;
976
+ let buf = "";
977
+ stream.on("data", (data) => {
978
+ buf += data.toString();
979
+ const lines = buf.split("\n");
980
+ buf = lines.pop() ?? "";
981
+ for (const line of lines) {
982
+ pushLog(line);
983
+ if (onLine?.(line)) return;
984
+ const msg = parseJupyterLine(line);
985
+ if (msg) pushEvent(msg);
986
+ }
987
+ });
988
+ };
989
+ }
984
990
  function handleJupyterSpawnError(spawnError, port2, lastLogs) {
985
991
  const logTail = lastLogs.length > 0 ? `
986
992
 
@@ -1019,6 +1025,70 @@ Close whatever is using it and try again, or use a different port:
1019
1025
 
1020
1026
  // src/index.tsx
1021
1027
  import { jsx as jsx2 } from "react/jsx-runtime";
1028
+ function getConfigDir() {
1029
+ const home = process.env.HOME ?? process.env.USERPROFILE ?? "";
1030
+ return join5(home, ".sharpe");
1031
+ }
1032
+ function getConfigPath() {
1033
+ return join5(getConfigDir(), "config.json");
1034
+ }
1035
+ function loadSavedToken() {
1036
+ try {
1037
+ const raw = readFileSync(getConfigPath(), "utf-8");
1038
+ const config = JSON.parse(raw);
1039
+ return config.connectionCode ?? null;
1040
+ } catch {
1041
+ return null;
1042
+ }
1043
+ }
1044
+ function saveToken(token) {
1045
+ try {
1046
+ mkdirSync4(getConfigDir(), { recursive: true });
1047
+ const config = { connectionCode: token };
1048
+ writeFileSync(getConfigPath(), JSON.stringify(config, null, 2) + "\n");
1049
+ } catch {
1050
+ }
1051
+ }
1052
+ function ensureShellAlias() {
1053
+ const home = process.env.HOME;
1054
+ if (!home) return;
1055
+ const shell = process.env.SHELL ?? "";
1056
+ let rcFile;
1057
+ if (shell.endsWith("/zsh")) {
1058
+ rcFile = join5(home, ".zshrc");
1059
+ } else if (shell.endsWith("/bash")) {
1060
+ const bashrc = join5(home, ".bashrc");
1061
+ rcFile = existsSync5(bashrc) ? bashrc : join5(home, ".bash_profile");
1062
+ } else {
1063
+ return;
1064
+ }
1065
+ const aliasLine = 'alias sharpe="npx @sharpe-jupyter/connect@latest"';
1066
+ try {
1067
+ const contents = readFileSync(rcFile, "utf-8");
1068
+ if (contents.includes("alias sharpe=")) return;
1069
+ } catch {
1070
+ }
1071
+ try {
1072
+ writeFileSync(rcFile, readFileSync(rcFile, "utf-8").trimEnd() + `
1073
+
1074
+ # Sharpe CLI
1075
+ ${aliasLine}
1076
+ `, "utf-8");
1077
+ console.log(`Added "sharpe" alias to ${rcFile}`);
1078
+ console.log(`Restart your terminal or run: source ${rcFile}
1079
+ `);
1080
+ } catch {
1081
+ try {
1082
+ writeFileSync(rcFile, `# Sharpe CLI
1083
+ ${aliasLine}
1084
+ `, "utf-8");
1085
+ console.log(`Added "sharpe" alias to ${rcFile}`);
1086
+ console.log(`Restart your terminal or run: source ${rcFile}
1087
+ `);
1088
+ } catch {
1089
+ }
1090
+ }
1091
+ }
1022
1092
  function parseArgs(argv) {
1023
1093
  const args = argv.slice(2);
1024
1094
  let connectionCode2 = null;
@@ -1027,7 +1097,10 @@ function parseArgs(argv) {
1027
1097
  const arg = args[i];
1028
1098
  if (arg === "--help" || arg === "-h") {
1029
1099
  console.log(`
1030
- Usage: sharpe-connect [connection-code] [options]
1100
+ Usage: sharpe [connection-code] [options]
1101
+
1102
+ If a connection code is provided, it is saved to ~/.sharpe/config.json
1103
+ for future use. Run "sharpe" with no arguments to reuse the saved code.
1031
1104
 
1032
1105
  Options:
1033
1106
  --port, -p Notebook port (default: 8888)
@@ -1045,5 +1118,12 @@ Options:
1045
1118
  }
1046
1119
  return { connectionCode: connectionCode2, port: port2 };
1047
1120
  }
1048
- var { connectionCode, port } = parseArgs(process.argv);
1121
+ var { connectionCode: cliToken, port } = parseArgs(process.argv);
1122
+ var connectionCode = cliToken;
1123
+ if (connectionCode) {
1124
+ saveToken(connectionCode);
1125
+ ensureShellAlias();
1126
+ } else {
1127
+ connectionCode = loadSavedToken();
1128
+ }
1049
1129
  render(/* @__PURE__ */ jsx2(App, { connectionCode, port }));
package/package.json CHANGED
@@ -1,9 +1,10 @@
1
1
  {
2
2
  "name": "@sharpe-jupyter/connect",
3
- "version": "0.4.3",
3
+ "version": "0.4.4",
4
4
  "description": "Connect a local JupyterHub to Sharpe via Cloudflare Tunnel",
5
5
  "type": "module",
6
6
  "bin": {
7
+ "sharpe": "dist/index.js",
7
8
  "sharpe-connect": "dist/index.js"
8
9
  },
9
10
  "files": [