Skip to content

Commit 6840eb8

Browse files
committed
feat: actual command gen
1 parent bb390ba commit 6840eb8

File tree

3 files changed

+50
-76
lines changed

3 files changed

+50
-76
lines changed

src/lib/shell.ts

Lines changed: 35 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -39,84 +39,50 @@ function getShell(): ShellName {
3939
* @param {string} commandToRun - The command to run after setting the variables.
4040
* @returns {string} The formatted script string.
4141
*/
42-
// eslint-disable-next-line complexity
43-
function generateEnvScript(
42+
export function generateEnvScript(
4443
envVars: EnvVars,
4544
commandToRun: string = "",
4645
): string {
4746
const shell = getShell()
48-
const commands: Array<string> = []
49-
50-
for (const [key, value] of Object.entries(envVars)) {
51-
if (value === undefined) {
52-
continue // Skip undefined values
47+
const filteredEnvVars = Object.entries(envVars).filter(
48+
([, value]) => value !== undefined,
49+
) as Array<[string, string]>
50+
51+
let commandBlock: string
52+
53+
switch (shell) {
54+
case "powershell": {
55+
commandBlock = filteredEnvVars
56+
.map(([key, value]) => `$env:${key} = ${value}`)
57+
.join("; ")
58+
break
5359
}
54-
55-
// Best-effort quoting to handle spaces and special characters.
56-
// PowerShell and cmd handle quotes differently from Unix shells.
57-
let escapedValue: string
58-
if (shell === "cmd") {
59-
// CMD is tricky with quotes. Often it's safer without them if no spaces.
60-
escapedValue = value.includes(" ") ? `"${value}"` : value
61-
} else {
62-
// For PowerShell and Unix shells, wrapping in double quotes is generally safe.
63-
// We escape any internal double quotes for robustness.
64-
escapedValue = `"${value.replaceAll('"', String.raw`\"`)}"`
60+
case "cmd": {
61+
commandBlock = filteredEnvVars
62+
.map(([key, value]) => `set ${key}=${value}`)
63+
.join(" & ")
64+
break
6565
}
66-
67-
switch (shell) {
68-
case "powershell": {
69-
commands.push(`$env:${key} = ${escapedValue}`)
70-
break
71-
}
72-
case "cmd": {
73-
commands.push(`set ${key}=${escapedValue}`)
74-
break
75-
}
76-
case "fish": {
77-
// Fish prefers 'set -gx KEY VALUE' syntax.
78-
commands.push(`set -gx ${key} ${escapedValue}`)
79-
break
80-
}
81-
default: {
82-
commands.push(`export ${key}=${escapedValue}`)
83-
break
84-
}
66+
case "fish": {
67+
commandBlock = filteredEnvVars
68+
.map(([key, value]) => `set -gx ${key} ${value}`)
69+
.join("; ")
70+
break
71+
}
72+
default: {
73+
// bash, zsh, sh
74+
const assignments = filteredEnvVars
75+
.map(([key, value]) => `${key}=${value}`)
76+
.join(" ")
77+
commandBlock = filteredEnvVars.length > 0 ? `export ${assignments}` : ""
78+
break
8579
}
8680
}
8781

88-
const intro = `# Paste the following into your terminal (${shell}) to set environment variables and run the command:\n`
89-
const finalCommand = commandToRun ? `\n${commandToRun}` : ""
90-
const commandBlock = commands.join("\n")
91-
92-
if (shell === "cmd") {
93-
// For cmd, chaining is difficult. Presenting a block to copy is most reliable.
94-
const runInstruction =
95-
finalCommand ? `\n\n# Now, run the command:\n${commandToRun}` : ""
96-
return `${intro}${commandBlock}${runInstruction}`
82+
if (commandBlock && commandToRun) {
83+
const separator = shell === "cmd" ? " & " : " && "
84+
return `${commandBlock}${separator}${commandToRun}`
9785
}
9886

99-
return `${intro}${commandBlock}${finalCommand}`
87+
return commandBlock || commandToRun
10088
}
101-
102-
// --- Example Usage ---
103-
104-
// 1. Define the environment variables and the final command.
105-
const serverUrl = "http://localhost:1234/v1"
106-
const selectedModel = "claude-3-opus-20240229"
107-
const selectedSmallModel = "claude-3-haiku-20240307"
108-
109-
const envVariables: EnvVars = {
110-
ANTHROPIC_BASE_URL: serverUrl,
111-
ANTHROPIC_AUTH_TOKEN: "your-secret-token",
112-
ANTHROPIC_MODEL: selectedModel,
113-
ANTHROPIC_SMALL_FAST_MODEL: selectedSmallModel,
114-
// You can include undefined values; the function will safely skip them.
115-
OPTIONAL_SETTING: undefined,
116-
}
117-
118-
const command = 'claude "What is the airspeed velocity of an unladen swallow?"'
119-
120-
// 2. Generate and print the script.
121-
const scriptString = generateEnvScript(envVariables, command)
122-
console.log(scriptString)

src/main.ts

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
11
#!/usr/bin/env node
22

33
import { defineCommand, runMain } from "citty"
4+
import clipboard from "clipboardy"
45
import consola from "consola"
5-
import child from "node:child_process"
66
import { serve, type ServerHandler } from "srvx"
77
import invariant from "tiny-invariant"
88

99
import { auth } from "./auth"
1010
import { ensurePaths } from "./lib/paths"
11+
import { generateEnvScript } from "./lib/shell"
1112
import { state } from "./lib/state"
1213
import { setupCopilotToken, setupGitHubToken } from "./lib/token"
1314
import { cacheModels, cacheVSCodeVersion } from "./lib/utils"
@@ -78,17 +79,18 @@ export async function runServer(options: RunServerOptions): Promise<void> {
7879
},
7980
)
8081

81-
child.spawn("claude", [], {
82-
detached: true,
83-
stdio: "ignore",
84-
shell: true,
85-
env: {
82+
const command = generateEnvScript(
83+
{
8684
ANTHROPIC_BASE_URL: serverUrl,
8785
ANTHROPIC_AUTH_TOKEN: "dummy",
8886
ANTHROPIC_MODEL: selectedModel,
8987
ANTHROPIC_SMALL_FAST_MODEL: selectedSmallModel,
9088
},
91-
})
89+
"claude",
90+
)
91+
92+
clipboard.writeSync(command)
93+
consola.success("Copied Claude Code command to clipboard!")
9294
}
9395

9496
serve({

src/routes/messages/stream-translation.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,12 @@ export function translateChunkToAnthropicEvents(
2323
): Array<AnthropicStreamEventData> {
2424
const events: Array<AnthropicStreamEventData> = []
2525

26+
// @ts-expect-error sometimes chunk.choices is empty, and idk why
27+
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
28+
if (chunk.choices.length === 0) {
29+
return events
30+
}
31+
2632
const choice = chunk.choices[0]
2733
const { delta } = choice
2834

0 commit comments

Comments
 (0)