@jenga-ai/agent 3.2.0 → 3.5.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.
Files changed (68) hide show
  1. package/README.md +52 -12
  2. package/agents/developer.md +16 -1
  3. package/agents/scrum-master.md +1 -0
  4. package/bin/jenga.js +10 -0
  5. package/lib/commands/dashboard.js +92 -0
  6. package/lib/skill-allow-list.json +6 -2
  7. package/package.json +21 -2
  8. package/project/app/api/lib/resolve-project-root.js +120 -0
  9. package/project/app/api/package.json +16 -0
  10. package/project/app/api/parsers/architecture.js +72 -0
  11. package/project/app/api/parsers/board.js +141 -0
  12. package/project/app/api/parsers/documentation.js +125 -0
  13. package/project/app/api/parsers/git-log.js +52 -0
  14. package/project/app/api/parsers/ideas.js +62 -0
  15. package/project/app/api/parsers/knowledge-graph.js +73 -0
  16. package/project/app/api/parsers/lib/markdown-dir-reader.js +163 -0
  17. package/project/app/api/parsers/rapports.js +148 -0
  18. package/project/app/api/parsers/todo.js +179 -0
  19. package/project/app/api/response.js +47 -0
  20. package/project/app/api/routes/architecture.js +23 -0
  21. package/project/app/api/routes/board.js +46 -0
  22. package/project/app/api/routes/documentation.js +24 -0
  23. package/project/app/api/routes/health.js +25 -0
  24. package/project/app/api/routes/history.js +55 -0
  25. package/project/app/api/routes/rapports.js +24 -0
  26. package/project/app/api/scripts/capture-snapshot.js +294 -0
  27. package/project/app/api/server.js +112 -0
  28. package/project/app/api/types.js +40 -0
  29. package/project/app/package.json +21 -0
  30. package/project/app/ui/dist/assets/index-7fj-vllY.js +104 -0
  31. package/project/app/ui/dist/assets/index-CdK3Qrep.css +1 -0
  32. package/project/app/ui/dist/index.html +13 -0
  33. package/project/app/ui/package.json +23 -0
  34. package/project/app/ui/scripts/build-snapshot-html.cjs +214 -0
  35. package/project/app/ui/scripts/dashboard-open.cjs +88 -0
  36. package/project/app/ui/scripts/dashboard-start.cjs +87 -0
  37. package/scripts/acquire-concurrency-slot.sh +220 -0
  38. package/scripts/compute-deploy-reconcile.sh +439 -0
  39. package/scripts/jenga-permission-level-switch.sh +19 -3
  40. package/scripts/mark-deployed.sh +532 -0
  41. package/scripts/populate-knowledge-graph.js +429 -0
  42. package/scripts/release-concurrency-slot.sh +129 -0
  43. package/scripts/validate-board.sh +60 -2
  44. package/scripts/verify-consumer-install.sh +470 -0
  45. package/skills/j-cloud-connect/SKILL.md +95 -0
  46. package/skills/j-cloud-connect/scripts/configure-backend.sh +267 -0
  47. package/skills/j-cloud-connect/scripts/install-rclone.sh +153 -0
  48. package/skills/j-dashboard/SKILL.md +144 -0
  49. package/skills/j-dashboard/scripts/launch.sh +121 -0
  50. package/skills/j-dashboard/scripts/resolve-app-dir.sh +164 -0
  51. package/skills/j-dashboard/scripts/snapshot.sh +267 -0
  52. package/skills/j-dashboard-share/SKILL.md +96 -0
  53. package/skills/j-dashboard-share/scripts/upload-snapshot.sh +173 -0
  54. package/skills/j-init/SKILL.md +52 -13
  55. package/skills/j-init/assets/.gitignore_template +1 -2
  56. package/skills/j-init/scripts/apply-scaffold-visibility.sh +192 -0
  57. package/skills/j-init/scripts/init.sh +19 -5
  58. package/skills/j-playbook/SKILL.md +12 -0
  59. package/skills/j-playbook-new/SKILL.md +155 -0
  60. package/skills/j-playbook-new/scripts/playbook-new.sh +332 -0
  61. package/skills/j-publish/scripts/npm_ci_pipeline.sh +6 -0
  62. package/skills/j-skillify/assets/init-new/assets/.gitignore_template +1 -2
  63. package/skills/j-uncharted/SKILL.md +54 -7
  64. package/skills/j-uncharted/assets/NODE_QUESTION_TEMPLATE.md +69 -0
  65. package/skills/j-uncharted/scripts/elicitation-state.sh +45 -7
  66. package/skills/jenga/scripts/load-nl-catalog.js +22 -6
  67. package/skills/jenga/scripts/load-playbooks.sh +146 -35
  68. 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.