Skip to content
Open
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
4 changes: 4 additions & 0 deletions .codex/skills/qa-night-shift/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,10 @@ Typical flow:
12. after `resolve -> continue` on a setup blocker, confirm `status`,
`report`, and Dash still show the retry-armed state and keep confidence
below `high` until the next `night-shift start` consumes the waiver
13. when the user specifically wants the browser surface, use
`night-shift dash`, then drive `start`, `resume`, and `resolve` from the
browser-backed HTTP/UI flow instead of the removed `dash --start`,
`dash --resume`, `start --ui`, or `resume --ui` entrypoints

For review-driven investigations, replace steps 3-4 with:

Expand Down
5 changes: 4 additions & 1 deletion .codex/skills/update-local-night-shift-cli/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,13 @@ run the code from a specific worktree.

## What this skill does

- Builds the selected worktree with `gleam build`.
- Builds the selected worktree with `gleam build` and `scripts/build_dash_assets.sh`.
- Creates a versioned install bundle under `~/.local/share/night-shift/<label>`.
- Copies the compiled Erlang package directories from `build/dev/erlang/`.
- Copies the built dashboard assets from `build/dash-assets/`.
- Writes stable `entrypoint.sh` and `entrypoint.ps1` launchers into that bundle.
- Exports `NIGHT_SHIFT_DASH_ASSET_ROOT` from the launcher so `night-shift dash`
works from the installed bundle.
- Repoints `~/.local/share/night-shift/current` to the new bundle.
- Ensures `~/.local/bin/night-shift` exists and launches `current/entrypoint.sh run`.
- Smoke-tests the install through the normal `night-shift` launcher.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ fi

SOURCE_DIR="$(cd "$SOURCE_DIR" && pwd)"
BUILD_DIR="$SOURCE_DIR/build/dev/erlang"
DASH_ASSET_DIR="$SOURCE_DIR/build/dash-assets"

if [ ! -f "$SOURCE_DIR/gleam.toml" ]; then
echo "Not a Gleam project: $SOURCE_DIR" >&2
Expand All @@ -62,13 +63,19 @@ fi
(
cd "$SOURCE_DIR"
gleam build
sh ./scripts/build_dash_assets.sh "$DASH_ASSET_DIR"
)

if [ ! -d "$BUILD_DIR" ]; then
echo "Missing build output: $BUILD_DIR" >&2
exit 1
fi

if [ ! -d "$DASH_ASSET_DIR" ]; then
echo "Missing dashboard assets: $DASH_ASSET_DIR" >&2
exit 1
fi

