Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -248,6 +248,21 @@ jobs:

sudo dpkg --add-architecture arm64
sudo apt-get update
# libgraphite2-3 / libharfbuzz0b / libfreetype6 / libssl3 are
# Multi-Arch: same — their :arm64 and :amd64 copies must be the
# EXACT same version. The runner ships stale :amd64 copies, and a
# jammy security bump (e.g. graphite2 1.3.14-1ubuntu0.1) made the
# :arm64 webkit dev chain demand a newer version than the preinstalled
# :amd64. apt then refuses the :arm64 copy ("not going to be
# installed" / held broken packages). Upgrading the preinstalled
# :amd64 copies first lets both arches land on the same version.
# --only-upgrade never installs new packages, so any lib that is
# absent is simply skipped.
sudo apt-get install -y --only-upgrade \
libgraphite2-3 \
libharfbuzz0b \
libfreetype6 \
libssl3
sudo apt-get install -y \
gcc-aarch64-linux-gnu \
g++-aarch64-linux-gnu \
Expand Down Expand Up @@ -455,6 +470,11 @@ jobs:

sudo dpkg --add-architecture arm64
sudo apt-get update
# libssl3 is Multi-Arch: same; align the preinstalled :amd64 copy
# with the version libssl-dev:arm64 pins so the cross install isn't
# blocked by a held-back amd64 copy (same failure mode as the desktop
# arm64 build — see the build-tauri step for the full rationale).
sudo apt-get install -y --only-upgrade libssl3
sudo apt-get install -y \
gcc-aarch64-linux-gnu \
g++-aarch64-linux-gnu \
Expand Down
118 changes: 94 additions & 24 deletions install.sh
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ set -euo pipefail

REPO="xintaofei/codeg"
INSTALL_DIR="${CODEG_INSTALL_DIR:-/usr/local/bin}"
WEB_DIR="${CODEG_WEB_DIR:-/usr/local/share/codeg/web}"
VERSION=""
# Stale codeg-server / codeg-mcp binaries elsewhere in PATH are removed by
# default so the user's `codeg-server` command always runs the freshly
Expand Down Expand Up @@ -117,6 +118,66 @@ read_bin_version() {
rm -f "$tmp"
}

# ── Privilege model ──
#
# Root can write anywhere and must NEVER call `sudo`: minimal root environments
# (containers, slim images) frequently don't ship sudo, and a blind `sudo mkdir`
# there aborts the whole script under `set -e` AFTER the binaries already landed
# — leaving a half-installed tree the version short-circuit then refuses to
# repair. A non-root user needs sudo only when the destination's nearest
# existing ancestor isn't writable.

PRIV=""
IS_ROOT=0
# Conservative default: if `id -u` somehow fails, assume NON-root (echo 1) so we
# fall back to writability-probing + sudo rather than wrongly skipping elevation.
# This is still correct for a real root whose `id` broke: `[ -w ]` on existing
# system dirs is true for root, so resolve_priv runs directly anyway.
if [ "$(id -u 2>/dev/null || echo 1)" = "0" ]; then
IS_ROOT=1
fi

HAVE_SUDO=0
if command -v sudo >/dev/null 2>&1; then
HAVE_SUDO=1
fi

# Walk up from $1 to the first ancestor that already exists, so writability can
# be tested for a not-yet-created path (e.g. /usr/local/share/codeg/web, whose
# parent /usr/local/share/codeg also doesn't exist on a fresh install).
nearest_existing_ancestor() {
local p="$1"
while [ -n "$p" ] && [ "$p" != "/" ] && [ ! -e "$p" ]; do
p="$(dirname "$p")"
done
echo "$p"
}

# Decide how to create/write into directory $1. Sets global PRIV to "" (run
# directly) or "sudo". Returns non-zero — without aborting under `set -e`, since
# callers invoke it via `if` — when elevation is required but sudo is absent.
resolve_priv() {
PRIV=""
[ "$IS_ROOT" -eq 1 ] && return 0
local anchor
anchor="$(nearest_existing_ancestor "$1")"
[ -w "$anchor" ] && return 0
if [ "$HAVE_SUDO" -eq 1 ]; then
PRIV="sudo"
return 0
fi
return 1
}

