snow-flow 10.0.12 → 10.0.13

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "$schema": "https://json.schemastore.org/package.json",
3
- "version": "10.0.12",
3
+ "version": "10.0.13",
4
4
  "name": "snow-flow",
5
5
  "description": "Snow-Flow - ServiceNow Multi-Agent Development Framework powered by AI",
6
6
  "license": "Elastic-2.0",
@@ -133,27 +133,37 @@ This file contains instructions for AI agents working in this codebase.
133
133
 
134
134
  /**
135
135
  * Try to open a URL in the user's browser.
136
- * Works in Codespaces (VS Code intercepts xdg-open), SSH with X forwarding, and local.
136
+ * Tries multiple strategies:
137
+ * 1. $BROWSER env var (set by Codespaces / Gitpod / custom setups)
138
+ * 2. Platform-native: open (macOS), cmd /c start (Windows), xdg-open (Linux)
137
139
  */
138
140
  async function tryOpenBrowser(url: string): Promise<boolean> {
139
141
  const { spawn } = await import("child_process")
140
- return new Promise((resolve) => {
141
- try {
142
- let proc: ReturnType<typeof spawn>
143
- if (process.platform === "darwin") {
144
- proc = spawn("open", [url], { detached: true, stdio: "ignore" })
145
- } else if (process.platform === "win32") {
146
- proc = spawn("cmd", ["/c", "start", url], { detached: true, stdio: "ignore" })
147
- } else {
148
- proc = spawn("xdg-open", [url], { detached: true, stdio: "ignore" })
142
+
143
+ const trySpawn = (cmd: string, args: string[]): Promise<boolean> =>
144
+ new Promise((resolve) => {
145
+ try {
146
+ const proc = spawn(cmd, args, { detached: true, stdio: "ignore" })
147
+ if (proc.unref) proc.unref()
148
+ proc.on("error", () => resolve(false))
149
+ // Give the process a moment to fail, then assume success
150
+ setTimeout(() => resolve(true), 200)
151
+ } catch {
152
+ resolve(false)
149
153
  }
150
- if (proc.unref) proc.unref()
151
- proc.on("error", () => resolve(false))
152
- resolve(true)
153
- } catch {
154
- resolve(false)
155
- }
156
- })
154
+ })
155
+
156
+ // 1. Try $BROWSER env var (Codespaces, Gitpod, etc.)
157
+ const browserEnv = process.env.BROWSER
158
+ if (browserEnv) {
159
+ const opened = await trySpawn(browserEnv, [url])
160
+ if (opened) return true
161
+ }
162
+
163
+ // 2. Platform-native
164
+ if (process.platform === "darwin") return trySpawn("open", [url])
165
+ if (process.platform === "win32") return trySpawn("cmd", ["/c", "start", url])
166
+ return trySpawn("xdg-open", [url])
157
167
  }
158
168
 
159
169
  type AuthMethod = "servicenow-oauth" | "servicenow-basic" | "enterprise-portal" | "enterprise-license" | "enterprise-combined" | "servicenow-llm"
@@ -368,6 +378,24 @@ function DialogAuthServiceNowOAuth() {
368
378
  setTimeout(() => secretInput?.focus(), 10)
369
379
  }
370
380
  }
381
+ // Keyboard shortcuts for headless auth step
382
+ if (step() === "callback-paste" && headlessAuthUrl()) {
383
+ if (evt.name === "o") {
384
+ tryOpenBrowser(headlessAuthUrl()).then((opened) => {
385
+ toast.show({
386
+ variant: opened ? "success" : "error",
387
+ message: opened ? "Browser opened!" : "Could not open browser",
388
+ duration: 3000,
389
+ })
390
+ })
391
+ }
392
+ if (evt.name === "c") {
393
+ Clipboard.copy(headlessAuthUrl()).then(
394
+ () => toast.show({ variant: "success", message: "URL copied to clipboard!", duration: 3000 }),
395
+ () => toast.show({ variant: "error", message: "Failed to copy", duration: 3000 }),
396
+ )
397
+ }
398
+ }
371
399
  })
372
400
 
373
401
  const startMcpServerAfterAuth = async () => {
@@ -638,14 +666,30 @@ function DialogAuthServiceNowOAuth() {
638
666
  paddingLeft={2}
639
667
  paddingRight={2}
640
668
  backgroundColor={theme.primary}
669
+ onMouseUp={() => {
670
+ tryOpenBrowser(headlessAuthUrl()).then((opened) => {
671
+ toast.show({
672
+ variant: opened ? "success" : "error",
673
+ message: opened ? "Browser opened!" : "Could not open browser",
674
+ duration: 3000,
675
+ })
676
+ })
677
+ }}
678
+ >
679
+ <text fg={theme.selectedListItemText} attributes={TextAttributes.BOLD}>[ Open in Browser ]</text>
680
+ </box>
681
+ <box
682
+ paddingLeft={2}
683
+ paddingRight={2}
684
+ backgroundColor={theme.backgroundElement}
641
685
  onMouseUp={() => {
642
686
  Clipboard.copy(headlessAuthUrl()).then(
643
- () => toast.show({ variant: "success", message: "URL copied to clipboard!", duration: 3000 }),
644
- () => toast.show({ variant: "error", message: "Failed to copy URL", duration: 3000 }),
687
+ () => toast.show({ variant: "success", message: "URL copied!", duration: 3000 }),
688
+ () => toast.show({ variant: "error", message: "Failed to copy", duration: 3000 }),
645
689
  )
646
690
  }}
647
691
  >
648
- <text fg={theme.selectedListItemText} attributes={TextAttributes.BOLD}>[ Copy URL ]</text>
692
+ <text fg={theme.text} attributes={TextAttributes.BOLD}>[ Copy URL ]</text>
649
693
  </box>
650
694
  </box>
651
695
  <box paddingTop={1}>
@@ -654,6 +698,14 @@ function DialogAuthServiceNowOAuth() {
654
698
  <text fg={theme.textMuted}> 2. The page may show an error (this is expected)</text>
655
699
  <text fg={theme.textMuted}> 3. Copy the FULL URL from your browser address bar</text>
656
700
  </box>
701
+ <box flexDirection="row" gap={1}>
702
+ <text fg={theme.text}>o</text>
703
+ <text fg={theme.textMuted}>open</text>
704
+ <text fg={theme.text}> c</text>
705
+ <text fg={theme.textMuted}>copy</text>
706
+ <text fg={theme.text}> esc</text>
707
+ <text fg={theme.textMuted}>back</text>
708
+ </box>
657
709
  </Show>
658
710
  <text fg={theme.text}>Paste the callback URL from your browser address bar:</text>
659
711
  <textarea