Big News: Socket raises $60M Series C at a $1B valuation to secure software supply chains for AI-driven development.Announcement
Sign In

@devlln/helm

Package Overview
Dependencies
Maintainers
1
Versions
12
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@devlln/helm - npm Package Compare versions

Comparing version
0.1.10
to
0.2.0
+103
bridge/src/bridgeAutoUpdater.test.ts
import test from "node:test";
import assert from "node:assert/strict";
import {
buildBridgeUpdateCommand,
checkForBridgeUpdate,
compareSemver,
detectBridgeInstallMethod,
shouldEnableBridgeAutoUpdate,
} from "./bridgeAutoUpdater.js";
test("compareSemver compares numeric version segments", () => {
assert.equal(compareSemver("0.1.10", "0.2.0"), -1);
assert.equal(compareSemver("0.2.0", "0.1.10"), 1);
assert.equal(compareSemver("1.0.0", "1.0.0"), 0);
assert.equal(compareSemver("1.0.0-beta.1", "1.0.0"), 0);
});
test("detectBridgeInstallMethod prefers Homebrew cellar installs and marks git checkouts as git", () => {
assert.equal(
detectBridgeInstallMethod({
rootDir: "/opt/homebrew/Cellar/helm/0.2.0/libexec",
packageName: "@devlln/helm",
hasGitDir: false,
}),
"homebrew"
);
assert.equal(
detectBridgeInstallMethod({
rootDir: "/Users/devlin/GitHub/helm-dev",
packageName: "@devlln/helm",
hasGitDir: true,
}),
"git"
);
});
test("shouldEnableBridgeAutoUpdate skips local checkouts unless forced", () => {
assert.equal(
shouldEnableBridgeAutoUpdate({
env: {},
installMethod: "git",
packageName: "@devlln/helm",
}),
false
);
assert.equal(
shouldEnableBridgeAutoUpdate({
env: { HELM_BRIDGE_AUTO_UPDATE: "1" },
installMethod: "git",
packageName: "@devlln/helm",
}),
true
);
assert.equal(
shouldEnableBridgeAutoUpdate({
env: {},
installMethod: "npm",
packageName: "@devlln/helm",
}),
true
);
});
test("buildBridgeUpdateCommand runs the packaged update script with the detected method", () => {
assert.deepEqual(
buildBridgeUpdateCommand({
rootDir: "/opt/homebrew/Cellar/helm/0.2.0/libexec",
installMethod: "homebrew",
}),
{
command: "/opt/homebrew/Cellar/helm/0.2.0/libexec/scripts/helm-update.sh",
args: ["--yes", "--source", "bridge-auto", "--method", "homebrew"],
}
);
});
test("checkForBridgeUpdate starts the updater when registry version is newer", async () => {
const updates: Array<{ command: string; args: string[] }> = [];
const result = await checkForBridgeUpdate({
rootDir: "/usr/local/lib/node_modules/@devlln/helm",
packageInfo: { name: "@devlln/helm", version: "0.1.10" },
hasGitDir: false,
env: {},
fetchLatestVersion: async () => "0.2.0",
runUpdate: (command, args) => {
updates.push({ command, args });
},
scriptExists: () => true,
});
assert.equal(result.status, "started");
assert.deepEqual(updates, [
{
command: "/usr/local/lib/node_modules/@devlln/helm/scripts/helm-update.sh",
args: ["--yes", "--source", "bridge-auto", "--method", "npm"],
},
]);
});
import { spawn } from "node:child_process";
import { existsSync, readFileSync } from "node:fs";
import { join } from "node:path";
export type BridgeInstallMethod = "npm" | "homebrew" | "git" | "unknown";
export interface BridgePackageInfo {
name: string;
version: string;
}
export interface BridgeUpdateCommand {
command: string;
args: string[];
}
export interface BridgeUpdateCheckResult {
status: "disabled" | "current" | "started" | "skipped";
reason?: string;
currentVersion?: string;
latestVersion?: string;
installMethod?: BridgeInstallMethod;
}
export interface BridgeUpdateCheckOptions {
rootDir: string;
env?: NodeJS.ProcessEnv | Record<string, string | undefined>;
packageInfo?: BridgePackageInfo | null;
hasGitDir?: boolean;
installMethod?: BridgeInstallMethod;
fetchLatestVersion?: (packageName: string) => Promise<string | null>;
runUpdate?: (command: string, args: string[]) => void;
scriptExists?: (path: string) => boolean;
logger?: Pick<Console, "log" | "warn">;
}
export interface StartBridgeAutoUpdaterOptions extends BridgeUpdateCheckOptions {
setTimeoutFn?: typeof setTimeout;
setIntervalFn?: typeof setInterval;
}
export interface BridgeAutoUpdaterHandle {
stop(): void;
checkNow(): Promise<BridgeUpdateCheckResult>;
}
const PUBLIC_PACKAGE_NAME = "@devlln/helm";
const DEFAULT_INITIAL_DELAY_MS = 30_000;
const DEFAULT_INTERVAL_MS = 6 * 60 * 60 * 1000;
function versionSegments(version: string): number[] {
const core = version.trim().replace(/^v/i, "").split("-", 1)[0] ?? "";
const rawSegments = core.split(".").slice(0, 3);
const segments = rawSegments.map((segment) => {
const value = Number.parseInt(segment, 10);
return Number.isFinite(value) ? value : 0;
});
while (segments.length < 3) {
segments.push(0);
}
return segments;
}
export function compareSemver(lhs: string, rhs: string): number {
const lhsSegments = versionSegments(lhs);
const rhsSegments = versionSegments(rhs);
for (let index = 0; index < 3; index += 1) {
const lhsValue = lhsSegments[index] ?? 0;
const rhsValue = rhsSegments[index] ?? 0;
if (lhsValue < rhsValue) {
return -1;
}
if (lhsValue > rhsValue) {
return 1;
}
}
return 0;
}
export function detectBridgeInstallMethod(input: {
rootDir: string;
packageName?: string | null;
hasGitDir?: boolean;
}): BridgeInstallMethod {
if (/\/Cellar\/helm\/[^/]+\/libexec\/?$/.test(input.rootDir) || input.rootDir.includes("/Cellar/helm/")) {
return "homebrew";
}
if (input.hasGitDir) {
return "git";
}
if (input.packageName === PUBLIC_PACKAGE_NAME || input.packageName === "@devlin/helm") {
return "npm";
}
return "unknown";
}
export function shouldEnableBridgeAutoUpdate(input: {
env: NodeJS.ProcessEnv | Record<string, string | undefined>;
installMethod: BridgeInstallMethod;
packageName?: string | null;
}): boolean {
const override = input.env.HELM_BRIDGE_AUTO_UPDATE?.trim().toLowerCase();
if (override === "0" || override === "false" || override === "off" || override === "no") {
return false;
}
if (override === "1" || override === "true" || override === "on" || override === "yes") {
return true;
}
return input.packageName === PUBLIC_PACKAGE_NAME
&& (input.installMethod === "npm" || input.installMethod === "homebrew");
}
export function buildBridgeUpdateCommand(input: {
rootDir: string;
installMethod: BridgeInstallMethod;
}): BridgeUpdateCommand {
return {
command: join(input.rootDir, "scripts", "helm-update.sh"),
args: ["--yes", "--source", "bridge-auto", "--method", input.installMethod],
};
}
function readPackageInfo(rootDir: string): BridgePackageInfo | null {
try {
const parsed = JSON.parse(readFileSync(join(rootDir, "package.json"), "utf8")) as {
name?: unknown;
version?: unknown;
};
if (typeof parsed.name !== "string" || typeof parsed.version !== "string") {
return null;
}
return { name: parsed.name, version: parsed.version };
} catch {
return null;
}
}
async function fetchNpmLatestVersion(packageName: string): Promise<string | null> {
const encodedName = encodeURIComponent(packageName).replace(/^%40/, "@");
const response = await fetch(`https://registry.npmjs.org/${encodedName}/latest`, {
headers: {
accept: "application/json",
},
});
if (!response.ok) {
return null;
}
const body = await response.json() as { version?: unknown };
return typeof body.version === "string" ? body.version : null;
}
function runDetachedUpdate(command: string, args: string[]): void {
const child = spawn(command, args, {
detached: true,
stdio: "ignore",
env: process.env,
});
child.unref();
}
export async function checkForBridgeUpdate(options: BridgeUpdateCheckOptions): Promise<BridgeUpdateCheckResult> {
const env = options.env ?? process.env;
const packageInfo = options.packageInfo ?? readPackageInfo(options.rootDir);
if (!packageInfo) {
return { status: "disabled", reason: "package-info-unavailable" };
}
const hasGitDir = options.hasGitDir ?? existsSync(join(options.rootDir, ".git"));
const installMethod = options.installMethod ?? detectBridgeInstallMethod({
rootDir: options.rootDir,
packageName: packageInfo.name,
hasGitDir,
});
if (!shouldEnableBridgeAutoUpdate({ env, installMethod, packageName: packageInfo.name })) {
return {
status: "disabled",
reason: "auto-update-disabled",
currentVersion: packageInfo.version,
installMethod,
};
}
const updateCommand = buildBridgeUpdateCommand({ rootDir: options.rootDir, installMethod });
const scriptExists = options.scriptExists ?? existsSync;
if (!scriptExists(updateCommand.command)) {
return {
status: "disabled",
reason: "update-script-missing",
currentVersion: packageInfo.version,
installMethod,
};
}
const fetchLatestVersion = options.fetchLatestVersion ?? fetchNpmLatestVersion;
let latestVersion: string | null = null;
try {
latestVersion = await fetchLatestVersion(PUBLIC_PACKAGE_NAME);
} catch (error) {
options.logger?.warn(`[bridge] Helm auto-update check failed: ${error instanceof Error ? error.message : String(error)}`);
return {
status: "skipped",
reason: "latest-version-fetch-failed",
currentVersion: packageInfo.version,
installMethod,
};
}
if (!latestVersion) {
return {
status: "skipped",
reason: "latest-version-unavailable",
currentVersion: packageInfo.version,
installMethod,
};
}
if (compareSemver(packageInfo.version, latestVersion) >= 0) {
return {
status: "current",
currentVersion: packageInfo.version,
latestVersion,
installMethod,
};
}
const runUpdate = options.runUpdate ?? runDetachedUpdate;
runUpdate(updateCommand.command, updateCommand.args);
options.logger?.log(`[bridge] Helm ${packageInfo.version} is older than ${latestVersion}; started ${installMethod} update.`);
return {
status: "started",
currentVersion: packageInfo.version,
latestVersion,
installMethod,
};
}
function numberFromEnv(
env: NodeJS.ProcessEnv | Record<string, string | undefined>,
key: string,
fallback: number
): number {
const raw = env[key];
if (raw === undefined || raw.trim() === "") {
return fallback;
}
const value = Number.parseInt(raw, 10);
return Number.isFinite(value) && value >= 0 ? value : fallback;
}
export function startBridgeAutoUpdater(options: StartBridgeAutoUpdaterOptions): BridgeAutoUpdaterHandle {
const env = options.env ?? process.env;
let stopped = false;
let running = false;
let timeoutHandle: ReturnType<typeof setTimeout> | null = null;
let intervalHandle: ReturnType<typeof setInterval> | null = null;
const setTimeoutFn = options.setTimeoutFn ?? setTimeout;
const setIntervalFn = options.setIntervalFn ?? setInterval;
const checkNow = async (): Promise<BridgeUpdateCheckResult> => {
if (stopped || running) {
return { status: "skipped", reason: stopped ? "stopped" : "already-running" };
}
running = true;
try {
return await checkForBridgeUpdate(options);
} finally {
running = false;
}
};
const initialDelayMS = numberFromEnv(env, "HELM_BRIDGE_AUTO_UPDATE_INITIAL_DELAY_MS", DEFAULT_INITIAL_DELAY_MS);
const intervalMS = numberFromEnv(env, "HELM_BRIDGE_AUTO_UPDATE_INTERVAL_MS", DEFAULT_INTERVAL_MS);
timeoutHandle = setTimeoutFn(() => {
void checkNow();
}, initialDelayMS);
timeoutHandle.unref?.();
if (intervalMS > 0) {
intervalHandle = setIntervalFn(() => {
void checkNow();
}, intervalMS);
intervalHandle.unref?.();
}
return {
stop() {
stopped = true;
if (timeoutHandle) {
clearTimeout(timeoutHandle);
}
if (intervalHandle) {
clearInterval(intervalHandle);
}
},
checkNow,
};
}
#!/usr/bin/env bash
set -euo pipefail
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
BRIDGE_DIR="$ROOT_DIR/bridge"
PACKAGE_NAME="${HELM_NPM_PACKAGE_NAME:-@devlln/helm}"
HOMEBREW_FORMULA="${HELM_HOMEBREW_FORMULA:-devlln/helm/helm}"
METHOD="auto"
SOURCE="manual"
YES=0
DRY_RUN=0
RESTART=1
usage() {
cat <<'EOF'
Usage: scripts/helm-update.sh [options]
Update the installed Helm bridge package and restart the launchd bridge service.
Options:
--method auto|npm|homebrew|git Select the update mechanism. Default: auto.
--yes Do not prompt before updating.
--dry-run Print the update commands without running them.
--no-restart Update without restarting the bridge service.
--source NAME Label the caller in logs. Default: manual.
-h, --help Show this help.
EOF
}
while [[ $# -gt 0 ]]; do
case "$1" in
--method)
METHOD="${2:-}"
shift 2
;;
--yes|-y)
YES=1
shift
;;
--dry-run)
DRY_RUN=1
shift
;;
--no-restart)
RESTART=0
shift
;;
--source)
SOURCE="${2:-}"
shift 2
;;
-h|--help)
usage
exit 0
;;
*)
echo "Unsupported argument: $1" >&2
usage >&2
exit 2
;;
esac
done
require_cmd() {
if [[ "$DRY_RUN" -eq 1 ]]; then
return
fi
if ! command -v "$1" >/dev/null 2>&1; then
echo "Missing required command: $1" >&2
exit 1
fi
}
run_cmd() {
if [[ "$DRY_RUN" -eq 1 ]]; then
printf '[helm-update] would run:'
printf ' %q' "$@"
printf '\n'
return 0
fi
"$@"
}
detect_method() {
if [[ "$ROOT_DIR" == *"/Cellar/helm/"* ]] && command -v brew >/dev/null 2>&1; then
echo "homebrew"
return
fi
if [[ -d "$ROOT_DIR/.git" ]]; then
echo "git"
return
fi
if command -v npm >/dev/null 2>&1; then
echo "npm"
return
fi
echo "unknown"
}
resolve_installed_root() {
local helm_bin
helm_bin="$(command -v helm || true)"
if [[ -z "$helm_bin" ]]; then
printf '%s\n' "$ROOT_DIR"
return
fi
python3 - "$helm_bin" "$ROOT_DIR" <<'PY'
import os
import sys
helm_bin, fallback = sys.argv[1:]
try:
real = os.path.realpath(helm_bin)
root = os.path.abspath(os.path.join(os.path.dirname(real), ".."))
if os.path.exists(os.path.join(root, "scripts", "bridge-service.sh")):
print(root)
raise SystemExit(0)
except OSError:
pass
print(fallback)
PY
}
restart_bridge_service() {
if [[ "$RESTART" -eq 0 ]]; then
return
fi
local installed_root
installed_root="$(resolve_installed_root)"
local service_script="$installed_root/scripts/bridge-service.sh"
if [[ ! -f "$service_script" ]]; then
echo "[helm-update] Bridge service script not found; skipping service restart." >&2
return
fi
if [[ "$DRY_RUN" -eq 1 ]]; then
run_cmd "$service_script" restart
return
fi
if "$service_script" restart; then
return
fi
echo "[helm-update] Bridge service restart failed. Run 'helm bridge service restart' after the update." >&2
}
METHOD="${METHOD:-auto}"
if [[ "$METHOD" == "auto" ]]; then
METHOD="$(detect_method)"
fi
case "$METHOD" in
npm|homebrew|git)
;;
unknown)
echo "Could not detect how Helm was installed. Reinstall with npm or Homebrew, or pass --method." >&2
exit 1
;;
*)
echo "Unsupported update method: $METHOD" >&2
usage >&2
exit 2
;;
esac
echo "[helm-update] source=${SOURCE} method=${METHOD} root=${ROOT_DIR}"
if [[ "$YES" -eq 0 && "$DRY_RUN" -eq 0 ]]; then
if [[ ! -t 0 ]]; then
echo "Refusing to update without --yes from a non-interactive shell." >&2
exit 1
fi
read -r -p "Update Helm using ${METHOD} and restart the bridge service? [y/N] " reply
case "$reply" in
y|Y|yes|YES)
;;
*)
echo "Update cancelled."
exit 1
;;
esac
fi
case "$METHOD" in
npm)
require_cmd npm
run_cmd npm install -g "${PACKAGE_NAME}@latest"
;;
homebrew)
require_cmd brew
run_cmd brew update
if ! run_cmd brew upgrade "$HOMEBREW_FORMULA"; then
run_cmd brew reinstall "$HOMEBREW_FORMULA"
fi
;;
git)
require_cmd git
require_cmd npm
run_cmd git -C "$ROOT_DIR" pull --ff-only
run_cmd npm --prefix "$BRIDGE_DIR" install
run_cmd npm --prefix "$BRIDGE_DIR" run build
;;
esac
restart_bridge_service
echo "[helm-update] update complete"
+28
-5