# Run "$@", elevating with sudo only when the last resolve_priv call decided so.
priv_run() {
if [ -n "$PRIV" ]; then
sudo "$@"
else
"$@"
fi
}

# ── Scan PATH for codeg-server binaries that shadow the target install ──
#
# A binary "shadows" the install only if it appears in PATH BEFORE the
Expand Down Expand Up @@ -188,16 +249,21 @@ fi
TARGET_VER="${VERSION#v}"

# Only short-circuit when the active binary is up to date AND the destination
# itself has it AND no other PATH entries shadow it. Otherwise we still need to
# install / clean up so the user's `codeg-server` command runs the new version.
# has it AND no other PATH entries shadow it AND the web assets are present.
# The web-asset check makes the installer self-healing: a prior run that placed
# the binary but failed before copying web/ (the classic root-without-sudo
# case) is repaired on re-run instead of exiting "nothing to do" forever.
if [ -n "$CURRENT_VERSION" ] && [ "$CURRENT_VERSION" = "$TARGET_VER" ] \
&& [ "${#PATH_CONFLICTS[@]}" -eq 0 ] \
&& [ -x "$DEST_BIN" ]; then
echo "codeg-server is already at version ${TARGET_VER}, nothing to do."
&& [ -x "$DEST_BIN" ] \
&& [ -f "${WEB_DIR}/index.html" ]; then
echo "codeg-server is already at version ${TARGET_VER} with web assets in place, nothing to do."
exit 0
fi

if [ -n "$CURRENT_VERSION" ]; then
if [ -n "$CURRENT_VERSION" ] && [ "$CURRENT_VERSION" = "$TARGET_VER" ]; then
echo "codeg-server is already at ${TARGET_VER}; reinstalling to repair the existing install..."
elif [ -n "$CURRENT_VERSION" ]; then
echo "Upgrading codeg-server: ${CURRENT_VERSION} -> ${TARGET_VER}..."
else
echo "Installing codeg-server ${VERSION} (${PLATFORM}/${ARCH_SUFFIX})..."
Expand Down Expand Up @@ -303,23 +369,28 @@ for _name in "${MANAGED_BINS[@]}"; do
fi
done

mkdir -p "$INSTALL_DIR"
# Resolve how to write into INSTALL_DIR, then create it and drop the binaries.
# Root writes directly; a non-root user uses sudo only when the prefix isn't
# already writable. Bail out clearly if elevation is needed but sudo is absent,
# instead of crashing mid-install under `set -e`.
if ! resolve_priv "$INSTALL_DIR"; then
echo "Error: need elevated privileges to install to ${INSTALL_DIR}, but 'sudo' is not installed."
echo " Re-run as root, install sudo, or set CODEG_INSTALL_DIR/CODEG_WEB_DIR to writable"
echo " paths (e.g. \$HOME/.local/bin and \$HOME/.local/share/codeg/web)."
exit 1
fi
if [ -n "$PRIV" ]; then
echo "Need sudo to install to ${INSTALL_DIR}"
fi

priv_run mkdir -p "$INSTALL_DIR"
_install_one() {
local name="$1"
local src="${TMP_DIR}/${ARTIFACT}/${name}"
local dst="${INSTALL_DIR}/${name}"
if [ -w "$INSTALL_DIR" ]; then
cp "$src" "$dst"
chmod +x "$dst"
else
sudo cp "$src" "$dst"
sudo chmod +x "$dst"
fi
priv_run cp "$src" "$dst"
priv_run chmod +x "$dst"
}

if [ ! -w "$INSTALL_DIR" ]; then
echo "Need sudo to install to ${INSTALL_DIR}"
fi
for _name in "${MANAGED_BINS[@]}"; do
_install_one "$_name"
done
Expand All @@ -332,17 +403,16 @@ DEST_BIN_REAL="$(canon_path "$DEST_BIN")"
# ── Install web assets ──

WEB_SRC="${TMP_DIR}/${ARTIFACT}/web"
WEB_DIR="${CODEG_WEB_DIR:-/usr/local/share/codeg/web}"