if [ -z "$LABEL" ]; then
if LABEL="$(git -C "$SOURCE_DIR" rev-parse --short HEAD 2>/dev/null)"; then
:
Expand All @@ -88,6 +95,8 @@ for package_dir in "$BUILD_DIR"/*; do
fi
done

cp -R "$DASH_ASSET_DIR" "$INSTALL_DIR"/dash-assets

cat > "$INSTALL_DIR/entrypoint.sh" <<'EOF'
#!/bin/sh
set -eu
Expand All @@ -97,6 +106,7 @@ BASE=$(CDPATH= cd -- "$(dirname "$0")" && pwd)
COMMAND="${1-default}"

run() {
NIGHT_SHIFT_DASH_ASSET_ROOT="$BASE/dash-assets" \
exec erl \
-pa "$BASE"/*/ebin \
-eval "$PACKAGE@@main:run($PACKAGE)" \
Expand Down Expand Up @@ -137,6 +147,7 @@ $Command = if ($args.Count -gt 0) { $args[0] } else { "run" }
switch ($Command) {
"run" {
$Remaining = if ($args.Count -gt 1) { $args[1..($args.Count - 1)] } else { @() }
$env:NIGHT_SHIFT_DASH_ASSET_ROOT = "$Base/dash-assets"
& erl -pa "$Base/*/ebin" -eval "$Package@@main:run($Package)" -noshell -extra @Remaining
}
"shell" {
Expand Down
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ Night Shift already has working support for:
- provider adapters for Codex CLI and Cursor Agent
- local verification before pull request delivery
- review-loop ingestion for open Night Shift pull requests
- a local monitor-only dashboard via `start --ui` and `resume --ui`
- a repo-local Dash front door via `night-shift dash`

The current operator flow is:

Expand Down Expand Up @@ -126,10 +126,10 @@ night-shift resume --explain
night-shift resume
```

If you want the local dashboard while a run is active:
If you want the browser front door for planning, execution, and audit:

```sh
night-shift start --ui
night-shift dash
```

## Source Development
Expand Down
8 changes: 8 additions & 0 deletions dash_web/gleam.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
name = "dash_web"
version = "0.1.0"
target = "javascript"

[dependencies]
gleam_stdlib = ">= 0.44.0 and < 2.0.0"
gleam_json = ">= 3.1.0 and < 4.0.0"
lustre = ">= 5.3.0 and < 6.0.0"
16 changes: 16 additions & 0 deletions dash_web/manifest.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# This file was generated by Gleam
# You typically do not need to edit this file

packages = [
{ name = "gleam_erlang", version = "1.3.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_erlang", source = "hex", outer_checksum = "1124AD3AA21143E5AF0FC5CF3D9529F6DB8CA03E43A55711B60B6B7B3874375C" },
{ name = "gleam_json", version = "3.1.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_json", source = "hex", outer_checksum = "44FDAA8847BE8FC48CA7A1C089706BD54BADCC4C45B237A992EDDF9F2CDB2836" },
{ name = "gleam_otp", version = "1.2.0", build_tools = ["gleam"], requirements = ["gleam_erlang", "gleam_stdlib"], otp_app = "gleam_otp", source = "hex", outer_checksum = "BA6A294E295E428EC1562DC1C11EA7530DCB981E8359134BEABC8493B7B2258E" },
{ name = "gleam_stdlib", version = "0.71.0", build_tools = ["gleam"], requirements = [], otp_app = "gleam_stdlib", source = "hex", outer_checksum = "702F3BC2A14793906880B1078B19A6165F87323AEE8D0C4A34085846336FCAAE" },
{ name = "houdini", version = "1.2.0", build_tools = ["gleam"], requirements = [], otp_app = "houdini", source = "hex", outer_checksum = "5DB1053F1AF828049C2B206D4403C18970ABEF5C18671CA3C2D2ED0DD64F6385" },
{ name = "lustre", version = "5.6.0", build_tools = ["gleam"], requirements = ["gleam_erlang", "gleam_json", "gleam_otp", "gleam_stdlib", "houdini"], otp_app = "lustre", source = "hex", outer_checksum = "EE558CD4DB9F09FCC16417ADF0183A3C2DAC3E4B21ED3AC0CAE859792AB810CA" },
]

[requirements]
gleam_json = { version = ">= 3.1.0 and < 4.0.0" }
gleam_stdlib = { version = ">= 0.44.0 and < 2.0.0" }
lustre = { version = ">= 5.3.0 and < 6.0.0" }
71 changes: 71 additions & 0 deletions dash_web/src/browser_ffi.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
let activeSource = null;

export function initialRunTarget() {
const root = document.querySelector("#app");
const value = root?.dataset?.initialRun ?? "";
return value.length > 0 ? value : "latest";
}

function respond(response, onSuccess, onError) {
response
.text()
.then((text) => {
if (response.ok) {
onSuccess(text);
} else {
onError(text || `Request failed: ${response.status}`);
}
})
.catch((error) => onError(String(error)));
}

export function fetchWorkspace(targetRun, onSuccess, onError) {
const suffix =
targetRun && targetRun !== "latest"
? `?run=${encodeURIComponent(targetRun)}`
: "";
fetch(`/api/workspace${suffix}`, { cache: "no-store" })
.then((response) => respond(response, onSuccess, onError))
.catch((error) => onError(String(error)));
}

export function fetchModels(provider, onSuccess, onError) {
fetch(`/api/init/models?provider=${encodeURIComponent(provider)}`, {
cache: "no-store",
})
.then((response) => respond(response, onSuccess, onError))
.catch((error) => onError(String(error)));
}

export function postJson(path, payload, onSuccess, onError) {
fetch(path, {
method: "POST",
cache: "no-store",
headers: { "content-type": "application/json" },
body: payload,
})
.then((response) => respond(response, onSuccess, onError))
.catch((error) => onError(String(error)));
}

export function openEventStream(targetRun, onOpen, onMessage, onError) {
if (activeSource) {
activeSource.close();
}

const runId = targetRun && targetRun.length > 0 ? targetRun : "latest";
const source = new EventSource(`/api/runs/${encodeURIComponent(runId)}/events`);
activeSource = source;

source.onopen = () => {
onOpen("connected");
};

source.onmessage = (event) => {
onMessage(event.data);
};

source.onerror = () => {
onError("disconnected");
};
}
Loading
Loading