@@ -18,2 +18,3 @@ #!/usr/bin/env node

helm platforms [--json]
helm update [--method auto|npm|homebrew|git] [--dry-run]
helm help [bridge]

@@ -25,2 +26,3 @@

platforms Detect the local runtimes, shell integration, Tailscale state, and Mac-app build support that Helm can use.
update Update the installed Helm bridge package and restart the bridge service.

@@ -33,2 +35,3 @@ Compatibility aliases:

helm down -> helm bridge down
helm upgrade -> helm update

@@ -43,2 +46,3 @@ Examples:

helm platforms --json
helm update --dry-run
`);

@@ -55,10 +59,14 @@ }

helm bridge status
helm bridge service <install|uninstall|start|stop|restart|status|print-plist>
helm bridge update [update options]
helm bridge down
Bridge commands:
setup Run the guided Helm bridge setup flow.
up Start the local bridge and Codex app-server helper.
pair Start the bridge if needed, then print the pairing QR and setup link.
status Show bridge health, pairing details, and voice-provider availability.
down Stop the local prototype bridge stack.
setup Run the guided Helm bridge setup flow.
up Start the local bridge and Codex app-server helper.
pair Start the bridge if needed, then print the pairing QR and setup link.
status Show bridge health, pairing details, and voice-provider availability.
service Manage the launchd bridge service.
update Update Helm and restart the bridge service.
down Stop the local prototype bridge stack.
`);

@@ -113,2 +121,6 @@ }

function runUpdate(args) {
runScript("helm-update.sh", args);
}
function runBridge(args) {

@@ -138,2 +150,9 @@ const [subcommand, ...bridgeArgs] = args;

return;
case "service":
runScript("bridge-service.sh", bridgeArgs);
return;
case "update":
case "upgrade":
runUpdate(bridgeArgs);
return;
case "down":

@@ -177,2 +196,6 @@ case "stop":

break;
case "update":
case "upgrade":
runUpdate(rawArgs);
break;
case "up":

@@ -179,0 +202,0 @@ case "start":

+2
-2
{
"name": "codex-voice-remote-bridge",
"version": "0.1.10",
"version": "0.2.0",
"lockfileVersion": 3,

@@ -9,3 +9,3 @@ "requires": true,

"name": "codex-voice-remote-bridge",
"version": "0.1.10",
"version": "0.2.0",
"dependencies": {

@@ -12,0 +12,0 @@ "dotenv": "^16.6.1",

{
"name": "codex-voice-remote-bridge",
"version": "0.1.10",
"version": "0.2.0",
"private": true,

@@ -5,0 +5,0 @@ "type": "module",

@@ -141,2 +141,8 @@ import test from "node:test";

): Promise<JSONValue | undefined>;
ensureAppServerThreadLoadedForDelivery(
threadId: string,
options?: { forceResume?: boolean }
): Promise<void>;
canRefreshCodexDesktopThreadRoute(): boolean;
refreshCodexDesktopThreadRoute(threadId: string, reason?: string): Promise<boolean>;
codexDesktopQueuedFollowUpsWithAppendedMessage(

@@ -360,6 +366,8 @@ currentMessages: TestQueuedFollowUp[],

test("idle Codex desktop turn falls back to app-server when desktop IPC has no loaded client", async () => {
test("idle Codex desktop turn falls back through app-server and refreshes Codex.app when desktop IPC has no loaded client", async () => {
const client = new CodexAppServerClient("ws://127.0.0.1:0");
const hooks = client as unknown as CodexClientPrivateHooks;
const requestCalls: Array<{ method: string; params?: JSONValue }> = [];
const ensureCalls: Array<{ threadId: string; options?: { forceResume?: boolean } }> = [];
const refreshCalls: Array<{ threadId: string; reason?: string }> = [];

@@ -387,5 +395,12 @@ hooks.loadThreadDeliverySummary = async () => ({

};
hooks.ensureAppServerThreadLoadedForDelivery = async (threadId, options) => {
ensureCalls.push({ threadId, options });
};
hooks.canRefreshCodexDesktopThreadRoute = () => true;
hooks.refreshCodexDesktopThreadRoute = async (threadId, reason) => {
refreshCalls.push({ threadId, reason });
return true;
};
hooks.request = async (method: string, params?: JSONValue) => {
requestCalls.push({ method, params });
assert.equal(method, "turn/start");
return {

@@ -400,2 +415,89 @@ ok: true,

assert.deepEqual(result, {
ok: true,
mode: "appServerStartAfterDesktopIpcNoClientWithDesktopRefresh",
threadId: "thread-1",
});
assert.deepEqual(ensureCalls, [
{ threadId: "thread-1", options: { forceResume: true } },
]);
assert.equal(requestCalls.length, 1);
assert.equal(requestCalls[0]?.method, "turn/start");
assert.deepEqual(requestCalls[0]?.params, {
threadId: "thread-1",
input: [
{
type: "text",
text: "from mobile",
text_elements: [],
},
],
});
assert.deepEqual(refreshCalls, [
{ threadId: "thread-1", reason: "desktop-ipc-no-client" },
]);
});
test("idle app-server thread loads and retries text start when direct start reports thread not loaded", async () => {
const client = new CodexAppServerClient("ws://127.0.0.1:0");
const hooks = client as unknown as CodexClientPrivateHooks;
const requestCalls: Array<{ method: string; params?: JSONValue }> = [];
const ensureCalls: Array<{ threadId: string; options?: { forceResume?: boolean } }> = [];
let rejectedFirstTextStart = false;
hooks.loadThreadDeliverySummary = async () => ({
sourceKind: "appServer",
status: "idle",
});
hooks.readThreadDeliverySnapshot = async () => ({
hasTurnData: true,
turnCount: 0,
matchingUserTextCount: 0,
updatedAt: 123_000,
threadStatus: "idle",
activeTurnId: null,
});
hooks.steerTurnViaCodexDesktopIpc = async () => {
throw new Error("Codex Desktop IPC thread-follower-steer-turn failed: no-client-found");
};
hooks.startTurnViaCodexDesktopIpc = async () => {
throw new Error("Codex Desktop IPC thread-follower-start-turn failed: no-client-found");
};
hooks.shouldStartViaAppServer = async () => true;
hooks.shouldPreferCLIResumeFallback = async () => false;
hooks.shouldPreferShellRelayFirst = async () => false;
hooks.ensureAppServerThreadLoadedForDelivery = async (threadId, options) => {
ensureCalls.push({ threadId, options });
};
hooks.request = async (method: string, params?: JSONValue) => {
requestCalls.push({ method, params });
if (
!rejectedFirstTextStart
&& requestCalls.length === 1
&&
method === "turn/start"
&& params
&& typeof params === "object"
&& !Array.isArray(params)
&& Array.isArray(params.input)
&& params.input.length > 0
) {
rejectedFirstTextStart = true;
throw new Error("thread not loaded: thread-1");
}
return {
ok: true,
};
};
const result = await client.startTurn("thread-1", "from mobile", {
deliveryMode: "steer",
});
assert.deepEqual(ensureCalls, [
{
threadId: "thread-1",
options: { forceResume: true },
},
]);
assert.deepEqual(requestCalls, [

@@ -415,6 +517,19 @@ {

},
{
method: "turn/start",
params: {
threadId: "thread-1",
input: [
{
type: "text",
text: "from mobile",
text_elements: [],
},
],
},
},
]);
assert.deepEqual(result, {
ok: true,
mode: "appServerStartAfterDesktopIpcNoClient",
mode: "appServerStartAfterThreadLoadRetry",
threadId: "thread-1",

@@ -424,2 +539,57 @@ });

test("missing delivery summary still retries text start for unloaded app-server threads", async () => {
const client = new CodexAppServerClient("ws://127.0.0.1:0");
const hooks = client as unknown as CodexClientPrivateHooks;
const requestCalls: Array<{ method: string; params?: JSONValue }> = [];
const ensureCalls: Array<{ threadId: string; options?: { forceResume?: boolean } }> = [];
hooks.loadThreadDeliverySummary = async () => null;
hooks.readThreadDeliverySnapshot = async () => null;
hooks.shouldStartViaAppServer = async () => true;
hooks.shouldPreferCLIResumeFallback = async () => false;
hooks.shouldPreferShellRelayFirst = async () => false;
hooks.ensureAppServerThreadLoadedForDelivery = async (threadId, options) => {
ensureCalls.push({ threadId, options });
};
hooks.request = async (method: string, params?: JSONValue) => {
requestCalls.push({ method, params });
if (method === "turn/start" && requestCalls.length === 1) {
throw new Error("thread not loaded: thread-1");
}
return {
ok: true,
};
};
const result = await client.startTurn("thread-1", "from mobile", {
deliveryMode: "steer",
});
assert.deepEqual(ensureCalls, [
{
threadId: "thread-1",
options: { forceResume: true },
},
]);
assert.deepEqual(requestCalls.map((call) => call.method), [
"turn/start",
"turn/start",
]);
assert.deepEqual(requestCalls[1]?.params, {
threadId: "thread-1",
input: [
{
type: "text",
text: "from mobile",
text_elements: [],
},
],
});
assert.deepEqual(result, {
ok: true,
mode: "appServerStartAfterThreadLoadRetry",
threadId: "thread-1",
});
});
test("running Codex desktop steer falls back to app-server steer when desktop IPC has no loaded client", async () => {

@@ -426,0 +596,0 @@ const client = new CodexAppServerClient("ws://127.0.0.1:0");

@@ -0,6 +1,12 @@

import { dirname, resolve } from "node:path";
import { fileURLToPath } from "node:url";
import { startBridgeAutoUpdater } from "./bridgeAutoUpdater.js";
import { BridgeServer } from "./bridgeServer.js";
async function main(): Promise<void> {
const rootDir = resolve(dirname(fileURLToPath(import.meta.url)), "..", "..");
const server = new BridgeServer();
await server.start();
startBridgeAutoUpdater({ rootDir, logger: console });
console.log("[bridge] listening");

@@ -15,2 +21,1 @@ }

});
{
"name": "@devlln/helm",
"version": "0.1.10",
"version": "0.2.0",
"private": false,

@@ -39,2 +39,3 @@ "description": "Helm CLI bridge installer and runtime helpers.",

"scripts/helm-runtime-wrapper.sh",
"scripts/helm-update.sh",
"scripts/helm_bootstrap_codex_thread.py",

@@ -41,0 +42,0 @@ "scripts/helm_codex_wrapper_plan.py",

@@ -52,2 +52,16 @@ <p align="center">

## Updates
Published npm and Homebrew installs check for bridge updates automatically while the launchd bridge service is running. Local git checkouts do not auto-update unless `HELM_BRIDGE_AUTO_UPDATE=1` is set.
Manual update commands:
```bash
helm update
helm update --dry-run
helm update --method homebrew
helm update --method npm
helm update --method git
```
Helm can detect:

@@ -54,0 +68,0 @@

@@ -40,2 +40,3 @@ #!/usr/bin/env bash

link_script "$ROOT_DIR/scripts/bridge-service.sh" "helm-bridge-service"
link_script "$ROOT_DIR/scripts/helm-update.sh" "helm-update"
link_script "$ROOT_DIR/scripts/print-pairing-qr.sh" "helm-pairing-qr"

@@ -75,2 +76,3 @@ link_script "$ROOT_DIR/scripts/detect-helm-platforms.sh" "helm-platforms"

helm-bridge-service
helm-update
helm-pairing-qr

@@ -104,5 +106,7 @@ helm-platforms

helm-pairing-qr
6. In helm on iPhone, scan the pairing QR.
7. Start Codex CLI, Claude Code, or Grok CLI sessions normally.
8. If Ollama is installed, start local model sessions with:
6. Update the installed bridge manually if needed:
helm update
7. In helm on iPhone, scan the pairing QR.
8. Start Codex CLI, Claude Code, or Grok CLI sessions normally.
9. If Ollama is installed, start local model sessions with:
helm-gemma

@@ -109,0 +113,0 @@ helm-qwen

Sorry, the diff of this file is too big to display