if [ -d "$WEB_SRC" ]; then
echo "Installing web assets to ${WEB_DIR}..."
if [ -w "$(dirname "$WEB_DIR")" ] 2>/dev/null; then
mkdir -p "$WEB_DIR"
cp -r "$WEB_SRC"/* "$WEB_DIR"/
else
sudo mkdir -p "$WEB_DIR"
sudo cp -r "$WEB_SRC"/* "$WEB_DIR"/
if ! resolve_priv "$WEB_DIR"; then
echo "Error: need elevated privileges to write ${WEB_DIR}, but 'sudo' is not installed."
echo " Re-run as root, install sudo, or set CODEG_WEB_DIR to a writable path."
exit 1
fi
priv_run mkdir -p "$WEB_DIR"
priv_run cp -r "$WEB_SRC"/* "$WEB_DIR"/
fi

# ── Remove shadowing binaries from earlier PATH entries ──
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "codeg",
"private": true,
"version": "0.15.12",
"version": "0.15.13",
"scripts": {
"dev": "next dev --turbopack",
"build": "next build",
Expand Down
2 changes: 1 addition & 1 deletion src-tauri/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion src-tauri/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "codeg"
version = "0.15.12"
version = "0.15.13"
description = "Agent Code Generation App"
authors = ["feitao"]
edition = "2021"
Expand Down
15 changes: 5 additions & 10 deletions src-tauri/experts/skills/brainstorming/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ Every project goes through this process. A todo list, a single-function utility,
You MUST create a task for each of these items and complete them in order:

1. **Explore project context** — check files, docs, recent commits
2. **Offer visual companion** (if topic will involve visual questions) — this is its own message, not combined with a clarifying question. See the Visual Companion section below.
2. **Offer the visual companion just-in-time** — NOT upfront. The first time a question would genuinely be clearer shown than described, offer it then (its own message); on approval its browser tab opens for you. If no visual question ever arises, never offer it. See the Visual Companion section below.
3. **Ask clarifying questions** — one at a time, understand purpose/constraints/success criteria
4. **Propose 2-3 approaches** — with trade-offs and your recommendation
5. **Present design** — in sections scaled to their complexity, get user approval after each section
Expand All @@ -36,8 +36,6 @@ You MUST create a task for each of these items and complete them in order:
```dot
digraph brainstorming {
"Explore project context" [shape=box];
"Visual questions ahead?" [shape=diamond];
"Offer Visual Companion\n(own message, no other content)" [shape=box];
"Ask clarifying questions" [shape=box];
"Propose 2-3 approaches" [shape=box];
"Present design sections" [shape=box];
Expand All @@ -47,10 +45,7 @@ digraph brainstorming {
"User reviews spec?" [shape=diamond];
"Invoke writing-plans skill" [shape=doublecircle];

"Explore project context" -> "Visual questions ahead?";
"Visual questions ahead?" -> "Offer Visual Companion\n(own message, no other content)" [label="yes"];
"Visual questions ahead?" -> "Ask clarifying questions" [label="no"];
"Offer Visual Companion\n(own message, no other content)" -> "Ask clarifying questions";
"Explore project context" -> "Ask clarifying questions";
"Ask clarifying questions" -> "Propose 2-3 approaches";
"Propose 2-3 approaches" -> "Present design sections";
"Present design sections" -> "User approves design?";
Expand Down Expand Up @@ -148,10 +143,10 @@ Wait for the user's response. If they request changes, make them and re-run the

A browser-based companion for showing mockups, diagrams, and visual options during brainstorming. Available as a tool — not a mode. Accepting the companion means it's available for questions that benefit from visual treatment; it does NOT mean every question goes through the browser.

**Offering the companion:** When you anticipate that upcoming questions will involve visual content (mockups, layouts, diagrams), offer it once for consent:
> "Some of what we're working on might be easier to explain if I can show it to you in a web browser. I can put together mockups, diagrams, comparisons, and other visuals as we go. This feature is still new and can be token-intensive. Want to try it? (Requires opening a local URL)"
**Offering the companion (just-in-time):** Do NOT offer it upfront. Wait until a question would genuinely be clearer shown than told — a real mockup / layout / diagram question, not merely a UI *topic*. The first time that happens, offer it then, as its own message:
> "This next part might be easier if I show you I can put together mockups, diagrams, and comparisons in a browser tab as we go. It's still new and can be token-intensive. Want me to? I'll open it for you."

**This offer MUST be its own message.** Do not combine it with clarifying questions, context summaries, or any other content. The message should contain ONLY the offer above and nothing else. Wait for the user's response before continuing. If they decline, proceed with text-only brainstorming.
**This offer MUST be its own message.** Only the offer — no clarifying question, summary, or other content. Wait for the user's response. If they accept, start the server with `--open` so their browser opens to the first screen automatically. If they decline, continue text-only and don't offer again unless they raise it.

**Per-question decision:** Even after the user accepts, decide FOR EACH QUESTION whether to use the browser or the terminal. The test: **would the user understand this better by seeing it than reading it?**

Expand Down
51 changes: 25 additions & 26 deletions src-tauri/experts/skills/brainstorming/scripts/frame-template.html
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,11 @@
*
* This template provides a consistent frame with:
* - OS-aware light/dark theming
* - Fixed header and selection indicator bar
* - Header branding and connection status
* - Scrollable main content area
* - CSS helpers for common UI patterns
*
* Content is injected via placeholder comment in #claude-content.
* Content is injected via placeholder comment in #frame-content.
*/

* { box-sizing: border-box; margin: 0; padding: 0; }
Expand Down Expand Up @@ -63,34 +63,37 @@
}

/* ===== FRAME STRUCTURE ===== */
.header {
background: var(--bg-secondary);
padding: 0.5rem 1.5rem;
display: flex;
justify-content: space-between;
align-items: center;
border-bottom: 1px solid var(--border);
flex-shrink: 0;
.brand { display: flex; align-items: center; min-width: 0; overflow: hidden; color: var(--text-secondary); line-height: 1; }
.brand a { color: inherit; text-decoration: none; display: flex; align-items: center; gap: 0.5rem; min-width: 0; max-width: 100%; line-height: 1; }
.brand-copy { display: block; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; line-height: 1; transform: translateY(-1px); }
.brand-logo { display: block; height: 1em; width: auto; max-width: 180px; flex-shrink: 0; filter: invert(1); }
@media (prefers-color-scheme: dark) {
.brand-logo { filter: none; }
}
.header h1 { font-size: 0.85rem; font-weight: 500; color: var(--text-secondary); }
.header .status { font-size: 0.7rem; color: var(--success); display: flex; align-items: center; gap: 0.4rem; }
.header .status::before { content: ''; width: 6px; height: 6px; background: var(--success); border-radius: 50%; }
.status { font-size: 0.7rem; color: var(--status-color, var(--success)); display: flex; align-items: center; gap: 0.4rem; justify-self: end; white-space: nowrap; line-height: 1; }
.status::before { content: ''; width: 6px; height: 6px; background: var(--status-color, var(--success)); border-radius: 50%; }

.main { flex: 1; overflow-y: auto; }
#claude-content { padding: 2rem; min-height: 100%; }
#frame-content { padding: 2rem; min-height: 100%; }

.indicator-bar {
.header {
background: var(--bg-secondary);
border-top: 1px solid var(--border);
border-bottom: 1px solid var(--border);
padding: 0.5rem 1.5rem;
flex-shrink: 0;
text-align: center;
display: grid;
grid-template-columns: minmax(0, 1fr) auto;
align-items: center;
gap: 1rem;
min-height: 42px;
}
.indicator-bar span {
.header .brand { justify-self: start; width: 100%; font-size: 0.75rem; line-height: 1; }
.header .status { grid-column: 2; line-height: 1; }
.header span {
font-size: 0.75rem;
color: var(--text-secondary);
}
.indicator-bar .selected-text {
.header .selected-text {
color: var(--accent);
font-weight: 500;
}
Expand Down Expand Up @@ -196,19 +199,15 @@
</head>
<body>
<div class="header">
<h1><a href="https://github.com/obra/superpowers" style="color: inherit; text-decoration: none;">Superpowers Brainstorming</a></h1>
<div class="status">Connected</div>
<!-- BRANDING -->
<div class="status">Connecting…</div>
</div>

<div class="main">
<div id="claude-content">
<div id="frame-content">
<!-- CONTENT -->
</div>
</div>

<div class="indicator-bar">
<span id="indicator-text">Click an option above, then return to the terminal</span>
</div>

</body>
</html>
Loading
Loading