mirror of
https://github.com/block/goose.git
synced 2026-04-28 03:29:36 +00:00
chore: set goose binaries as executable in package.json (#8589)
This commit is contained in:
parent
fd93865951
commit
67b90205ed
15 changed files with 452 additions and 390 deletions
|
|
@ -7,6 +7,9 @@
|
|||
"type": "git",
|
||||
"url": "git+https://github.com/aaif-goose/goose.git"
|
||||
},
|
||||
"bin": {
|
||||
"goose": "bin/goose"
|
||||
},
|
||||
"keywords": [
|
||||
"goose",
|
||||
"ai",
|
||||
|
|
|
|||
|
|
@ -7,6 +7,9 @@
|
|||
"type": "git",
|
||||
"url": "git+https://github.com/aaif-goose/goose.git"
|
||||
},
|
||||
"bin": {
|
||||
"goose": "bin/goose"
|
||||
},
|
||||
"keywords": [
|
||||
"goose",
|
||||
"ai",
|
||||
|
|
|
|||
|
|
@ -7,6 +7,9 @@
|
|||
"type": "git",
|
||||
"url": "git+https://github.com/aaif-goose/goose.git"
|
||||
},
|
||||
"bin": {
|
||||
"goose": "bin/goose"
|
||||
},
|
||||
"keywords": [
|
||||
"goose",
|
||||
"ai",
|
||||
|
|
|
|||
|
|
@ -7,6 +7,9 @@
|
|||
"type": "git",
|
||||
"url": "git+https://github.com/aaif-goose/goose.git"
|
||||
},
|
||||
"bin": {
|
||||
"goose": "bin/goose"
|
||||
},
|
||||
"keywords": [
|
||||
"goose",
|
||||
"ai",
|
||||
|
|
|
|||
|
|
@ -7,6 +7,9 @@
|
|||
"type": "git",
|
||||
"url": "git+https://github.com/aaif-goose/goose.git"
|
||||
},
|
||||
"bin": {
|
||||
"goose": "bin/goose.exe"
|
||||
},
|
||||
"keywords": [
|
||||
"goose",
|
||||
"ai",
|
||||
|
|
|
|||
32
ui/pnpm-lock.yaml
generated
32
ui/pnpm-lock.yaml
generated
|
|
@ -667,6 +667,22 @@ importers:
|
|||
typescript:
|
||||
specifier: ~5.9.3
|
||||
version: 5.9.3
|
||||
optionalDependencies:
|
||||
'@aaif/goose-binary-darwin-arm64':
|
||||
specifier: workspace:*
|
||||
version: link:../goose-binary/goose-binary-darwin-arm64
|
||||
'@aaif/goose-binary-darwin-x64':
|
||||
specifier: workspace:*
|
||||
version: link:../goose-binary/goose-binary-darwin-x64
|
||||
'@aaif/goose-binary-linux-arm64':
|
||||
specifier: workspace:*
|
||||
version: link:../goose-binary/goose-binary-linux-arm64
|
||||
'@aaif/goose-binary-linux-x64':
|
||||
specifier: workspace:*
|
||||
version: link:../goose-binary/goose-binary-linux-x64
|
||||
'@aaif/goose-binary-win32-x64':
|
||||
specifier: workspace:*
|
||||
version: link:../goose-binary/goose-binary-win32-x64
|
||||
|
||||
text:
|
||||
dependencies:
|
||||
|
|
@ -716,22 +732,6 @@ importers:
|
|||
typescript:
|
||||
specifier: ^5.7.0
|
||||
version: 5.9.3
|
||||
optionalDependencies:
|
||||
'@aaif/goose-binary-darwin-arm64':
|
||||
specifier: workspace:*
|
||||
version: link:../goose-binary/goose-binary-darwin-arm64
|
||||
'@aaif/goose-binary-darwin-x64':
|
||||
specifier: workspace:*
|
||||
version: link:../goose-binary/goose-binary-darwin-x64
|
||||
'@aaif/goose-binary-linux-arm64':
|
||||
specifier: workspace:*
|
||||
version: link:../goose-binary/goose-binary-linux-arm64
|
||||
'@aaif/goose-binary-linux-x64':
|
||||
specifier: workspace:*
|
||||
version: link:../goose-binary/goose-binary-linux-x64
|
||||
'@aaif/goose-binary-win32-x64':
|
||||
specifier: workspace:*
|
||||
version: link:../goose-binary/goose-binary-win32-x64
|
||||
|
||||
packages:
|
||||
|
||||
|
|
|
|||
|
|
@ -79,7 +79,6 @@ apt-get install -y -qq --no-install-recommends \
|
|||
echo "==> Compiling goose (this takes a while)..."
|
||||
cargo build --release --bin goose
|
||||
cp /build/target/release/goose /output/goose
|
||||
chmod +x /output/goose
|
||||
echo "==> Done"
|
||||
'
|
||||
|
||||
|
|
@ -123,8 +122,7 @@ WORKDIR /build
|
|||
COPY . .
|
||||
RUN mkdir -p /output && \
|
||||
cargo build --release --bin goose && \
|
||||
cp target/release/goose /output/goose && \
|
||||
chmod +x /output/goose
|
||||
cp target/release/goose /output/goose
|
||||
DEOF
|
||||
|
||||
# Build in Docker and extract the binary
|
||||
|
|
@ -141,7 +139,6 @@ DEOF
|
|||
docker cp "${cid}:/output/goose" "${pkg_dir}/goose"
|
||||
docker rm "${cid}" >/dev/null
|
||||
docker rmi "${iid}" >/dev/null 2>&1 || true
|
||||
chmod +x "${pkg_dir}/goose"
|
||||
|
||||
rm -rf "${ctx}"
|
||||
|
||||
|
|
|
|||
|
|
@ -17,6 +17,16 @@
|
|||
"type": "module",
|
||||
"main": "./dist/index.js",
|
||||
"types": "./dist/index.d.ts",
|
||||
"exports": {
|
||||
".": {
|
||||
"types": "./dist/index.d.ts",
|
||||
"default": "./dist/index.js"
|
||||
},
|
||||
"./node": {
|
||||
"types": "./dist/resolve-binary.d.ts",
|
||||
"default": "./dist/resolve-binary.js"
|
||||
}
|
||||
},
|
||||
"files": [
|
||||
"dist"
|
||||
],
|
||||
|
|
@ -35,6 +45,13 @@
|
|||
"peerDependencies": {
|
||||
"@agentclientprotocol/sdk": "*"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@aaif/goose-binary-darwin-arm64": "workspace:*",
|
||||
"@aaif/goose-binary-darwin-x64": "workspace:*",
|
||||
"@aaif/goose-binary-linux-arm64": "workspace:*",
|
||||
"@aaif/goose-binary-linux-x64": "workspace:*",
|
||||
"@aaif/goose-binary-win32-x64": "workspace:*"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@agentclientprotocol/sdk": "^0.14.1",
|
||||
"@hey-api/openapi-ts": "^0.92.3",
|
||||
|
|
|
|||
43
ui/sdk/src/resolve-binary.ts
Normal file
43
ui/sdk/src/resolve-binary.ts
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
import { createRequire } from "node:module";
|
||||
import { dirname, join } from "node:path";
|
||||
|
||||
const PLATFORMS: Record<string, string> = {
|
||||
"darwin-arm64": "@aaif/goose-binary-darwin-arm64",
|
||||
"darwin-x64": "@aaif/goose-binary-darwin-x64",
|
||||
"linux-arm64": "@aaif/goose-binary-linux-arm64",
|
||||
"linux-x64": "@aaif/goose-binary-linux-x64",
|
||||
"win32-x64": "@aaif/goose-binary-win32-x64",
|
||||
};
|
||||
|
||||
/**
|
||||
* Resolves the path to the goose binary.
|
||||
*
|
||||
* Resolution order:
|
||||
* 1. `GOOSE_BINARY` environment variable (explicit override)
|
||||
* 2. Platform-specific `@aaif/goose-binary-*` optional dependency
|
||||
*
|
||||
* @throws if no binary can be found
|
||||
*/
|
||||
export function resolveGooseBinary(): string {
|
||||
const envBinary = process.env.GOOSE_BINARY;
|
||||
if (envBinary) return envBinary;
|
||||
|
||||
const key = `${process.platform}-${process.arch}`;
|
||||
const pkg = PLATFORMS[key];
|
||||
if (!pkg) {
|
||||
throw new Error(
|
||||
`No goose binary available for ${key}. Set GOOSE_BINARY to the path of a goose binary.`,
|
||||
);
|
||||
}
|
||||
|
||||
try {
|
||||
const require = createRequire(import.meta.url);
|
||||
const pkgDir = dirname(require.resolve(`${pkg}/package.json`));
|
||||
const binName = process.platform === "win32" ? "goose.exe" : "goose";
|
||||
return join(pkgDir, "bin", binName);
|
||||
} catch {
|
||||
throw new Error(
|
||||
`goose binary package ${pkg} is not installed. Set GOOSE_BINARY or install the native package.`,
|
||||
);
|
||||
}
|
||||
}
|
||||
1
ui/text/.gitignore
vendored
1
ui/text/.gitignore
vendored
|
|
@ -1,3 +1,2 @@
|
|||
dist
|
||||
node_modules
|
||||
server-binary.json
|
||||
|
|
|
|||
|
|
@ -19,15 +19,11 @@
|
|||
"goose": "dist/tui.js"
|
||||
},
|
||||
"files": [
|
||||
"dist",
|
||||
"scripts/postinstall.mjs",
|
||||
"server-binary.json"
|
||||
"dist"
|
||||
],
|
||||
"scripts": {
|
||||
"build": "tsc",
|
||||
"dev:binary": "node scripts/dev-binary.mjs",
|
||||
"start": "npm run dev:binary && tsx src/tui.tsx",
|
||||
"postinstall": "node scripts/postinstall.mjs",
|
||||
"start": "node scripts/dev-start.mjs",
|
||||
"lint": "tsc --noEmit"
|
||||
},
|
||||
"dependencies": {
|
||||
|
|
@ -41,13 +37,6 @@
|
|||
"meow": "^13.2.0",
|
||||
"react": "^19.2.4"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@aaif/goose-binary-darwin-arm64": "workspace:*",
|
||||
"@aaif/goose-binary-darwin-x64": "workspace:*",
|
||||
"@aaif/goose-binary-linux-arm64": "workspace:*",
|
||||
"@aaif/goose-binary-linux-x64": "workspace:*",
|
||||
"@aaif/goose-binary-win32-x64": "workspace:*"
|
||||
},
|
||||
"overrides": {
|
||||
"react": "^19.2.4"
|
||||
},
|
||||
|
|
|
|||
|
|
@ -1,103 +0,0 @@
|
|||
#!/usr/bin/env node
|
||||
|
||||
// For development: ensures the Rust binary is built from source and
|
||||
// points server-binary.json to the local target/release/goose binary.
|
||||
// Rebuilds if source files are newer than the binary.
|
||||
|
||||
import { writeFileSync, existsSync, statSync } from "node:fs";
|
||||
import { join, dirname } from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import { spawnSync } from "node:child_process";
|
||||
|
||||
const __dirname = dirname(fileURLToPath(import.meta.url));
|
||||
const projectRoot = join(__dirname, "..", "..", "..");
|
||||
const binaryName = process.platform === "win32" ? "goose.exe" : "goose";
|
||||
const binaryPath = join(projectRoot, "target", "release", binaryName);
|
||||
|
||||
// Verify we're in a development environment with Cargo.toml
|
||||
const cargoToml = join(projectRoot, "Cargo.toml");
|
||||
if (!existsSync(cargoToml)) {
|
||||
console.error("Error: Not in a Rust workspace (Cargo.toml not found)");
|
||||
console.error("This script is for development only. In production, use the prebuilt binaries.");
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
function needsRebuild() {
|
||||
if (!existsSync(binaryPath)) {
|
||||
console.log("Binary not found, needs build");
|
||||
return true;
|
||||
}
|
||||
|
||||
const binaryMtime = statSync(binaryPath).mtimeMs;
|
||||
|
||||
// Check if any Rust source files are newer than the binary
|
||||
const cargoLock = join(projectRoot, "Cargo.lock");
|
||||
|
||||
if (existsSync(cargoToml) && statSync(cargoToml).mtimeMs > binaryMtime) {
|
||||
console.log("Cargo.toml changed, needs rebuild");
|
||||
return true;
|
||||
}
|
||||
|
||||
if (existsSync(cargoLock) && statSync(cargoLock).mtimeMs > binaryMtime) {
|
||||
console.log("Cargo.lock changed, needs rebuild");
|
||||
return true;
|
||||
}
|
||||
|
||||
// Check if goose-acp crate sources are newer than the binary
|
||||
const acpDir = join(projectRoot, "crates", "goose-acp");
|
||||
if (existsSync(acpDir)) {
|
||||
const result = spawnSync(
|
||||
"find",
|
||||
[acpDir, "-type", "f", "(", "-name", "*.rs", "-o", "-name", "Cargo.toml", ")", "-newer", binaryPath],
|
||||
{ encoding: "utf-8" },
|
||||
);
|
||||
const changed = (result.stdout ?? "").trim();
|
||||
if (changed) {
|
||||
const first = changed.split("\n")[0];
|
||||
console.log(`goose-acp changed (e.g. ${first}), needs rebuild`);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
function buildBinary() {
|
||||
console.log("Building goose-cli from source...");
|
||||
const result = spawnSync(
|
||||
"cargo",
|
||||
["build", "--release", "-p", "goose-cli"],
|
||||
{
|
||||
cwd: projectRoot,
|
||||
stdio: "inherit",
|
||||
}
|
||||
);
|
||||
|
||||
if (result.error) {
|
||||
console.error(`Failed to build: ${result.error.message}`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
if (result.status !== 0) {
|
||||
console.error(`Build failed with exit code ${result.status}`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
console.log(`Built goose binary at ${binaryPath}`);
|
||||
}
|
||||
|
||||
// Main logic
|
||||
if (needsRebuild()) {
|
||||
buildBinary();
|
||||
} else {
|
||||
console.log("Binary is up to date, skipping build");
|
||||
}
|
||||
|
||||
// Write the server-binary.json to point to the local build
|
||||
const outDir = join(__dirname, "..");
|
||||
writeFileSync(
|
||||
join(outDir, "server-binary.json"),
|
||||
JSON.stringify({ binaryPath }, null, 2) + "\n",
|
||||
);
|
||||
|
||||
console.log(`Using local goose binary at ${binaryPath}`);
|
||||
36
ui/text/scripts/dev-start.mjs
Normal file
36
ui/text/scripts/dev-start.mjs
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
#!/usr/bin/env node
|
||||
|
||||
// Development entrypoint: ensures a goose binary is available, then launches
|
||||
// the TUI via tsx. Skips the cargo build if GOOSE_BINARY is already set.
|
||||
|
||||
import { execFileSync } from "node:child_process";
|
||||
import { existsSync } from "node:fs";
|
||||
import { join, dirname } from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
|
||||
const __dirname = dirname(fileURLToPath(import.meta.url));
|
||||
const repoRoot = join(__dirname, "..", "..", "..");
|
||||
|
||||
if (!process.env.GOOSE_BINARY) {
|
||||
const binName = process.platform === "win32" ? "goose.exe" : "goose";
|
||||
const binaryPath = join(repoRoot, "target", "debug", binName);
|
||||
|
||||
console.log("Building goose (debug)…");
|
||||
execFileSync("cargo", ["build", "-p", "goose-cli"], {
|
||||
cwd: repoRoot,
|
||||
stdio: "inherit",
|
||||
});
|
||||
|
||||
if (!existsSync(binaryPath)) {
|
||||
console.error(`Build succeeded but binary not found at ${binaryPath}`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
process.env.GOOSE_BINARY = binaryPath;
|
||||
}
|
||||
|
||||
execFileSync("tsx", [join(__dirname, "..", "src", "tui.tsx"), ...process.argv.slice(2)], {
|
||||
cwd: process.cwd(),
|
||||
stdio: "inherit",
|
||||
env: process.env,
|
||||
});
|
||||
|
|
@ -1,63 +0,0 @@
|
|||
#!/usr/bin/env node
|
||||
|
||||
// Resolves the path to the goose binary from the platform-specific
|
||||
// optional dependency. Writes the result to a JSON file that the CLI reads at
|
||||
// startup so it can spawn the server automatically.
|
||||
|
||||
import { writeFileSync, mkdirSync, chmodSync } from "node:fs";
|
||||
import { createRequire } from "node:module";
|
||||
import { dirname, join } from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
|
||||
const __dirname = dirname(fileURLToPath(import.meta.url));
|
||||
const require = createRequire(import.meta.url);
|
||||
|
||||
const PLATFORMS = {
|
||||
"darwin-arm64": "@aaif/goose-binary-darwin-arm64",
|
||||
"darwin-x64": "@aaif/goose-binary-darwin-x64",
|
||||
"linux-arm64": "@aaif/goose-binary-linux-arm64",
|
||||
"linux-x64": "@aaif/goose-binary-linux-x64",
|
||||
"win32-x64": "@aaif/goose-binary-win32-x64",
|
||||
};
|
||||
|
||||
const key = `${process.platform}-${process.arch}`;
|
||||
const pkg = PLATFORMS[key];
|
||||
|
||||
if (!pkg) {
|
||||
console.warn(
|
||||
`@aaif/goose: no prebuilt goose binary for ${key}. ` +
|
||||
`You will need to provide a server URL manually with --server.`,
|
||||
);
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
let binaryPath;
|
||||
try {
|
||||
// Resolve the package directory, then point at the binary inside it
|
||||
const pkgDir = dirname(require.resolve(`${pkg}/package.json`));
|
||||
const binName = process.platform === "win32" ? "goose.exe" : "goose";
|
||||
binaryPath = join(pkgDir, "bin", binName);
|
||||
} catch {
|
||||
// The optional dependency wasn't installed (e.g. wrong platform). That's fine.
|
||||
console.warn(
|
||||
`@aaif/goose: optional dependency ${pkg} not installed. ` +
|
||||
`You will need to provide a server URL manually with --server.`,
|
||||
);
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
// Ensure the binary is executable (npm may strip permissions during packaging)
|
||||
if (process.platform !== "win32") {
|
||||
try {
|
||||
chmodSync(binaryPath, 0o755);
|
||||
} catch {}
|
||||
}
|
||||
|
||||
const outDir = join(__dirname, "..");
|
||||
mkdirSync(outDir, { recursive: true });
|
||||
writeFileSync(
|
||||
join(outDir, "server-binary.json"),
|
||||
JSON.stringify({ binaryPath }, null, 2) + "\n",
|
||||
);
|
||||
|
||||
console.log(`@aaif/goose: found native goose binary at ${binaryPath}`);
|
||||
|
|
@ -5,9 +5,6 @@ import { MultilineInput } from "ink-multiline-input";
|
|||
import meow from "meow";
|
||||
import { spawn } from "node:child_process";
|
||||
import { Readable, Writable } from "node:stream";
|
||||
import { readFileSync } from "node:fs";
|
||||
import { dirname, join } from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import type {
|
||||
SessionNotification,
|
||||
RequestPermissionRequest,
|
||||
|
|
@ -19,6 +16,7 @@ import type {
|
|||
} from "@agentclientprotocol/sdk";
|
||||
import { ndJsonStream } from "@agentclientprotocol/sdk";
|
||||
import { GooseClient } from "@aaif/goose-sdk";
|
||||
import { resolveGooseBinary } from "@aaif/goose-sdk/node";
|
||||
import Onboarding from "./onboarding.js";
|
||||
import ConfigureScreen, { ConfigureIntent } from "./configure.js";
|
||||
import ExtensionsManager from "./extensions.js";
|
||||
|
|
@ -35,7 +33,15 @@ import {
|
|||
import { Header } from "./components/Header.js";
|
||||
import { Rule } from "./components/Rule.js";
|
||||
import { isErrorStatus, formatError } from "./utils.js";
|
||||
import { CRANBERRY, TEAL, GOLD, TEXT_PRIMARY, TEXT_SECONDARY, TEXT_DIM, RULE_COLOR } from "./colors.js";
|
||||
import {
|
||||
CRANBERRY,
|
||||
TEAL,
|
||||
GOLD,
|
||||
TEXT_PRIMARY,
|
||||
TEXT_SECONDARY,
|
||||
TEXT_DIM,
|
||||
RULE_COLOR,
|
||||
} from "./colors.js";
|
||||
import { Spinner, SPINNER_FRAMES } from "./components/Spinner.js";
|
||||
import {
|
||||
PASTE_THRESHOLD,
|
||||
|
|
@ -137,7 +143,9 @@ const InputBar = React.memo(function InputBar({
|
|||
flexShrink={0}
|
||||
>
|
||||
<Box>
|
||||
<Text color={CRANBERRY} bold>{"❯ "}</Text>
|
||||
<Text color={CRANBERRY} bold>
|
||||
{"❯ "}
|
||||
</Text>
|
||||
{isPasteMode ? (
|
||||
<Box width={contentWidth} justifyContent="space-between">
|
||||
<Box width={Math.max(contentWidth - 20, 10)}>
|
||||
|
|
@ -145,10 +153,16 @@ const InputBar = React.memo(function InputBar({
|
|||
{(() => {
|
||||
const text = pastedFull;
|
||||
const availableWidth = Math.max(contentWidth - 20, 10);
|
||||
const flat = text.replace(/\n/g, " ").replace(/\s+/g, " ").trim();
|
||||
const flat = text
|
||||
.replace(/\n/g, " ")
|
||||
.replace(/\s+/g, " ")
|
||||
.trim();
|
||||
if (flat.length <= availableWidth) return flat;
|
||||
const suffix = ` (${flat.length.toLocaleString()} chars)`;
|
||||
const previewLen = Math.max(availableWidth - suffix.length - 1, 5);
|
||||
const previewLen = Math.max(
|
||||
availableWidth - suffix.length - 1,
|
||||
5,
|
||||
);
|
||||
return flat.slice(0, previewLen) + "…" + suffix;
|
||||
})()}
|
||||
</Text>
|
||||
|
|
@ -170,10 +184,13 @@ const InputBar = React.memo(function InputBar({
|
|||
newline: (key) => key.return && key.ctrl,
|
||||
}}
|
||||
useCustomInput={(handler, isActive) => {
|
||||
useInput((ch, key) => {
|
||||
if (key.shift && (key.upArrow || key.downArrow)) return;
|
||||
handler(ch, key);
|
||||
}, { isActive });
|
||||
useInput(
|
||||
(ch, key) => {
|
||||
if (key.shift && (key.upArrow || key.downArrow)) return;
|
||||
handler(ch, key);
|
||||
},
|
||||
{ isActive },
|
||||
);
|
||||
}}
|
||||
/>
|
||||
{scrollHint && <Text color={TEXT_DIM}>shift+↑↓ history</Text>}
|
||||
|
|
@ -227,36 +244,64 @@ function buildContentLines({
|
|||
const safeWidth = Math.max(width, 20);
|
||||
|
||||
const turnId = String(turnIndex);
|
||||
lines.push(...renderUserPrompt(turn.userText, safeWidth, turnId, (text: string, availableWidth: number) => {
|
||||
const flat = text.replace(/\n/g, " ").replace(/\s+/g, " ").trim();
|
||||
const safeWidth = Math.max(availableWidth, 10);
|
||||
const maxPreview = Math.max(safeWidth - 30, Math.min(SENT_PREVIEW_LEN, safeWidth - 10));
|
||||
if (flat.length <= maxPreview + 10) {
|
||||
return (
|
||||
<Box width={safeWidth}>
|
||||
<Text color={TEXT_PRIMARY} bold wrap="wrap">{flat}</Text>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
const preview = flat.slice(0, maxPreview) + "…";
|
||||
const remaining = flat.length - maxPreview;
|
||||
return (
|
||||
<Box width={safeWidth}>
|
||||
<Text color={TEXT_PRIMARY} bold wrap="wrap">{preview}</Text>
|
||||
<Text color={TEXT_DIM}> ({remaining.toLocaleString()} more chars)</Text>
|
||||
</Box>
|
||||
);
|
||||
}));
|
||||
lines.push(
|
||||
...renderUserPrompt(
|
||||
turn.userText,
|
||||
safeWidth,
|
||||
turnId,
|
||||
(text: string, availableWidth: number) => {
|
||||
const flat = text.replace(/\n/g, " ").replace(/\s+/g, " ").trim();
|
||||
const safeWidth = Math.max(availableWidth, 10);
|
||||
const maxPreview = Math.max(
|
||||
safeWidth - 30,
|
||||
Math.min(SENT_PREVIEW_LEN, safeWidth - 10),
|
||||
);
|
||||
if (flat.length <= maxPreview + 10) {
|
||||
return (
|
||||
<Box width={safeWidth}>
|
||||
<Text color={TEXT_PRIMARY} bold wrap="wrap">
|
||||
{flat}
|
||||
</Text>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
const preview = flat.slice(0, maxPreview) + "…";
|
||||
const remaining = flat.length - maxPreview;
|
||||
return (
|
||||
<Box width={safeWidth}>
|
||||
<Text color={TEXT_PRIMARY} bold wrap="wrap">
|
||||
{preview}
|
||||
</Text>
|
||||
<Text color={TEXT_DIM}>
|
||||
{" "}
|
||||
({remaining.toLocaleString()} more chars)
|
||||
</Text>
|
||||
</Box>
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
// Process response items
|
||||
const hasToolCalls = turn.responseItems.some((it) => it.itemType === "tool_call");
|
||||
const hasToolCalls = turn.responseItems.some(
|
||||
(it) => it.itemType === "tool_call",
|
||||
);
|
||||
let tcIdx = 0;
|
||||
|
||||
for (let i = 0; i < turn.responseItems.length; i++) {
|
||||
const item = turn.responseItems[i]!;
|
||||
|
||||
if (item.itemType === "tool_call") {
|
||||
lines.push(...renderToolCallItem(item, i, safeWidth, toolCallsExpanded, tcIdx === 0, hasToolCalls));
|
||||
lines.push(
|
||||
...renderToolCallItem(
|
||||
item,
|
||||
i,
|
||||
safeWidth,
|
||||
toolCallsExpanded,
|
||||
tcIdx === 0,
|
||||
hasToolCalls,
|
||||
),
|
||||
);
|
||||
tcIdx++;
|
||||
} else if (item.itemType === "error") {
|
||||
lines.push(...renderErrorItem(item, i, safeWidth));
|
||||
|
|
@ -280,7 +325,12 @@ function buildContentLines({
|
|||
const hRule = "─".repeat(Math.max(dialogWidth - 2, 0));
|
||||
const permissionLines: React.ReactElement[] = [];
|
||||
|
||||
permissionLines.push(emptyLine(`pm-gap-${perm.toolTitle.slice(0, 10).replace(/[^a-zA-Z0-9]/g, '')}`, fullWidth));
|
||||
permissionLines.push(
|
||||
emptyLine(
|
||||
`pm-gap-${perm.toolTitle.slice(0, 10).replace(/[^a-zA-Z0-9]/g, "")}`,
|
||||
fullWidth,
|
||||
),
|
||||
);
|
||||
|
||||
permissionLines.push(
|
||||
<Box key="pm-t" width={fullWidth} height={1}>
|
||||
|
|
@ -292,15 +342,27 @@ function buildContentLines({
|
|||
permissionLines.push(
|
||||
<Box key={key} width={fullWidth} height={1}>
|
||||
<Text color={GOLD}>│ </Text>
|
||||
<Box width={innerWidth} height={1}>{content}</Box>
|
||||
<Box width={innerWidth} height={1}>
|
||||
{content}
|
||||
</Box>
|
||||
<Text color={GOLD}> │</Text>
|
||||
</Box>,
|
||||
);
|
||||
};
|
||||
|
||||
row("pm-title", <Text color={GOLD} bold>🔒 Permission required</Text>);
|
||||
row(
|
||||
"pm-title",
|
||||
<Text color={GOLD} bold>
|
||||
🔒 Permission required
|
||||
</Text>,
|
||||
);
|
||||
row("pm-g1", <Text> </Text>);
|
||||
row("pm-tool", <Text wrap="truncate-end" color={TEXT_PRIMARY}>{perm.toolTitle}</Text>);
|
||||
row(
|
||||
"pm-tool",
|
||||
<Text wrap="truncate-end" color={TEXT_PRIMARY}>
|
||||
{perm.toolTitle}
|
||||
</Text>,
|
||||
);
|
||||
row("pm-g2", <Text> </Text>);
|
||||
|
||||
for (let i = 0; i < perm.options.length; i++) {
|
||||
|
|
@ -308,18 +370,22 @@ function buildContentLines({
|
|||
const k = PERMISSION_KEYS[opt.kind] ?? String(i + 1);
|
||||
const label = PERMISSION_LABELS[opt.kind] ?? opt.name;
|
||||
const active = i === selectedIdx;
|
||||
row(`pm-o${i}`, (
|
||||
row(
|
||||
`pm-o${i}`,
|
||||
<>
|
||||
<Text color={active ? GOLD : RULE_COLOR}>{active ? "▸ " : " "}</Text>
|
||||
<Text color={active ? TEXT_PRIMARY : TEXT_SECONDARY} bold={active}>
|
||||
[{k}] {label}
|
||||
</Text>
|
||||
</>
|
||||
));
|
||||
</>,
|
||||
);
|
||||
}
|
||||
|
||||
row("pm-g3", <Text> </Text>);
|
||||
row("pm-help", <Text color={TEXT_DIM}>↑↓ select · enter confirm · esc cancel</Text>);
|
||||
row(
|
||||
"pm-help",
|
||||
<Text color={TEXT_DIM}>↑↓ select · enter confirm · esc cancel</Text>,
|
||||
);
|
||||
|
||||
permissionLines.push(
|
||||
<Box key="pm-b" width={fullWidth} height={1}>
|
||||
|
|
@ -367,9 +433,11 @@ const Viewport = React.memo(function Viewport({
|
|||
const above = startIdx;
|
||||
elements.push(
|
||||
<Box key="si-up" width={width} height={1} justifyContent="center">
|
||||
{above > 0
|
||||
? <Text color={TEXT_DIM}>▲ {above} more (↑)</Text>
|
||||
: <Text> </Text>}
|
||||
{above > 0 ? (
|
||||
<Text color={TEXT_DIM}>▲ {above} more (↑)</Text>
|
||||
) : (
|
||||
<Text> </Text>
|
||||
)}
|
||||
</Box>,
|
||||
);
|
||||
}
|
||||
|
|
@ -383,9 +451,11 @@ const Viewport = React.memo(function Viewport({
|
|||
const below = total - endIdx;
|
||||
elements.push(
|
||||
<Box key="si-dn" width={width} height={1} justifyContent="center">
|
||||
{below > 0
|
||||
? <Text color={TEXT_DIM}>▼ {below} more (↓)</Text>
|
||||
: <Text> </Text>}
|
||||
{below > 0 ? (
|
||||
<Text color={TEXT_DIM}>▼ {below} more (↓)</Text>
|
||||
) : (
|
||||
<Text> </Text>
|
||||
)}
|
||||
</Box>,
|
||||
);
|
||||
}
|
||||
|
|
@ -394,7 +464,11 @@ const Viewport = React.memo(function Viewport({
|
|||
const constrainedHeight = Math.max(height, 1);
|
||||
|
||||
return (
|
||||
<Box flexDirection="column" height={constrainedHeight} width={constrainedWidth}>
|
||||
<Box
|
||||
flexDirection="column"
|
||||
height={constrainedHeight}
|
||||
width={constrainedWidth}
|
||||
>
|
||||
{elements}
|
||||
</Box>
|
||||
);
|
||||
|
|
@ -438,11 +512,15 @@ const SplashScreen = React.memo(function SplashScreen({
|
|||
{topPad > 0 && <Box height={topPad} />}
|
||||
<Box flexDirection="column" alignItems="center">
|
||||
{frame.map((line, i) => (
|
||||
<Text key={i} color={TEXT_PRIMARY}>{line}</Text>
|
||||
<Text key={i} color={TEXT_PRIMARY}>
|
||||
{line}
|
||||
</Text>
|
||||
))}
|
||||
</Box>
|
||||
<Box marginTop={1}>
|
||||
<Text color={TEXT_PRIMARY} bold>goose</Text>
|
||||
<Text color={TEXT_PRIMARY} bold>
|
||||
goose
|
||||
</Text>
|
||||
</Box>
|
||||
<Box alignItems="center">
|
||||
<Text color={TEXT_DIM}>your on-machine AI agent</Text>
|
||||
|
|
@ -484,7 +562,9 @@ function App({
|
|||
const [scrollOffset, setScrollOffset] = useState(0);
|
||||
const [pastedFull, setPastedFull] = useState<string | null>(null);
|
||||
const [needsOnboarding, setNeedsOnboarding] = useState(false);
|
||||
type Overlay = { screen: "configure"; intent: ConfigureIntent } | { screen: "extensions" };
|
||||
type Overlay =
|
||||
| { screen: "configure"; intent: ConfigureIntent }
|
||||
| { screen: "extensions" };
|
||||
const [overlay, setOverlay] = useState<Overlay | null>(null);
|
||||
|
||||
const clientRef = useRef<GooseClient | null>(null);
|
||||
|
|
@ -527,13 +607,22 @@ function App({
|
|||
if (lastItem.content.type === "text") {
|
||||
newItems[newItems.length - 1] = {
|
||||
...lastItem,
|
||||
content: { ...lastItem.content, text: lastItem.content.text + text },
|
||||
content: {
|
||||
...lastItem.content,
|
||||
text: lastItem.content.text + text,
|
||||
},
|
||||
};
|
||||
} else {
|
||||
newItems.push({ itemType: "content_chunk", content: { type: "text", text } });
|
||||
newItems.push({
|
||||
itemType: "content_chunk",
|
||||
content: { type: "text", text },
|
||||
});
|
||||
}
|
||||
} else {
|
||||
newItems.push({ itemType: "content_chunk", content: { type: "text", text } });
|
||||
newItems.push({
|
||||
itemType: "content_chunk",
|
||||
content: { type: "text", text },
|
||||
});
|
||||
}
|
||||
|
||||
return [...prev.slice(0, -1), { ...last, responseItems: newItems }];
|
||||
|
|
@ -605,7 +694,9 @@ function App({
|
|||
if (option === "cancelled") {
|
||||
resolve({ outcome: { outcome: "cancelled" } });
|
||||
} else {
|
||||
resolve({ outcome: { outcome: "selected", optionId: option.optionId } });
|
||||
resolve({
|
||||
outcome: { outcome: "selected", optionId: option.optionId },
|
||||
});
|
||||
}
|
||||
setPendingPermission(null);
|
||||
setPermissionIdx(0);
|
||||
|
|
@ -665,29 +756,32 @@ function App({
|
|||
[executePrompt, processQueue],
|
||||
);
|
||||
|
||||
const createSession = useCallback(async (client: GooseClient) => {
|
||||
setStatus("creating session…");
|
||||
setLoading(true);
|
||||
try {
|
||||
const session = await client.newSession({
|
||||
cwd: process.cwd(),
|
||||
mcpServers: [],
|
||||
});
|
||||
sessionIdRef.current = session.sessionId;
|
||||
setLoading(false);
|
||||
setStatus("ready");
|
||||
const createSession = useCallback(
|
||||
async (client: GooseClient) => {
|
||||
setStatus("creating session…");
|
||||
setLoading(true);
|
||||
try {
|
||||
const session = await client.newSession({
|
||||
cwd: process.cwd(),
|
||||
mcpServers: [],
|
||||
});
|
||||
sessionIdRef.current = session.sessionId;
|
||||
setLoading(false);
|
||||
setStatus("ready");
|
||||
|
||||
if (initialPrompt && !sentInitialPrompt.current) {
|
||||
sentInitialPrompt.current = true;
|
||||
await sendPrompt(initialPrompt);
|
||||
setTimeout(() => exit(), 100);
|
||||
if (initialPrompt && !sentInitialPrompt.current) {
|
||||
sentInitialPrompt.current = true;
|
||||
await sendPrompt(initialPrompt);
|
||||
setTimeout(() => exit(), 100);
|
||||
}
|
||||
} catch (e: unknown) {
|
||||
const errorMsg = formatError(e);
|
||||
setStatus(`failed: ${errorMsg}`);
|
||||
setLoading(false);
|
||||
}
|
||||
} catch (e: unknown) {
|
||||
const errorMsg = formatError(e);
|
||||
setStatus(`failed: ${errorMsg}`);
|
||||
setLoading(false);
|
||||
}
|
||||
}, [initialPrompt, sendPrompt, exit]);
|
||||
},
|
||||
[initialPrompt, sendPrompt, exit],
|
||||
);
|
||||
|
||||
const handleOnboardingComplete = useCallback(() => {
|
||||
setNeedsOnboarding(false);
|
||||
|
|
@ -751,8 +845,11 @@ function App({
|
|||
setStatus("checking provider…");
|
||||
let hasProvider = false;
|
||||
try {
|
||||
const resp = await client.goose.GooseConfigRead({ key: "GOOSE_PROVIDER" });
|
||||
hasProvider = resp.value != null && resp.value !== "" && resp.value !== "null";
|
||||
const resp = await client.goose.GooseConfigRead({
|
||||
key: "GOOSE_PROVIDER",
|
||||
});
|
||||
hasProvider =
|
||||
resp.value != null && resp.value !== "" && resp.value !== "null";
|
||||
} catch {
|
||||
hasProvider = false;
|
||||
}
|
||||
|
|
@ -774,10 +871,17 @@ function App({
|
|||
}
|
||||
})();
|
||||
|
||||
return () => { cancelled = true; };
|
||||
return () => {
|
||||
cancelled = true;
|
||||
};
|
||||
}, [
|
||||
serverConnection, initialPrompt, createSession,
|
||||
appendAgent, handleToolCall, handleToolCallUpdate, exit,
|
||||
serverConnection,
|
||||
initialPrompt,
|
||||
createSession,
|
||||
appendAgent,
|
||||
handleToolCall,
|
||||
handleToolCallUpdate,
|
||||
exit,
|
||||
]);
|
||||
|
||||
const handleSubmit = useCallback(
|
||||
|
|
@ -800,86 +904,118 @@ function App({
|
|||
[loading, sendPrompt],
|
||||
);
|
||||
|
||||
useInput((ch, key) => {
|
||||
if (key.escape || (ch === "c" && key.ctrl)) {
|
||||
if (pendingPermission) { resolvePermission("cancelled"); return; }
|
||||
if (key.escape && pastedFull !== null) return;
|
||||
exit();
|
||||
}
|
||||
useInput(
|
||||
(ch, key) => {
|
||||
if (key.escape || (ch === "c" && key.ctrl)) {
|
||||
if (pendingPermission) {
|
||||
resolvePermission("cancelled");
|
||||
return;
|
||||
}
|
||||
if (key.escape && pastedFull !== null) return;
|
||||
exit();
|
||||
}
|
||||
|
||||
if (!loading && !pendingPermission && sessionIdRef.current) {
|
||||
if (key.ctrl && (ch === "p" || ch === "P")) { setOverlay({ screen: "configure", intent: "provider" }); return; }
|
||||
if (key.ctrl && (ch === "m" || ch === "M")) { setOverlay({ screen: "configure", intent: "model" }); return; }
|
||||
if (key.ctrl && (ch === "e" || ch === "E")) { setOverlay({ screen: "extensions" }); return; }
|
||||
if (ch === "g" && key.ctrl) { setOverlay({ screen: "configure", intent: "provider" }); return; }
|
||||
}
|
||||
if (!loading && !pendingPermission && sessionIdRef.current) {
|
||||
if (key.ctrl && (ch === "p" || ch === "P")) {
|
||||
setOverlay({ screen: "configure", intent: "provider" });
|
||||
return;
|
||||
}
|
||||
if (key.ctrl && (ch === "m" || ch === "M")) {
|
||||
setOverlay({ screen: "configure", intent: "model" });
|
||||
return;
|
||||
}
|
||||
if (key.ctrl && (ch === "e" || ch === "E")) {
|
||||
setOverlay({ screen: "extensions" });
|
||||
return;
|
||||
}
|
||||
if (ch === "g" && key.ctrl) {
|
||||
setOverlay({ screen: "configure", intent: "provider" });
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (pendingPermission) {
|
||||
const opts = pendingPermission.options;
|
||||
if (key.upArrow) { setPermissionIdx((i) => (i - 1 + opts.length) % opts.length); return; }
|
||||
if (key.downArrow) { setPermissionIdx((i) => (i + 1) % opts.length); return; }
|
||||
if (key.return) {
|
||||
const sel = opts[permissionIdx];
|
||||
if (sel) resolvePermission({ optionId: sel.optionId });
|
||||
if (pendingPermission) {
|
||||
const opts = pendingPermission.options;
|
||||
if (key.upArrow) {
|
||||
setPermissionIdx((i) => (i - 1 + opts.length) % opts.length);
|
||||
return;
|
||||
}
|
||||
if (key.downArrow) {
|
||||
setPermissionIdx((i) => (i + 1) % opts.length);
|
||||
return;
|
||||
}
|
||||
if (key.return) {
|
||||
const sel = opts[permissionIdx];
|
||||
if (sel) resolvePermission({ optionId: sel.optionId });
|
||||
return;
|
||||
}
|
||||
const keyMap: Record<string, string> = {
|
||||
y: "allow_once",
|
||||
a: "allow_always",
|
||||
n: "reject_once",
|
||||
N: "reject_always",
|
||||
};
|
||||
const kind = keyMap[ch];
|
||||
if (kind) {
|
||||
const m = opts.find((o) => o.kind === kind);
|
||||
if (m) resolvePermission({ optionId: m.optionId });
|
||||
}
|
||||
return;
|
||||
}
|
||||
const keyMap: Record<string, string> = {
|
||||
y: "allow_once", a: "allow_always", n: "reject_once", N: "reject_always",
|
||||
};
|
||||
const kind = keyMap[ch];
|
||||
if (kind) {
|
||||
const m = opts.find((o) => o.kind === kind);
|
||||
if (m) resolvePermission({ optionId: m.optionId });
|
||||
|
||||
const viewingHistory =
|
||||
viewTurnIdx !== -1 && viewTurnIdx < turns.length - 1;
|
||||
const multilineOwnsArrows =
|
||||
!pendingPermission &&
|
||||
!initialPrompt &&
|
||||
!viewingHistory &&
|
||||
pastedFull === null;
|
||||
|
||||
if (key.tab) {
|
||||
const idx = viewTurnIdx === -1 ? turns.length - 1 : viewTurnIdx;
|
||||
const t = turns[idx];
|
||||
if (t && t.responseItems.some((it) => it.itemType === "tool_call")) {
|
||||
setToolCallsExpanded((prev) => !prev);
|
||||
}
|
||||
return;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
const viewingHistory = viewTurnIdx !== -1 && viewTurnIdx < turns.length - 1;
|
||||
const multilineOwnsArrows =
|
||||
!pendingPermission && !initialPrompt && !viewingHistory && pastedFull === null;
|
||||
|
||||
if (key.tab) {
|
||||
const idx = viewTurnIdx === -1 ? turns.length - 1 : viewTurnIdx;
|
||||
const t = turns[idx];
|
||||
if (t && t.responseItems.some((it) => it.itemType === "tool_call")) {
|
||||
setToolCallsExpanded((prev) => !prev);
|
||||
if (key.upArrow && !key.shift) {
|
||||
if (!multilineOwnsArrows) setScrollOffset((prev) => prev + 3);
|
||||
return;
|
||||
}
|
||||
if (key.downArrow && !key.shift) {
|
||||
if (!multilineOwnsArrows)
|
||||
setScrollOffset((prev) => Math.max(prev - 3, 0));
|
||||
return;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (key.upArrow && !key.shift) {
|
||||
if (!multilineOwnsArrows) setScrollOffset((prev) => prev + 3);
|
||||
return;
|
||||
}
|
||||
if (key.downArrow && !key.shift) {
|
||||
if (!multilineOwnsArrows) setScrollOffset((prev) => Math.max(prev - 3, 0));
|
||||
return;
|
||||
}
|
||||
|
||||
if (key.upArrow && key.shift) {
|
||||
setTurns((cur) => {
|
||||
if (cur.length <= 1) return cur;
|
||||
setViewTurnIdx((prev) => {
|
||||
const eff = prev === -1 ? cur.length - 1 : prev;
|
||||
return Math.max(eff - 1, 0);
|
||||
if (key.upArrow && key.shift) {
|
||||
setTurns((cur) => {
|
||||
if (cur.length <= 1) return cur;
|
||||
setViewTurnIdx((prev) => {
|
||||
const eff = prev === -1 ? cur.length - 1 : prev;
|
||||
return Math.max(eff - 1, 0);
|
||||
});
|
||||
return cur;
|
||||
});
|
||||
return cur;
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (key.downArrow && key.shift) {
|
||||
setTurns((cur) => {
|
||||
if (cur.length <= 1) return cur;
|
||||
setViewTurnIdx((prev) => {
|
||||
if (prev === -1) return -1;
|
||||
const next = prev + 1;
|
||||
return next >= cur.length ? -1 : next;
|
||||
return;
|
||||
}
|
||||
if (key.downArrow && key.shift) {
|
||||
setTurns((cur) => {
|
||||
if (cur.length <= 1) return cur;
|
||||
setViewTurnIdx((prev) => {
|
||||
if (prev === -1) return -1;
|
||||
const next = prev + 1;
|
||||
return next >= cur.length ? -1 : next;
|
||||
});
|
||||
return cur;
|
||||
});
|
||||
return cur;
|
||||
});
|
||||
return;
|
||||
}
|
||||
}, { isActive: !needsOnboarding && !overlay });
|
||||
return;
|
||||
}
|
||||
},
|
||||
{ isActive: !needsOnboarding && !overlay },
|
||||
);
|
||||
|
||||
const PAD_X = 2;
|
||||
const PAD_Y = 1;
|
||||
|
|
@ -891,7 +1027,8 @@ function App({
|
|||
const currentTurn = turns[effectiveTurnIdx];
|
||||
const isViewingHistory = viewTurnIdx !== -1 && viewTurnIdx < turns.length - 1;
|
||||
const isLatest = !isViewingHistory;
|
||||
const showInputBar = !pendingPermission && !initialPrompt && !isViewingHistory;
|
||||
const showInputBar =
|
||||
!pendingPermission && !initialPrompt && !isViewingHistory;
|
||||
|
||||
const headerH = 2;
|
||||
const isPasteMode = pastedFull !== null;
|
||||
|
|
@ -924,11 +1061,7 @@ function App({
|
|||
|
||||
if (needsOnboarding && clientRef.current) {
|
||||
return (
|
||||
<Box
|
||||
flexDirection="column"
|
||||
width={safeTermWidth}
|
||||
height={safeTermHeight}
|
||||
>
|
||||
<Box flexDirection="column" width={safeTermWidth} height={safeTermHeight}>
|
||||
<Onboarding
|
||||
client={clientRef.current}
|
||||
width={safeTermWidth}
|
||||
|
|
@ -943,13 +1076,20 @@ function App({
|
|||
if (overlay.screen === "configure") {
|
||||
const intent = overlay.intent;
|
||||
return (
|
||||
<Box flexDirection="column" width={safeTermWidth} height={safeTermHeight}>
|
||||
<Box
|
||||
flexDirection="column"
|
||||
width={safeTermWidth}
|
||||
height={safeTermHeight}
|
||||
>
|
||||
<ConfigureScreen
|
||||
client={clientRef.current}
|
||||
sessionId={sessionIdRef.current}
|
||||
width={safeTermWidth}
|
||||
height={safeTermHeight}
|
||||
onComplete={() => { setOverlay(null); setStatus("ready"); }}
|
||||
onComplete={() => {
|
||||
setOverlay(null);
|
||||
setStatus("ready");
|
||||
}}
|
||||
onCancel={() => setOverlay(null)}
|
||||
initialIntent={intent}
|
||||
/>
|
||||
|
|
@ -957,7 +1097,11 @@ function App({
|
|||
);
|
||||
} else if (overlay.screen === "extensions") {
|
||||
return (
|
||||
<Box flexDirection="column" width={safeTermWidth} height={safeTermHeight}>
|
||||
<Box
|
||||
flexDirection="column"
|
||||
width={safeTermWidth}
|
||||
height={safeTermHeight}
|
||||
>
|
||||
<ExtensionsManager
|
||||
client={clientRef.current}
|
||||
sessionId={sessionIdRef.current}
|
||||
|
|
@ -1057,8 +1201,6 @@ const cli = meow(
|
|||
},
|
||||
);
|
||||
|
||||
|
||||
|
||||
let serverProcess: ReturnType<typeof spawn> | null = null;
|
||||
|
||||
async function runTextMode(serverConnection: Stream | string, prompt: string) {
|
||||
|
|
@ -1077,7 +1219,9 @@ async function runTextMode(serverConnection: Stream | string, prompt: string) {
|
|||
params: RequestPermissionRequest,
|
||||
): Promise<RequestPermissionResponse> => {
|
||||
// Auto-reject in text mode
|
||||
const rejectOption = params.options.find(o => o.kind === "reject_once");
|
||||
const rejectOption = params.options.find(
|
||||
(o) => o.kind === "reject_once",
|
||||
);
|
||||
if (rejectOption) {
|
||||
return {
|
||||
outcome: { outcome: "selected", optionId: rejectOption.optionId },
|
||||
|
|
@ -1119,29 +1263,7 @@ async function main() {
|
|||
if (cli.flags.server) {
|
||||
serverConnection = cli.flags.server;
|
||||
} else {
|
||||
const binary = (() => {
|
||||
const __dirname = dirname(fileURLToPath(import.meta.url));
|
||||
const candidates = [
|
||||
join(__dirname, "..", "server-binary.json"),
|
||||
join(__dirname, "server-binary.json"),
|
||||
];
|
||||
for (const candidate of candidates) {
|
||||
try {
|
||||
const data = JSON.parse(readFileSync(candidate, "utf-8"));
|
||||
return data.binaryPath ?? null;
|
||||
} catch {
|
||||
// not found here, try next
|
||||
}
|
||||
}
|
||||
return null;
|
||||
})();
|
||||
if (!binary) {
|
||||
console.error(
|
||||
"No goose binary found. Use --server <url> or install the native package.",
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const binary = resolveGooseBinary();
|
||||
serverProcess = spawn(binary, ["acp"], {
|
||||
stdio: ["pipe", "pipe", "ignore"],
|
||||
detached: false,
|
||||
|
|
@ -1152,8 +1274,12 @@ async function main() {
|
|||
process.exit(1);
|
||||
});
|
||||
|
||||
const output = Writable.toWeb(serverProcess.stdin!) as WritableStream<Uint8Array>;
|
||||
const input = Readable.toWeb(serverProcess.stdout!) as ReadableStream<Uint8Array>;
|
||||
const output = Writable.toWeb(
|
||||
serverProcess.stdin!,
|
||||
) as WritableStream<Uint8Array>;
|
||||
const input = Readable.toWeb(
|
||||
serverProcess.stdout!,
|
||||
) as ReadableStream<Uint8Array>;
|
||||
serverConnection = ndJsonStream(output, input);
|
||||
}
|
||||
|
||||
|
|
@ -1180,8 +1306,14 @@ function cleanup() {
|
|||
}
|
||||
|
||||
process.on("exit", cleanup);
|
||||
process.on("SIGINT", () => { cleanup(); process.exit(0); });
|
||||
process.on("SIGTERM", () => { cleanup(); process.exit(0); });
|
||||
process.on("SIGINT", () => {
|
||||
cleanup();
|
||||
process.exit(0);
|
||||
});
|
||||
process.on("SIGTERM", () => {
|
||||
cleanup();
|
||||
process.exit(0);
|
||||
});
|
||||
|
||||
main().catch((err) => {
|
||||
console.error(err);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue