@jenga-ai/agent 3.2.0 → 3.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +52 -12
- package/agents/developer.md +16 -1
- package/agents/scrum-master.md +1 -0
- package/bin/jenga.js +10 -0
- package/lib/commands/dashboard.js +92 -0
- package/lib/skill-allow-list.json +6 -2
- package/package.json +21 -2
- package/project/app/api/lib/resolve-project-root.js +120 -0
- package/project/app/api/package.json +16 -0
- package/project/app/api/parsers/architecture.js +72 -0
- package/project/app/api/parsers/board.js +141 -0
- package/project/app/api/parsers/documentation.js +125 -0
- package/project/app/api/parsers/git-log.js +52 -0
- package/project/app/api/parsers/ideas.js +62 -0
- package/project/app/api/parsers/knowledge-graph.js +73 -0
- package/project/app/api/parsers/lib/markdown-dir-reader.js +163 -0
- package/project/app/api/parsers/rapports.js +148 -0
- package/project/app/api/parsers/todo.js +179 -0
- package/project/app/api/response.js +47 -0
- package/project/app/api/routes/architecture.js +23 -0
- package/project/app/api/routes/board.js +46 -0
- package/project/app/api/routes/documentation.js +24 -0
- package/project/app/api/routes/health.js +25 -0
- package/project/app/api/routes/history.js +55 -0
- package/project/app/api/routes/rapports.js +24 -0
- package/project/app/api/scripts/capture-snapshot.js +294 -0
- package/project/app/api/server.js +112 -0
- package/project/app/api/types.js +40 -0
- package/project/app/package.json +21 -0
- package/project/app/ui/dist/assets/index-7fj-vllY.js +104 -0
- package/project/app/ui/dist/assets/index-CdK3Qrep.css +1 -0
- package/project/app/ui/dist/index.html +13 -0
- package/project/app/ui/package.json +23 -0
- package/project/app/ui/scripts/build-snapshot-html.cjs +214 -0
- package/project/app/ui/scripts/dashboard-open.cjs +88 -0
- package/project/app/ui/scripts/dashboard-start.cjs +87 -0
- package/scripts/acquire-concurrency-slot.sh +220 -0
- package/scripts/compute-deploy-reconcile.sh +439 -0
- package/scripts/jenga-permission-level-switch.sh +19 -3
- package/scripts/mark-deployed.sh +532 -0
- package/scripts/populate-knowledge-graph.js +429 -0
- package/scripts/release-concurrency-slot.sh +129 -0
- package/scripts/validate-board.sh +60 -2
- package/scripts/verify-consumer-install.sh +470 -0
- package/skills/j-cloud-connect/SKILL.md +95 -0
- package/skills/j-cloud-connect/scripts/configure-backend.sh +267 -0
- package/skills/j-cloud-connect/scripts/install-rclone.sh +153 -0
- package/skills/j-dashboard/SKILL.md +144 -0
- package/skills/j-dashboard/scripts/launch.sh +121 -0
- package/skills/j-dashboard/scripts/resolve-app-dir.sh +164 -0
- package/skills/j-dashboard/scripts/snapshot.sh +267 -0
- package/skills/j-dashboard-share/SKILL.md +96 -0
- package/skills/j-dashboard-share/scripts/upload-snapshot.sh +173 -0
- package/skills/j-playbook/SKILL.md +12 -0
- package/skills/j-playbook-new/SKILL.md +155 -0
- package/skills/j-playbook-new/scripts/playbook-new.sh +332 -0
- package/skills/j-publish/scripts/npm_ci_pipeline.sh +6 -0
- package/skills/jenga/scripts/load-nl-catalog.js +22 -6
- package/skills/jenga/scripts/load-playbooks.sh +123 -24
- package/templates/SCRUM_BOARD_SCHEMA.md +18 -6
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# -----------------------------------------------------------------------------
|
|
3
|
+
# skills/j-dashboard/scripts/launch.sh
|
|
4
|
+
#
|
|
5
|
+
# Thin wrapper around project/app's existing `dashboard:start` / `dashboard:open`
|
|
6
|
+
# npm scripts (which themselves shell into
|
|
7
|
+
# project/app/ui/scripts/dashboard-start.cjs / dashboard-open.cjs). This script
|
|
8
|
+
# introduces NO server-launch, health-check, or browser-open logic of its own —
|
|
9
|
+
# it only resolves paths and forwards flags to those existing scripts, per
|
|
10
|
+
# E47_S04_T01's scope (the default, no-`--snapshot` launch mode of `j.dashboard`).
|
|
11
|
+
#
|
|
12
|
+
# Usage:
|
|
13
|
+
# launch.sh start [--port <n>] [--serve-app]
|
|
14
|
+
# launch.sh open [--port <n>]
|
|
15
|
+
# launch.sh both [--port <n>] [--serve-app]
|
|
16
|
+
#
|
|
17
|
+
# `both` starts the server in the background (it is long-running / blocking by
|
|
18
|
+
# design — see dashboard-start.cjs), waits briefly, then runs `open`, which
|
|
19
|
+
# itself health-checks before opening the browser and always exits 0 regardless
|
|
20
|
+
# of outcome (dashboard-open.cjs's own documented contract).
|
|
21
|
+
#
|
|
22
|
+
# Same symlink-resolution + repo-root derivation pattern used elsewhere in this
|
|
23
|
+
# skill library (e.g. skills/j-mirror-public/scripts/compute-publicize-diff.sh),
|
|
24
|
+
# so this script behaves identically whether invoked directly or via a symlink,
|
|
25
|
+
# and whether run from this monorepo or a mirrored/distributed copy.
|
|
26
|
+
# -----------------------------------------------------------------------------
|
|
27
|
+
|
|
28
|
+
set -euo pipefail
|
|
29
|
+
|
|
30
|
+
usage() {
|
|
31
|
+
cat <<'EOF'
|
|
32
|
+
Usage: launch.sh <start|open|both> [--port <n>] [--serve-app]
|
|
33
|
+
|
|
34
|
+
start Run `npm run dashboard:start` in project/app.
|
|
35
|
+
open Run `npm run dashboard:open` in project/app.
|
|
36
|
+
both Start the server in the background, then open the browser.
|
|
37
|
+
|
|
38
|
+
--port <n> Forwarded unchanged to the underlying npm script.
|
|
39
|
+
--serve-app Forwarded unchanged to `dashboard:start` only (ignored by `open`).
|
|
40
|
+
EOF
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
die() {
|
|
44
|
+
echo "Error: $*" >&2
|
|
45
|
+
exit 1
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
if [ $# -lt 1 ]; then
|
|
49
|
+
usage >&2
|
|
50
|
+
die "missing mode argument"
|
|
51
|
+
fi
|
|
52
|
+
|
|
53
|
+
MODE="$1"
|
|
54
|
+
shift
|
|
55
|
+
|
|
56
|
+
case "$MODE" in
|
|
57
|
+
start|open|both) ;;
|
|
58
|
+
-h|--help) usage; exit 0 ;;
|
|
59
|
+
*) usage >&2; die "unknown mode: $MODE (expected start|open|both)" ;;
|
|
60
|
+
esac
|
|
61
|
+
|
|
62
|
+
# -----------------------------------------------------------------------------
|
|
63
|
+
# Locate script + repo root (symlink-resolved SCRIPT_DIR -> SKILL_DIR -> REPO_ROOT)
|
|
64
|
+
# -----------------------------------------------------------------------------
|
|
65
|
+
|
|
66
|
+
SCRIPT_PATH="${BASH_SOURCE[0]}"
|
|
67
|
+
while [ -h "$SCRIPT_PATH" ]; do
|
|
68
|
+
LINK_TARGET="$(readlink "$SCRIPT_PATH")"
|
|
69
|
+
case "$LINK_TARGET" in
|
|
70
|
+
/*) SCRIPT_PATH="$LINK_TARGET" ;;
|
|
71
|
+
*) SCRIPT_PATH="$(cd "$(dirname "$SCRIPT_PATH")" && pwd)/$LINK_TARGET" ;;
|
|
72
|
+
esac
|
|
73
|
+
done
|
|
74
|
+
SCRIPT_DIR="$(cd "$(dirname "$SCRIPT_PATH")" && pwd)"
|
|
75
|
+
SKILL_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
|
|
76
|
+
|
|
77
|
+
# project/app is NOT always at the repo root: in a consumer install it ships
|
|
78
|
+
# inside the package, under node_modules/@jenga-ai/agent/. resolve-app-dir.sh
|
|
79
|
+
# owns that whole search (and is shared with snapshot.sh) — see its header.
|
|
80
|
+
# Invoked via `bash`, not executed directly: a shipped script losing its
|
|
81
|
+
# executable bit is exactly the packaging defect this change also fixes, and
|
|
82
|
+
# resolving the app dir must not be the thing that breaks when it happens.
|
|
83
|
+
APP_DIR="$(bash "$SCRIPT_DIR/resolve-app-dir.sh" --marker "package.json" --from "$(pwd)")" \
|
|
84
|
+
|| die "could not locate the dashboard app directory (see message above)"
|
|
85
|
+
|
|
86
|
+
# Remaining args (--port <n>, --serve-app) are forwarded verbatim via the
|
|
87
|
+
# "${EXTRA_ARGS[@]:-}" default-expansion form, not the bare "${EXTRA_ARGS[@]}"
|
|
88
|
+
# form. On bash < 4.4 — notably including macOS's stock /bin/bash 3.2, which
|
|
89
|
+
# `#!/usr/bin/env bash` resolves to by default on that platform — expanding
|
|
90
|
+
# an empty array under `set -u` ("nounset") throws "unbound variable"; the
|
|
91
|
+
# `:-` default (even though the default itself is empty) is an explicit,
|
|
92
|
+
# nounset-safe form that keeps `set -u` in force everywhere in this script.
|
|
93
|
+
# This preserves the `-- ` separator unconditionally (npm run <script> --
|
|
94
|
+
# with nothing after it is a harmless no-op, and always including it avoids
|
|
95
|
+
# reintroducing the nested-npm-run flag-forwarding bug this epic already hit
|
|
96
|
+
# once — see epic E47's own history) rather than branching on argument count.
|
|
97
|
+
EXTRA_ARGS=("$@")
|
|
98
|
+
|
|
99
|
+
run_start() {
|
|
100
|
+
(cd "$APP_DIR" && npm run dashboard:start -- "${EXTRA_ARGS[@]:-}")
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
run_open() {
|
|
104
|
+
(cd "$APP_DIR" && npm run dashboard:open -- "${EXTRA_ARGS[@]:-}")
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
case "$MODE" in
|
|
108
|
+
start)
|
|
109
|
+
run_start
|
|
110
|
+
;;
|
|
111
|
+
open)
|
|
112
|
+
run_open
|
|
113
|
+
;;
|
|
114
|
+
both)
|
|
115
|
+
run_start &
|
|
116
|
+
START_PID=$!
|
|
117
|
+
echo "Started dashboard server in background (pid $START_PID)."
|
|
118
|
+
sleep 1
|
|
119
|
+
run_open
|
|
120
|
+
;;
|
|
121
|
+
esac
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# -----------------------------------------------------------------------------
|
|
3
|
+
# skills/j-dashboard/scripts/resolve-app-dir.sh
|
|
4
|
+
#
|
|
5
|
+
# Prints the absolute path of the dashboard's `project/app` directory, or exits
|
|
6
|
+
# non-zero with a clear message if it cannot be found.
|
|
7
|
+
#
|
|
8
|
+
# Why this script exists
|
|
9
|
+
# ----------------------
|
|
10
|
+
# launch.sh and snapshot.sh both used to hard-code
|
|
11
|
+
#
|
|
12
|
+
# APP_DIR="$(git rev-parse --show-toplevel)/project/app"
|
|
13
|
+
#
|
|
14
|
+
# which is only ever correct inside this monorepo. In a CONSUMER install the
|
|
15
|
+
# dashboard does not live at the consumer repo root at all — it ships inside the
|
|
16
|
+
# package, at <consumer>/node_modules/@jenga-ai/agent/project/app — so both
|
|
17
|
+
# scripts died with "capture script not found" / "dashboard app not found" on
|
|
18
|
+
# every consumer, no matter how well the package was built. Centralizing the
|
|
19
|
+
# resolution here means that fallback exists once, for both callers, instead of
|
|
20
|
+
# being duplicated (or, as it was, missing entirely).
|
|
21
|
+
#
|
|
22
|
+
# Resolution order — first candidate whose <candidate>/<marker> exists wins:
|
|
23
|
+
#
|
|
24
|
+
# 1. Package-root-relative: this script lives at
|
|
25
|
+
# <root>/skills/j-dashboard/scripts/, so <root>/project/app is three
|
|
26
|
+
# levels up. Covers the monorepo invoked directly AND the package invoked
|
|
27
|
+
# in place from node_modules/@jenga-ai/agent/skills/j-dashboard/scripts/.
|
|
28
|
+
# 2. Git repo root: covers the monorepo invoked through a mirrored copy under
|
|
29
|
+
# .claude/skills/ or .agents/skills/, where (1) resolves to the mirror
|
|
30
|
+
# directory rather than the repo root.
|
|
31
|
+
# 3. Git repo root + node_modules/@jenga-ai/agent: the consumer install case —
|
|
32
|
+
# the skill was mirrored into <consumer>/.claude/skills/ by postinstall, so
|
|
33
|
+
# neither (1) nor (2) can see the packaged app.
|
|
34
|
+
# 4. Walk up from --from (default: cwd), checking both project/app and
|
|
35
|
+
# node_modules/@jenga-ai/agent/project/app at each level. Covers consumers
|
|
36
|
+
# that are not git repositories at all, where (2) and (3) are unavailable.
|
|
37
|
+
#
|
|
38
|
+
# Usage:
|
|
39
|
+
# resolve-app-dir.sh --marker <relative-path> [--from <dir>]
|
|
40
|
+
#
|
|
41
|
+
# --marker <relative-path> File that must exist under project/app for a
|
|
42
|
+
# candidate to be accepted. Callers pass whatever
|
|
43
|
+
# they actually need, so a half-shipped package is
|
|
44
|
+
# rejected here rather than failing later with a
|
|
45
|
+
# confusing error: launch.sh passes `package.json`,
|
|
46
|
+
# snapshot.sh passes `api/scripts/capture-snapshot.js`.
|
|
47
|
+
# --from <dir> Starting directory for the walk-up candidate.
|
|
48
|
+
# Default: the current working directory.
|
|
49
|
+
#
|
|
50
|
+
# Exit codes: 0 (path printed to stdout), 2 (bad usage), 1 (not found).
|
|
51
|
+
# -----------------------------------------------------------------------------
|
|
52
|
+
|
|
53
|
+
set -euo pipefail
|
|
54
|
+
|
|
55
|
+
die() {
|
|
56
|
+
echo "Error: $*" >&2
|
|
57
|
+
exit 1
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
MARKER=""
|
|
61
|
+
START_DIR=""
|
|
62
|
+
|
|
63
|
+
while [ $# -gt 0 ]; do
|
|
64
|
+
case "$1" in
|
|
65
|
+
--marker)
|
|
66
|
+
[ $# -ge 2 ] || { echo "Error: --marker requires a value" >&2; exit 2; }
|
|
67
|
+
MARKER="$2"
|
|
68
|
+
shift 2
|
|
69
|
+
;;
|
|
70
|
+
--from)
|
|
71
|
+
[ $# -ge 2 ] || { echo "Error: --from requires a value" >&2; exit 2; }
|
|
72
|
+
START_DIR="$2"
|
|
73
|
+
shift 2
|
|
74
|
+
;;
|
|
75
|
+
-h|--help)
|
|
76
|
+
sed -n '/^# Usage:/,/^# Exit codes/p' "$0" | sed 's/^# \{0,1\}//'
|
|
77
|
+
exit 0
|
|
78
|
+
;;
|
|
79
|
+
*)
|
|
80
|
+
echo "Error: unknown argument: $1" >&2
|
|
81
|
+
exit 2
|
|
82
|
+
;;
|
|
83
|
+
esac
|
|
84
|
+
done
|
|
85
|
+
|
|
86
|
+
[ -n "$MARKER" ] || { echo "Error: --marker is required" >&2; exit 2; }
|
|
87
|
+
[ -n "$START_DIR" ] || START_DIR="$(pwd)"
|
|
88
|
+
|
|
89
|
+
# -----------------------------------------------------------------------------
|
|
90
|
+
# Locate this script (symlink-resolved), matching launch.sh/snapshot.sh's own
|
|
91
|
+
# pattern so behavior is identical however the skill directory was reached.
|
|
92
|
+
# -----------------------------------------------------------------------------
|
|
93
|
+
|
|
94
|
+
SCRIPT_PATH="${BASH_SOURCE[0]}"
|
|
95
|
+
while [ -h "$SCRIPT_PATH" ]; do
|
|
96
|
+
LINK_TARGET="$(readlink "$SCRIPT_PATH")"
|
|
97
|
+
case "$LINK_TARGET" in
|
|
98
|
+
/*) SCRIPT_PATH="$LINK_TARGET" ;;
|
|
99
|
+
*) SCRIPT_PATH="$(cd "$(dirname "$SCRIPT_PATH")" && pwd)/$LINK_TARGET" ;;
|
|
100
|
+
esac
|
|
101
|
+
done
|
|
102
|
+
SCRIPT_DIR="$(cd "$(dirname "$SCRIPT_PATH")" && pwd)"
|
|
103
|
+
|
|
104
|
+
# The installed package name, kept in one place so a rename only touches here.
|
|
105
|
+
PKG_SUBPATH="node_modules/@jenga-ai/agent"
|
|
106
|
+
|
|
107
|
+
# Accepts a candidate base directory if <base>/project/app/<marker> exists.
|
|
108
|
+
# Prints the resolved app dir and returns 0; returns 1 otherwise.
|
|
109
|
+
try_base() {
|
|
110
|
+
local base="$1"
|
|
111
|
+
[ -n "$base" ] || return 1
|
|
112
|
+
local app="$base/project/app"
|
|
113
|
+
[ -f "$app/$MARKER" ] || return 1
|
|
114
|
+
(cd "$app" && pwd)
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
CANDIDATES_TRIED=()
|
|
118
|
+
|
|
119
|
+
# ── 1. Package-root-relative (monorepo direct, or package invoked in place) ────
|
|
120
|
+
PKG_ROOT="$(cd "$SCRIPT_DIR/../../.." 2>/dev/null && pwd || true)"
|
|
121
|
+
if [ -n "$PKG_ROOT" ]; then
|
|
122
|
+
CANDIDATES_TRIED+=("$PKG_ROOT/project/app")
|
|
123
|
+
if RESOLVED="$(try_base "$PKG_ROOT")"; then
|
|
124
|
+
echo "$RESOLVED"
|
|
125
|
+
exit 0
|
|
126
|
+
fi
|
|
127
|
+
fi
|
|
128
|
+
|
|
129
|
+
# ── 2 & 3. Git repo root, then the package inside it (consumer install) ───────
|
|
130
|
+
REPO_ROOT="$(git -C "$SCRIPT_DIR" rev-parse --show-toplevel 2>/dev/null || true)"
|
|
131
|
+
if [ -n "$REPO_ROOT" ]; then
|
|
132
|
+
CANDIDATES_TRIED+=("$REPO_ROOT/project/app" "$REPO_ROOT/$PKG_SUBPATH/project/app")
|
|
133
|
+
if RESOLVED="$(try_base "$REPO_ROOT")"; then
|
|
134
|
+
echo "$RESOLVED"
|
|
135
|
+
exit 0
|
|
136
|
+
fi
|
|
137
|
+
if RESOLVED="$(try_base "$REPO_ROOT/$PKG_SUBPATH")"; then
|
|
138
|
+
echo "$RESOLVED"
|
|
139
|
+
exit 0
|
|
140
|
+
fi
|
|
141
|
+
fi
|
|
142
|
+
|
|
143
|
+
# ── 4. Walk up from --from, for consumers that are not git repositories ───────
|
|
144
|
+
# Bounded by reaching "/" so it always terminates, and checks the packaged
|
|
145
|
+
# location at every level because node_modules may sit above the invoking dir.
|
|
146
|
+
#
|
|
147
|
+
# Deliberately checks ONLY $PKG_SUBPATH/project/app here, never a bare
|
|
148
|
+
# project/app. An installed package is unambiguous evidence that this dashboard
|
|
149
|
+
# belongs to this project; a bare project/app in some ancestor directory is not
|
|
150
|
+
# — it is just as likely to be an unrelated checkout that happens to sit higher
|
|
151
|
+
# up the tree, and silently rendering THAT project's board would be worse than
|
|
152
|
+
# failing. Bare project/app is only ever accepted via candidates 1 and 2, which
|
|
153
|
+
# are anchored to this script's own location rather than the caller's cwd.
|
|
154
|
+
DIR="$(cd "$START_DIR" 2>/dev/null && pwd || true)"
|
|
155
|
+
while [ -n "$DIR" ]; do
|
|
156
|
+
if RESOLVED="$(try_base "$DIR/$PKG_SUBPATH")"; then
|
|
157
|
+
echo "$RESOLVED"
|
|
158
|
+
exit 0
|
|
159
|
+
fi
|
|
160
|
+
[ "$DIR" = "/" ] && break
|
|
161
|
+
DIR="$(dirname "$DIR")"
|
|
162
|
+
done
|
|
163
|
+
|
|
164
|
+
die "dashboard app not found (looked for project/app/$MARKER). Tried: ${CANDIDATES_TRIED[*]:-none}, and walked up from $START_DIR checking both project/app and $PKG_SUBPATH/project/app. If this is a consumer install, reinstall @jenga-ai/agent with install scripts enabled (npm approve-scripts @jenga-ai/agent) so the dashboard is present."
|
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# -----------------------------------------------------------------------------
|
|
3
|
+
# skills/j-dashboard/scripts/snapshot.sh
|
|
4
|
+
#
|
|
5
|
+
# Orchestrates `j.dashboard --snapshot` (E47_S04_T03): runs E47_S04_T02's capture
|
|
6
|
+
# step, then bundles the UI into a single self-contained HTML file with that
|
|
7
|
+
# captured data embedded inline, and reports the final output path.
|
|
8
|
+
#
|
|
9
|
+
# Step 1 — capture: node project/app/api/scripts/capture-snapshot.js writes a
|
|
10
|
+
# single JSON artifact ({schema_version, captured_at, project_root, routes})
|
|
11
|
+
# by calling /v1/board, /v1/history, /v1/architecture exactly once each via a
|
|
12
|
+
# short-lived ad-hoc server. Run from the ORIGINAL invocation directory
|
|
13
|
+
# (captured before any `cd` below) so its own resolveProjectRoot() walk-up
|
|
14
|
+
# resolves the *invoking* project's root (E47_S02's contract) — not this
|
|
15
|
+
# framework repo's, and not project/app/ui's.
|
|
16
|
+
#
|
|
17
|
+
# Step 2 — bundle: project/app/ui/scripts/build-snapshot-html.cjs inlines the
|
|
18
|
+
# built dist/ (JS + CSS) into a single index.html and injects the captured
|
|
19
|
+
# JSON as an inline <script id="jenga-dashboard-data"> tag. Zero dependencies
|
|
20
|
+
# and no build tooling, so it behaves identically in this monorepo and in a
|
|
21
|
+
# consumer install, which only ever receives the prebuilt dist/. Where the UI
|
|
22
|
+
# sources and node_modules are available, dist/ is rebuilt first so a
|
|
23
|
+
# snapshot is never taken from a stale build.
|
|
24
|
+
#
|
|
25
|
+
# project/app itself is located by resolve-app-dir.sh, which also knows to look
|
|
26
|
+
# inside node_modules/@jenga-ai/agent — this script previously assumed the
|
|
27
|
+
# repo root, which is wrong for every consumer install.
|
|
28
|
+
#
|
|
29
|
+
# Either step failing hard-fails this script (set -e) with no output file
|
|
30
|
+
# written — matching capture-snapshot.js's own "no partial artifact" contract.
|
|
31
|
+
#
|
|
32
|
+
# --data-url (E47_S04_T04): remote-delivery mode for sessions that cannot
|
|
33
|
+
# assume a shared filesystem with the user. After the existing build/copy
|
|
34
|
+
# step, base64-encodes the final output HTML and prints a
|
|
35
|
+
# `data:text/html;base64,...` URI to stdout, additive to (not a replacement
|
|
36
|
+
# for) the existing "Snapshot dashboard written to: <path>" line. Refuses
|
|
37
|
+
# (hard-fail, no partial output) if the post-encoding size exceeds
|
|
38
|
+
# MAX_DATA_URL_BYTES (default ~25MB, overridable via
|
|
39
|
+
# SNAPSHOT_MAX_DATA_URL_BYTES for testing/tuning). No effect at all on the
|
|
40
|
+
# plain-path behavior when --data-url is not passed.
|
|
41
|
+
#
|
|
42
|
+
# Usage:
|
|
43
|
+
# snapshot.sh [--out <path>] [--project-root <path>] [--data-url]
|
|
44
|
+
#
|
|
45
|
+
# --out <path> Final output HTML path. Default: <cwd>/jenga.html
|
|
46
|
+
# (cwd at invocation time, i.e. the invoking project's
|
|
47
|
+
# own directory — same directory --project-root would
|
|
48
|
+
# otherwise need to point at).
|
|
49
|
+
# --project-root <path> Forwarded unchanged to capture-snapshot.js's own
|
|
50
|
+
# --project-root override. Default: let
|
|
51
|
+
# capture-snapshot.js resolve it from the invocation
|
|
52
|
+
# cwd (no override).
|
|
53
|
+
# --data-url After writing --out, also print a
|
|
54
|
+
# `data:text/html;base64,...` URI of the same file
|
|
55
|
+
# to stdout. Refuses (non-zero exit, no URI printed)
|
|
56
|
+
# if the base64-encoded size exceeds
|
|
57
|
+
# SNAPSHOT_MAX_DATA_URL_BYTES (default ~25MB).
|
|
58
|
+
# -----------------------------------------------------------------------------
|
|
59
|
+
|
|
60
|
+
set -euo pipefail
|
|
61
|
+
|
|
62
|
+
# Post-encoding size threshold for --data-url, in bytes. Overridable via
|
|
63
|
+
# SNAPSHOT_MAX_DATA_URL_BYTES (used by tests to exercise the refusal path
|
|
64
|
+
# without generating a real multi-megabyte fixture).
|
|
65
|
+
MAX_DATA_URL_BYTES="${SNAPSHOT_MAX_DATA_URL_BYTES:-26214400}" # 25 * 1024 * 1024
|
|
66
|
+
|
|
67
|
+
usage() {
|
|
68
|
+
cat <<'EOF'
|
|
69
|
+
Usage: snapshot.sh [--out <path>] [--project-root <path>] [--data-url]
|
|
70
|
+
|
|
71
|
+
--out <path> Final output HTML path. Default: <cwd>/jenga.html
|
|
72
|
+
--project-root <path> Forwarded to capture-snapshot.js's --project-root override.
|
|
73
|
+
--data-url Also print a data:text/html;base64,... URI of the
|
|
74
|
+
output file to stdout (for remote/no-shared-filesystem
|
|
75
|
+
sessions). Refuses if the encoded size exceeds ~25MB
|
|
76
|
+
(SNAPSHOT_MAX_DATA_URL_BYTES).
|
|
77
|
+
EOF
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
die() {
|
|
81
|
+
echo "Error: $*" >&2
|
|
82
|
+
exit 1
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
OUT_PATH=""
|
|
86
|
+
PROJECT_ROOT_ARG=""
|
|
87
|
+
DATA_URL=0
|
|
88
|
+
|
|
89
|
+
while [ $# -gt 0 ]; do
|
|
90
|
+
case "$1" in
|
|
91
|
+
--out)
|
|
92
|
+
[ $# -ge 2 ] || die "--out requires a value"
|
|
93
|
+
OUT_PATH="$2"
|
|
94
|
+
shift 2
|
|
95
|
+
;;
|
|
96
|
+
--project-root)
|
|
97
|
+
[ $# -ge 2 ] || die "--project-root requires a value"
|
|
98
|
+
PROJECT_ROOT_ARG="$2"
|
|
99
|
+
shift 2
|
|
100
|
+
;;
|
|
101
|
+
--data-url)
|
|
102
|
+
DATA_URL=1
|
|
103
|
+
shift
|
|
104
|
+
;;
|
|
105
|
+
-h|--help)
|
|
106
|
+
usage
|
|
107
|
+
exit 0
|
|
108
|
+
;;
|
|
109
|
+
*)
|
|
110
|
+
usage >&2
|
|
111
|
+
die "unknown argument: $1"
|
|
112
|
+
;;
|
|
113
|
+
esac
|
|
114
|
+
done
|
|
115
|
+
|
|
116
|
+
# Capture the invocation cwd BEFORE any `cd` below — this is what
|
|
117
|
+
# capture-snapshot.js's own resolveProjectRoot() walk-up must see, per E47_S02's
|
|
118
|
+
# "resolve against the invoking project, not this repo" contract.
|
|
119
|
+
ORIG_CWD="$(pwd)"
|
|
120
|
+
|
|
121
|
+
if [ -z "$OUT_PATH" ]; then
|
|
122
|
+
OUT_PATH="$ORIG_CWD/jenga.html"
|
|
123
|
+
fi
|
|
124
|
+
# Resolve OUT_PATH to an absolute path up front, since later steps `cd` elsewhere
|
|
125
|
+
# and a relative --out would otherwise silently resolve against the wrong directory.
|
|
126
|
+
case "$OUT_PATH" in
|
|
127
|
+
/*) ;;
|
|
128
|
+
*) OUT_PATH="$ORIG_CWD/$OUT_PATH" ;;
|
|
129
|
+
esac
|
|
130
|
+
|
|
131
|
+
# -----------------------------------------------------------------------------
|
|
132
|
+
# Locate script + repo root (symlink-resolved SCRIPT_DIR -> SKILL_DIR -> REPO_ROOT)
|
|
133
|
+
# Same pattern as launch.sh — behaves identically whether invoked directly or
|
|
134
|
+
# via a symlink, and whether run from this monorepo or a mirrored/distributed copy.
|
|
135
|
+
# -----------------------------------------------------------------------------
|
|
136
|
+
|
|
137
|
+
SCRIPT_PATH="${BASH_SOURCE[0]}"
|
|
138
|
+
while [ -h "$SCRIPT_PATH" ]; do
|
|
139
|
+
LINK_TARGET="$(readlink "$SCRIPT_PATH")"
|
|
140
|
+
case "$LINK_TARGET" in
|
|
141
|
+
/*) SCRIPT_PATH="$LINK_TARGET" ;;
|
|
142
|
+
*) SCRIPT_PATH="$(cd "$(dirname "$SCRIPT_PATH")" && pwd)/$LINK_TARGET" ;;
|
|
143
|
+
esac
|
|
144
|
+
done
|
|
145
|
+
SCRIPT_DIR="$(cd "$(dirname "$SCRIPT_PATH")" && pwd)"
|
|
146
|
+
SKILL_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
|
|
147
|
+
|
|
148
|
+
# project/app is NOT always at the repo root: in a consumer install it ships
|
|
149
|
+
# inside the package, under node_modules/@jenga-ai/agent/. resolve-app-dir.sh
|
|
150
|
+
# owns that whole search (and is shared with launch.sh) — see its header.
|
|
151
|
+
# Invoked via `bash`, not executed directly: a shipped script losing its
|
|
152
|
+
# executable bit is exactly the packaging defect this change also fixes, and
|
|
153
|
+
# resolving the app dir must not be the thing that breaks when it happens.
|
|
154
|
+
APP_DIR="$(bash "$SCRIPT_DIR/resolve-app-dir.sh" --marker "api/scripts/capture-snapshot.js" --from "$ORIG_CWD")" \
|
|
155
|
+
|| die "could not locate the dashboard app directory (see message above)"
|
|
156
|
+
|
|
157
|
+
API_DIR="$APP_DIR/api"
|
|
158
|
+
UI_DIR="$APP_DIR/ui"
|
|
159
|
+
|
|
160
|
+
[ -f "$API_DIR/scripts/capture-snapshot.js" ] || die "capture script not found at $API_DIR/scripts/capture-snapshot.js"
|
|
161
|
+
# The built UI, not its sources: bundling no longer needs vite (see Step 2).
|
|
162
|
+
[ -f "$UI_DIR/dist/index.html" ] || die "dashboard UI has not been built — no $UI_DIR/dist/index.html. In this monorepo run: npm run ui:build --prefix project/app. In a consumer install this means the package shipped without project/app/ui/dist (a packaging regression)."
|
|
163
|
+
|
|
164
|
+
# -----------------------------------------------------------------------------
|
|
165
|
+
# Scratch workspace — always cleaned up, success or failure.
|
|
166
|
+
# -----------------------------------------------------------------------------
|
|
167
|
+
|
|
168
|
+
TMP_DIR="$(mktemp -d)"
|
|
169
|
+
trap 'rm -rf "$TMP_DIR"' EXIT
|
|
170
|
+
|
|
171
|
+
SNAPSHOT_JSON="$TMP_DIR/dashboard-snapshot-data.json"
|
|
172
|
+
SNAPSHOT_DIST="$TMP_DIR/dist-snapshot"
|
|
173
|
+
|
|
174
|
+
# -----------------------------------------------------------------------------
|
|
175
|
+
# Step 1 — capture (from ORIG_CWD, not REPO_ROOT/UI_DIR — see header comment)
|
|
176
|
+
# -----------------------------------------------------------------------------
|
|
177
|
+
|
|
178
|
+
CAPTURE_ARGS=(--out "$SNAPSHOT_JSON")
|
|
179
|
+
if [ -n "$PROJECT_ROOT_ARG" ]; then
|
|
180
|
+
CAPTURE_ARGS+=(--project-root "$PROJECT_ROOT_ARG")
|
|
181
|
+
fi
|
|
182
|
+
|
|
183
|
+
(cd "$ORIG_CWD" && node "$API_DIR/scripts/capture-snapshot.js" "${CAPTURE_ARGS[@]}")
|
|
184
|
+
|
|
185
|
+
[ -f "$SNAPSHOT_JSON" ] || die "capture step reported success but no artifact was written to $SNAPSHOT_JSON"
|
|
186
|
+
|
|
187
|
+
# -----------------------------------------------------------------------------
|
|
188
|
+
# Step 2 — bundle: single-file build with the captured data embedded inline.
|
|
189
|
+
#
|
|
190
|
+
# Two sub-steps, deliberately split so the second one is identical everywhere:
|
|
191
|
+
#
|
|
192
|
+
# 2a. If the UI sources AND its node_modules are present (i.e. this monorepo,
|
|
193
|
+
# or a dev checkout), refresh dist/ first so a snapshot never silently
|
|
194
|
+
# ships a stale build. Skipped entirely in a consumer install, which has
|
|
195
|
+
# only the prebuilt dist/ the package shipped — and needs nothing more.
|
|
196
|
+
# If sources are present but node_modules/vite isn't (so the rebuild
|
|
197
|
+
# above can't run), a dev checkout still refuses to bundle a dist/ that
|
|
198
|
+
# looks older than its own sources — see the staleness check below.
|
|
199
|
+
# 2b. Inline that dist/ into one self-contained HTML file with the captured
|
|
200
|
+
# JSON embedded, via the dependency-free build-snapshot-html.cjs.
|
|
201
|
+
#
|
|
202
|
+
# This used to be `npm run build:snapshot` (vite build --mode snapshot), which
|
|
203
|
+
# only ever worked here: the tarball ships dist/ and scripts/ but no
|
|
204
|
+
# package.json, vite.config.js, or src/, so there was no vite to run on any
|
|
205
|
+
# consumer. Inlining a prebuilt dist needs no build tooling, so one code path
|
|
206
|
+
# now serves both — and every local run exercises the consumer path too.
|
|
207
|
+
# -----------------------------------------------------------------------------
|
|
208
|
+
|
|
209
|
+
if [ -f "$UI_DIR/package.json" ] && [ -d "$UI_DIR/node_modules/vite" ]; then
|
|
210
|
+
(cd "$UI_DIR" && npm run build)
|
|
211
|
+
[ -f "$UI_DIR/dist/index.html" ] || die "UI rebuild reported success but produced no $UI_DIR/dist/index.html"
|
|
212
|
+
elif [ -d "$UI_DIR/src" ]; then
|
|
213
|
+
# This is a dev checkout (it has UI sources, unlike a consumer install which
|
|
214
|
+
# only ships prebuilt dist/ + scripts/), but node_modules/vite isn't present
|
|
215
|
+
# right now to rebuild with -- e.g. `npm install` hasn't been (re-)run for
|
|
216
|
+
# this package. The rebuild above was skipped, so dist/ may already be
|
|
217
|
+
# stale relative to src/. A stale snapshot must never ship silently (this is
|
|
218
|
+
# exactly how a fixed bug re-appeared in a previously-generated jenga.html):
|
|
219
|
+
# fail loudly instead of trusting an on-disk dist/ of unknown age.
|
|
220
|
+
STALE_SRC="$(find "$UI_DIR/src" -type f -newer "$UI_DIR/dist/index.html" -print -quit 2>/dev/null)"
|
|
221
|
+
if [ -n "$STALE_SRC" ]; then
|
|
222
|
+
die "$UI_DIR/dist is older than UI sources (e.g. $STALE_SRC) and node_modules/vite is not installed here to rebuild it -- refusing to bundle a possibly-stale snapshot. Run 'npm install' in $UI_DIR (or otherwise rebuild dist/), then retry."
|
|
223
|
+
fi
|
|
224
|
+
fi
|
|
225
|
+
|
|
226
|
+
mkdir -p "$SNAPSHOT_DIST"
|
|
227
|
+
node "$UI_DIR/scripts/build-snapshot-html.cjs" \
|
|
228
|
+
--dist "$UI_DIR/dist" \
|
|
229
|
+
--data "$SNAPSHOT_JSON" \
|
|
230
|
+
--out "$SNAPSHOT_DIST/index.html"
|
|
231
|
+
|
|
232
|
+
[ -f "$SNAPSHOT_DIST/index.html" ] || die "bundling step reported success but no index.html was produced in $SNAPSHOT_DIST"
|
|
233
|
+
|
|
234
|
+
# -----------------------------------------------------------------------------
|
|
235
|
+
# Step 3 — place the final artifact and report its path.
|
|
236
|
+
# -----------------------------------------------------------------------------
|
|
237
|
+
|
|
238
|
+
mkdir -p "$(dirname "$OUT_PATH")"
|
|
239
|
+
cp "$SNAPSHOT_DIST/index.html" "$OUT_PATH"
|
|
240
|
+
|
|
241
|
+
echo "Snapshot dashboard written to: $OUT_PATH"
|
|
242
|
+
|
|
243
|
+
# -----------------------------------------------------------------------------
|
|
244
|
+
# Step 4 — optional data: URL delivery mode (E47_S04_T04).
|
|
245
|
+
#
|
|
246
|
+
# For sessions that cannot assume a shared filesystem with the user (e.g. a
|
|
247
|
+
# remote/cloud agent session), base64-encode the just-written output file and
|
|
248
|
+
# print a data:text/html;base64,... URI any browser can open directly — no
|
|
249
|
+
# hosting, third-party service, or git round-trip required. Additive to the
|
|
250
|
+
# plain-path report above, never a replacement for it.
|
|
251
|
+
# -----------------------------------------------------------------------------
|
|
252
|
+
|
|
253
|
+
if [ "$DATA_URL" -eq 1 ]; then
|
|
254
|
+
[ -f "$OUT_PATH" ] || die "--data-url: expected output file missing at $OUT_PATH after write step"
|
|
255
|
+
|
|
256
|
+
# `base64` without newline-wrapping flags (GNU's -w0 and BSD/macOS's -b are
|
|
257
|
+
# not portable across each other), then strip embedded newlines with `tr` --
|
|
258
|
+
# portable everywhere and avoids the platform-specific flag entirely.
|
|
259
|
+
ENCODED="$(base64 <"$OUT_PATH" | tr -d '\n')"
|
|
260
|
+
ENCODED_BYTES="${#ENCODED}"
|
|
261
|
+
|
|
262
|
+
if [ "$ENCODED_BYTES" -gt "$MAX_DATA_URL_BYTES" ]; then
|
|
263
|
+
die "--data-url: encoded size ($ENCODED_BYTES bytes) for $OUT_PATH exceeds the $MAX_DATA_URL_BYTES-byte threshold; refusing to emit an oversized data: URI. Use the plain --out file path instead, or raise SNAPSHOT_MAX_DATA_URL_BYTES if you understand the tradeoff."
|
|
264
|
+
fi
|
|
265
|
+
|
|
266
|
+
echo "data:text/html;base64,${ENCODED}"
|
|
267
|
+
fi
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: j.dashboard-share
|
|
3
|
+
description: Snapshot the project dashboard and upload it to a configured cloud storage remote in one step, by sequencing j-dashboard's snapshot script and E47_S05_T01's rclone upload script.
|
|
4
|
+
keywords:
|
|
5
|
+
- dashboard share
|
|
6
|
+
- share dashboard
|
|
7
|
+
- upload dashboard
|
|
8
|
+
- cloud share snapshot
|
|
9
|
+
- dashboard to drive
|
|
10
|
+
- snapshot upload
|
|
11
|
+
examples:
|
|
12
|
+
- "share the dashboard snapshot to the cloud"
|
|
13
|
+
- "upload a dashboard snapshot"
|
|
14
|
+
- "j.dashboard-share"
|
|
15
|
+
- "send the dashboard snapshot to my google drive"
|
|
16
|
+
- "snapshot and upload the dashboard"
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
# Dashboard Share — Snapshot + Cloud Upload
|
|
20
|
+
|
|
21
|
+
## Purpose
|
|
22
|
+
|
|
23
|
+
`E47_S05` chains two already-implemented, independently-scoped capabilities into one step: capturing
|
|
24
|
+
a point-in-time dashboard snapshot and uploading it to a configured cloud storage remote. Both halves
|
|
25
|
+
already exist as standalone scripts — `skills/j-dashboard/scripts/snapshot.sh` (`E47_S04_T02`/`T03`,
|
|
26
|
+
capture + single-file bundle) and `skills/j-dashboard-share/scripts/upload-snapshot.sh`
|
|
27
|
+
(`E47_S05_T01`, templated-path `rclone copyto` upload). Per this repo's "Scripts Over Inline Logic"
|
|
28
|
+
principle (`CLAUDE.md`), this `SKILL.md` introduces **no new capture or upload logic of its own** — it
|
|
29
|
+
only sequences those two scripts and relays their output, plus the minimal remote-selection judgment
|
|
30
|
+
call described in step 2 below (interpreting `rclone listremotes` output and presenting a choice to
|
|
31
|
+
the user, not reimplementing any config/upload behavior).
|
|
32
|
+
|
|
33
|
+
This is **upload only** — this skill never runs `rclone link` or any other share-link-creating
|
|
34
|
+
command, matching the story's explicit, deliberate scope decision (see `E47_S05`'s Purpose section in
|
|
35
|
+
`PROJECT_SUMMARY.md`): upload and link-creation are two distinct actions with a real permission
|
|
36
|
+
consequence, and creating a public link stays a separate, manual, deliberate step the user takes on
|
|
37
|
+
their own, never something this skill chains automatically.
|
|
38
|
+
|
|
39
|
+
## Instructions
|
|
40
|
+
|
|
41
|
+
1. **Capture the snapshot.** Invoke:
|
|
42
|
+
|
|
43
|
+
```
|
|
44
|
+
bash skills/j-dashboard/scripts/snapshot.sh [--out <path>]
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
Forward `--out <path>` only if the user explicitly requested a specific local output path;
|
|
48
|
+
otherwise let it default. Relay the script's own stdout/stderr as-is if it fails — do not
|
|
49
|
+
reinterpret its error output. On success, read the local snapshot file's path from its documented
|
|
50
|
+
`Snapshot dashboard written to: <path>` line (the exact line `snapshot.sh` emits on completion) —
|
|
51
|
+
do not guess or re-derive the path any other way. If this step fails (non-zero exit), stop here;
|
|
52
|
+
do not proceed to step 2 or 3.
|
|
53
|
+
|
|
54
|
+
2. **Determine which remote to upload to.** `upload-snapshot.sh` (step 3) requires an explicit
|
|
55
|
+
`--remote <name>` naming an already-configured rclone remote — it does not pick one on its own.
|
|
56
|
+
Resolve this before invoking it:
|
|
57
|
+
- If the user's request already names a specific remote (e.g. "upload it to my gdrive remote"),
|
|
58
|
+
use that name directly and skip straight to step 3.
|
|
59
|
+
- Otherwise, run `rclone listremotes` to see what's currently configured (a plain read-only
|
|
60
|
+
enumeration — not upload or config logic, so it stays within this repo's inline-judgment
|
|
61
|
+
carve-out for interpreting output and presenting results to the user):
|
|
62
|
+
- **Zero remotes configured:** tell the user to run `j.cloud-connect` first to configure one,
|
|
63
|
+
and stop here — do not invoke `upload-snapshot.sh` at all in this case (it would only
|
|
64
|
+
reproduce the same "not configured" message after the fact).
|
|
65
|
+
- **Exactly one remote configured:** use it automatically, no prompt needed.
|
|
66
|
+
- **More than one remote configured:** ask the user which one to use, following CLAUDE.md's
|
|
67
|
+
standard Interaction Pattern (numbered list of the configured remote names, "Other" as the
|
|
68
|
+
final option).
|
|
69
|
+
|
|
70
|
+
3. **Upload the snapshot.** Invoke:
|
|
71
|
+
|
|
72
|
+
```
|
|
73
|
+
bash skills/j-dashboard-share/scripts/upload-snapshot.sh --file <path-from-step-1> --remote <name-from-step-2>
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
Relay its stdout/stderr to the user as-is, including the final printed destination path on
|
|
77
|
+
success (`JengaAI/<repo-directory-name>/<datetime>-board-snapshot.html` on the chosen remote) or
|
|
78
|
+
its own actionable error message on failure (e.g. it independently re-checks the remote is
|
|
79
|
+
configured and points at `j.cloud-connect` if not, in case that state changed between step 2's
|
|
80
|
+
enumeration and this call). Do not reinterpret, summarize away, or suppress its output.
|
|
81
|
+
|
|
82
|
+
## Out of Scope
|
|
83
|
+
|
|
84
|
+
- Any new dashboard capture, HTML bundling, or single-file export logic — that is entirely
|
|
85
|
+
`skills/j-dashboard/scripts/snapshot.sh`'s scope (`E47_S04`). Do not duplicate or reimplement any
|
|
86
|
+
part of it here.
|
|
87
|
+
- Any new `rclone copyto` invocation, destination-path templating, or "remote not configured"
|
|
88
|
+
detection logic — that is entirely `skills/j-dashboard-share/scripts/upload-snapshot.sh`'s scope
|
|
89
|
+
(`E47_S05_T01`). This `SKILL.md` only decides *which* configured remote name to pass to it (step 2
|
|
90
|
+
above); it never re-derives the destination path or re-implements the configured-remote check.
|
|
91
|
+
- Running `rclone link` or any other share-link-creating command, automatically or on request routed
|
|
92
|
+
through this skill — that is a deliberate, permanent scope exclusion for `E47_S05` (see Purpose
|
|
93
|
+
above), not a gap to close later.
|
|
94
|
+
- `rclone` installation, backend configuration, or OAuth authentication — that is entirely
|
|
95
|
+
`j.cloud-connect`'s scope (`E60_S01`). This skill only points the user at it when no remote is
|
|
96
|
+
configured; it does not run any part of that flow itself.
|