@etiquekit/etq 0.0.1 → 1.0.4

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 (83) hide show
  1. package/AGENTS.md +28 -0
  2. package/QuickStart.md +86 -0
  3. package/README.md +77 -9
  4. package/bin/etiquette +1150 -0
  5. package/bin/etiquette-core +313 -0
  6. package/docs/ARCHITECTURE.md +100 -0
  7. package/docs/CODEX_CLIENT_COMPATIBILITY.md +83 -0
  8. package/docs/CONCEPTS.md +128 -0
  9. package/docs/CONCEPT_STATUS.md +67 -0
  10. package/docs/CORE_PROFILE.md +70 -0
  11. package/docs/LANE_PROVISIONING.md +157 -0
  12. package/docs/README.md +29 -0
  13. package/docs/RELEASE_SURFACE_AUDIT.md +156 -0
  14. package/docs/SEAT_DISCIPLINE.md +116 -0
  15. package/docs/SEAT_PROVISIONING.md +100 -0
  16. package/docs/TEAM_HANDOFF.md +190 -0
  17. package/docs/WORKTREE_QOL.md +92 -0
  18. package/docs/contracts/ledger-entry/README.md +83 -0
  19. package/docs/contracts/ledger-entry/ledger-entry.v0.1.md +116 -0
  20. package/docs/contracts/ledger-entry/ledger-entry.v0.2.md +90 -0
  21. package/docs/contracts/ledger-entry/ledger-entry.v0.md +123 -0
  22. package/docs/contracts/ledger-entry/ledger-query-cli.v0.1.md +64 -0
  23. package/package.json +42 -11
  24. package/scripts/install.sh +144 -0
  25. package/scripts/uninstall.sh +58 -0
  26. package/templates/etiquette-vanilla-v0/README.md +69 -0
  27. package/templates/etiquette-vanilla-v0/bin/stamp-vanilla +33 -0
  28. package/templates/etiquette-vanilla-v0/source/control-seat/.llm-substrate/HANDOFF.md +22 -0
  29. package/templates/etiquette-vanilla-v0/source/control-seat/README.md +16 -0
  30. package/templates/etiquette-vanilla-v0/source/control-seat/bin/_lib/http.sh +121 -0
  31. package/templates/etiquette-vanilla-v0/source/control-seat/bin/_lib/sanitize.sh +191 -0
  32. package/templates/etiquette-vanilla-v0/source/control-seat/bin/_lib/state.sh +67 -0
  33. package/templates/etiquette-vanilla-v0/source/control-seat/bin/access-assurance-check +55 -0
  34. package/templates/etiquette-vanilla-v0/source/control-seat/bin/compact-local-events +71 -0
  35. package/templates/etiquette-vanilla-v0/source/control-seat/bin/hooks/post-tool-use +64 -0
  36. package/templates/etiquette-vanilla-v0/source/control-seat/bin/hooks/session-end +64 -0
  37. package/templates/etiquette-vanilla-v0/source/control-seat/bin/hooks/session-start +64 -0
  38. package/templates/etiquette-vanilla-v0/source/control-seat/bin/hooks/stop +64 -0
  39. package/templates/etiquette-vanilla-v0/source/control-seat/bin/hooks/user-prompt-submit +64 -0
  40. package/templates/etiquette-vanilla-v0/source/control-seat/bin/local-event +53 -0
  41. package/templates/etiquette-vanilla-v0/source/control-seat/bin/post-receipt +143 -0
  42. package/templates/etiquette-vanilla-v0/source/control-seat/bin/post-telemetry +112 -0
  43. package/templates/etiquette-vanilla-v0/source/control-seat/bin/seat-consume-pass +115 -0
  44. package/templates/etiquette-vanilla-v0/source/control-seat/bin/seat-doctor +20 -0
  45. package/templates/etiquette-vanilla-v0/source/control-seat/bin/seat-init +86 -0
  46. package/templates/etiquette-vanilla-v0/source/control-seat/bin/seat-post-readiness +171 -0
  47. package/templates/etiquette-vanilla-v0/source/control-seat/bin/session-init +132 -0
  48. package/templates/etiquette-vanilla-v0/source/control-seat/docs/work/access/ACCESS-ASSURANCE.md +39 -0
  49. package/templates/etiquette-vanilla-v0/source/control-seat/docs/work/access/workspace-secure-profile.v0.json +53 -0
  50. package/templates/etiquette-vanilla-v0/source/control-seat/docs/work/ledger/LEDGER.md +7 -0
  51. package/templates/etiquette-vanilla-v0/source/control-seat/docs/work/playbacks/PLAYBACK_TEMPLATE.md +31 -0
  52. package/templates/etiquette-vanilla-v0/source/control-seat/docs/work/receipts/.gitkeep +1 -0
  53. package/templates/etiquette-vanilla-v0/source/control-seat/docs/work/tasks/CONTROL-001.yaml +13 -0
  54. package/templates/etiquette-vanilla-v0/source/etiquette-kernel/README.md +6 -0
  55. package/templates/etiquette-vanilla-v0/source/etiquette-kernel/bin/kernel-doctor +11 -0
  56. package/templates/etiquette-vanilla-v0/source/etiquette-kernel/contracts/playback.v0.md +37 -0
  57. package/templates/etiquette-vanilla-v0/source/etiquette-kernel/contracts/receipt.v0.md +25 -0
  58. package/templates/etiquette-vanilla-v0/source/etiquette-kernel/contracts/seat-readiness.v0.md +22 -0
  59. package/templates/etiquette-vanilla-v0/source/etiquette-kernel/contracts/task.v0.md +18 -0
  60. package/templates/etiquette-vanilla-v0/validate-vanilla.sh +43 -0
  61. package/templates/github-actions/README.md +30 -0
  62. package/templates/github-actions/w1-webhook-wake-canary.yml +111 -0
  63. package/templates/hosted-receiver/README.md +41 -0
  64. package/templates/hosted-receiver/w1-github-webhook-receiver.mjs +129 -0
  65. package/templates/seat-packs-v0/README.md +72 -0
  66. package/templates/seat-packs-v0/bin/provision-seat +70 -0
  67. package/templates/seat-packs-v0/source/claude-code-seat/.llm-substrate/HANDOFF.md +24 -0
  68. package/templates/seat-packs-v0/source/claude-code-seat/README.md +19 -0
  69. package/templates/seat-packs-v0/source/claude-code-seat/bin/seat-doctor +8 -0
  70. package/templates/seat-packs-v0/source/codex-seat/.llm-substrate/HANDOFF.md +25 -0
  71. package/templates/seat-packs-v0/source/codex-seat/README.md +22 -0
  72. package/templates/seat-packs-v0/source/codex-seat/bin/seat-doctor +8 -0
  73. package/templates/seat-packs-v0/source/gemini-seat/.llm-substrate/HANDOFF.md +23 -0
  74. package/templates/seat-packs-v0/source/gemini-seat/README.md +19 -0
  75. package/templates/seat-packs-v0/source/gemini-seat/bin/seat-doctor +8 -0
  76. package/templates/seat-packs-v0/source/ollama-seat/.llm-substrate/HANDOFF.md +24 -0
  77. package/templates/seat-packs-v0/source/ollama-seat/README.md +19 -0
  78. package/templates/seat-packs-v0/source/ollama-seat/bin/seat-doctor +8 -0
  79. package/templates/seat-packs-v0/source/openrouter-seat/.llm-substrate/HANDOFF.md +24 -0
  80. package/templates/seat-packs-v0/source/openrouter-seat/README.md +19 -0
  81. package/templates/seat-packs-v0/source/openrouter-seat/bin/seat-doctor +8 -0
  82. package/templates/seat-packs-v0/validate-seat-packs.sh +27 -0
  83. package/bin/etq +0 -4
@@ -0,0 +1,71 @@
1
+ #!/bin/sh
2
+ set -eu
3
+
4
+ ROOT=$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd)
5
+
6
+ if [ "$#" -lt 1 ] || [ "$#" -gt 2 ]; then
7
+ echo "usage: sh bin/compact-local-events <task-id> [success|partial|blocked|failed]" >&2
8
+ exit 2
9
+ fi
10
+
11
+ task_id=$1
12
+ status=${2:-success}
13
+ operator_principal=${ETIQUETTE_OPERATOR_PRINCIPAL:-local-operator}
14
+ execution_seat=${ETIQUETTE_EXECUTION_SEAT:-control-seat}
15
+ event_log=${ETIQUETTE_EVENT_LOG:-"$ROOT/.etiquette/events/local.ndjson"}
16
+ receipt_dir=${ETIQUETTE_RECEIPT_DIR:-"$ROOT/docs/work/receipts"}
17
+
18
+ case "$status" in
19
+ success|partial|blocked|failed) ;;
20
+ *)
21
+ echo "invalid receipt status: $status" >&2
22
+ exit 2
23
+ ;;
24
+ esac
25
+
26
+ if [ ! -f "$event_log" ]; then
27
+ echo "missing local event log: $event_log" >&2
28
+ exit 1
29
+ fi
30
+
31
+ task_pattern='"task_id":"'"$task_id"'"'
32
+ matches=$(grep -F "$task_pattern" "$event_log" || true)
33
+
34
+ if [ -z "$matches" ]; then
35
+ echo "no events found for task: $task_id" >&2
36
+ exit 1
37
+ fi
38
+
39
+ event_count=$(printf '%s\n' "$matches" | sed '/^$/d' | wc -l | tr -d ' ')
40
+ first_seq=$(printf '%s\n' "$matches" | sed -n '1s/.*"seq":\([0-9][0-9]*\).*/\1/p')
41
+ last_seq=$(printf '%s\n' "$matches" | sed -n '$s/.*"seq":\([0-9][0-9]*\).*/\1/p')
42
+ last_action=$(printf '%s\n' "$matches" | sed -n '$s/.*"action":"\([^"]*\)".*/\1/p')
43
+
44
+ mkdir -p "$receipt_dir"
45
+ receipt="$receipt_dir/$task_id-receipt.yaml"
46
+ tmp="$receipt.tmp.$$"
47
+
48
+ cat > "$tmp" <<EOF
49
+ task_id: $task_id
50
+ operator_principal: $operator_principal
51
+ execution_seat: $execution_seat
52
+ status: $status
53
+ sources_read:
54
+ - .etiquette/events/local.ndjson
55
+ files_changed:
56
+ - docs/work/receipts/$task_id-receipt.yaml
57
+ validation:
58
+ - sh bin/seat-doctor
59
+ risks: []
60
+ next_owner: control-seat
61
+ compaction:
62
+ scope: local-only
63
+ event_log: .etiquette/events/local.ndjson
64
+ event_count: $event_count
65
+ first_seq: $first_seq
66
+ last_seq: $last_seq
67
+ last_action: $last_action
68
+ EOF
69
+
70
+ mv "$tmp" "$receipt"
71
+ printf 'receipt: %s\n' "$receipt"
@@ -0,0 +1,64 @@
1
+ #!/bin/sh
2
+ # bin/hooks/post-tool-use — Etiquette Hook Adapter v0.1
3
+ # Maps runtime lifecycle event to etiquette `hook post_tool_use`.
4
+ #
5
+ # DISABLED BY DEFAULT. Enable with:
6
+ # export ETIQUETTE_HOOKS_ENABLED=1
7
+ # export ETIQUETTE_WORKSPACE_ID=<opaque-workspace-id>
8
+ # export ETIQUETTE_CLI_SCRIPT=/path/to/etiquette/packages/control/src/cli.ts
9
+ #
10
+ # Optional input:
11
+ # stdin or $ETIQUETTE_HOOK_PAYLOAD = JSON object payload (sanitized server-side)
12
+ #
13
+ # Contract:
14
+ # - disabled-by-default: silent exit 0 when not opted in
15
+ # - fast-fail: any error is swallowed; never blocks the operator
16
+ # - non-authoritative: writes per-workspace outbox only; no ledger writes
17
+ set -eu
18
+
19
+ if [ "${ETIQUETTE_HOOKS_ENABLED:-0}" != "1" ]; then
20
+ exit 0
21
+ fi
22
+
23
+ CLI_BIN=${ETIQUETTE_CLI_BIN:-bun}
24
+ CLI_SCRIPT=${ETIQUETTE_CLI_SCRIPT:-}
25
+
26
+ if [ -z "$CLI_SCRIPT" ] || [ ! -f "$CLI_SCRIPT" ]; then
27
+ # hook enabled but no cli script located; non-blocking exit
28
+ exit 0
29
+ fi
30
+
31
+ # Payload via env var (preferred) or stdin
32
+ if [ -n "${ETIQUETTE_HOOK_PAYLOAD:-}" ]; then
33
+ payload=$ETIQUETTE_HOOK_PAYLOAD
34
+ elif [ ! -t 0 ]; then
35
+ payload=$(cat)
36
+ else
37
+ payload='{}'
38
+ fi
39
+
40
+ if [ -n "${ETIQUETTE_WORKSPACE_ID:-}" ]; then
41
+ set -- hook post_tool_use --workspace "$ETIQUETTE_WORKSPACE_ID" --payload "$payload"
42
+ else
43
+ set -- hook post_tool_use --payload "$payload"
44
+ fi
45
+
46
+ timeout_seconds=${ETIQUETTE_HOOK_TIMEOUT_SECONDS:-2}
47
+ case "$timeout_seconds" in
48
+ ''|*[!0-9]*) timeout_seconds=2 ;;
49
+ esac
50
+
51
+ # Run with timeout safety. Output goes to /dev/null so a hook never pollutes the
52
+ # runtime's stdout, and timeout/failure never blocks the operator.
53
+ (
54
+ "$CLI_BIN" "$CLI_SCRIPT" "$@" >/dev/null 2>&1
55
+ ) &
56
+ hook_pid=$!
57
+ (
58
+ sleep "$timeout_seconds"
59
+ kill "$hook_pid" 2>/dev/null || true
60
+ ) &
61
+ watchdog_pid=$!
62
+ wait "$hook_pid" 2>/dev/null || true
63
+ kill "$watchdog_pid" 2>/dev/null || true
64
+ exit 0
@@ -0,0 +1,64 @@
1
+ #!/bin/sh
2
+ # bin/hooks/session-end — Etiquette Hook Adapter v0.1
3
+ # Maps runtime lifecycle event to etiquette `hook session_end`.
4
+ #
5
+ # DISABLED BY DEFAULT. Enable with:
6
+ # export ETIQUETTE_HOOKS_ENABLED=1
7
+ # export ETIQUETTE_WORKSPACE_ID=<opaque-workspace-id>
8
+ # export ETIQUETTE_CLI_SCRIPT=/path/to/etiquette/packages/control/src/cli.ts
9
+ #
10
+ # Optional input:
11
+ # stdin or $ETIQUETTE_HOOK_PAYLOAD = JSON object payload (sanitized server-side)
12
+ #
13
+ # Contract:
14
+ # - disabled-by-default: silent exit 0 when not opted in
15
+ # - fast-fail: any error is swallowed; never blocks the operator
16
+ # - non-authoritative: writes per-workspace outbox only; no ledger writes
17
+ set -eu
18
+
19
+ if [ "${ETIQUETTE_HOOKS_ENABLED:-0}" != "1" ]; then
20
+ exit 0
21
+ fi
22
+
23
+ CLI_BIN=${ETIQUETTE_CLI_BIN:-bun}
24
+ CLI_SCRIPT=${ETIQUETTE_CLI_SCRIPT:-}
25
+
26
+ if [ -z "$CLI_SCRIPT" ] || [ ! -f "$CLI_SCRIPT" ]; then
27
+ # hook enabled but no cli script located; non-blocking exit
28
+ exit 0
29
+ fi
30
+
31
+ # Payload via env var (preferred) or stdin
32
+ if [ -n "${ETIQUETTE_HOOK_PAYLOAD:-}" ]; then
33
+ payload=$ETIQUETTE_HOOK_PAYLOAD
34
+ elif [ ! -t 0 ]; then
35
+ payload=$(cat)
36
+ else
37
+ payload='{}'
38
+ fi
39
+
40
+ if [ -n "${ETIQUETTE_WORKSPACE_ID:-}" ]; then
41
+ set -- hook session_end --workspace "$ETIQUETTE_WORKSPACE_ID" --payload "$payload"
42
+ else
43
+ set -- hook session_end --payload "$payload"
44
+ fi
45
+
46
+ timeout_seconds=${ETIQUETTE_HOOK_TIMEOUT_SECONDS:-2}
47
+ case "$timeout_seconds" in
48
+ ''|*[!0-9]*) timeout_seconds=2 ;;
49
+ esac
50
+
51
+ # Run with timeout safety. Output goes to /dev/null so a hook never pollutes the
52
+ # runtime's stdout, and timeout/failure never blocks the operator.
53
+ (
54
+ "$CLI_BIN" "$CLI_SCRIPT" "$@" >/dev/null 2>&1
55
+ ) &
56
+ hook_pid=$!
57
+ (
58
+ sleep "$timeout_seconds"
59
+ kill "$hook_pid" 2>/dev/null || true
60
+ ) &
61
+ watchdog_pid=$!
62
+ wait "$hook_pid" 2>/dev/null || true
63
+ kill "$watchdog_pid" 2>/dev/null || true
64
+ exit 0
@@ -0,0 +1,64 @@
1
+ #!/bin/sh
2
+ # bin/hooks/session-start — Etiquette Hook Adapter v0.1
3
+ # Maps runtime lifecycle event to etiquette `hook session_start`.
4
+ #
5
+ # DISABLED BY DEFAULT. Enable with:
6
+ # export ETIQUETTE_HOOKS_ENABLED=1
7
+ # export ETIQUETTE_WORKSPACE_ID=<opaque-workspace-id>
8
+ # export ETIQUETTE_CLI_SCRIPT=/path/to/etiquette/packages/control/src/cli.ts
9
+ #
10
+ # Optional input:
11
+ # stdin or $ETIQUETTE_HOOK_PAYLOAD = JSON object payload (sanitized server-side)
12
+ #
13
+ # Contract:
14
+ # - disabled-by-default: silent exit 0 when not opted in
15
+ # - fast-fail: any error is swallowed; never blocks the operator
16
+ # - non-authoritative: writes per-workspace outbox only; no ledger writes
17
+ set -eu
18
+
19
+ if [ "${ETIQUETTE_HOOKS_ENABLED:-0}" != "1" ]; then
20
+ exit 0
21
+ fi
22
+
23
+ CLI_BIN=${ETIQUETTE_CLI_BIN:-bun}
24
+ CLI_SCRIPT=${ETIQUETTE_CLI_SCRIPT:-}
25
+
26
+ if [ -z "$CLI_SCRIPT" ] || [ ! -f "$CLI_SCRIPT" ]; then
27
+ # hook enabled but no cli script located; non-blocking exit
28
+ exit 0
29
+ fi
30
+
31
+ # Payload via env var (preferred) or stdin
32
+ if [ -n "${ETIQUETTE_HOOK_PAYLOAD:-}" ]; then
33
+ payload=$ETIQUETTE_HOOK_PAYLOAD
34
+ elif [ ! -t 0 ]; then
35
+ payload=$(cat)
36
+ else
37
+ payload='{}'
38
+ fi
39
+
40
+ if [ -n "${ETIQUETTE_WORKSPACE_ID:-}" ]; then
41
+ set -- hook session_start --workspace "$ETIQUETTE_WORKSPACE_ID" --payload "$payload"
42
+ else
43
+ set -- hook session_start --payload "$payload"
44
+ fi
45
+
46
+ timeout_seconds=${ETIQUETTE_HOOK_TIMEOUT_SECONDS:-2}
47
+ case "$timeout_seconds" in
48
+ ''|*[!0-9]*) timeout_seconds=2 ;;
49
+ esac
50
+
51
+ # Run with timeout safety. Output goes to /dev/null so a hook never pollutes the
52
+ # runtime's stdout, and timeout/failure never blocks the operator.
53
+ (
54
+ "$CLI_BIN" "$CLI_SCRIPT" "$@" >/dev/null 2>&1
55
+ ) &
56
+ hook_pid=$!
57
+ (
58
+ sleep "$timeout_seconds"
59
+ kill "$hook_pid" 2>/dev/null || true
60
+ ) &
61
+ watchdog_pid=$!
62
+ wait "$hook_pid" 2>/dev/null || true
63
+ kill "$watchdog_pid" 2>/dev/null || true
64
+ exit 0
@@ -0,0 +1,64 @@
1
+ #!/bin/sh
2
+ # bin/hooks/stop — Etiquette Hook Adapter v0.1
3
+ # Maps runtime lifecycle event to etiquette `hook stop`.
4
+ #
5
+ # DISABLED BY DEFAULT. Enable with:
6
+ # export ETIQUETTE_HOOKS_ENABLED=1
7
+ # export ETIQUETTE_WORKSPACE_ID=<opaque-workspace-id>
8
+ # export ETIQUETTE_CLI_SCRIPT=/path/to/etiquette/packages/control/src/cli.ts
9
+ #
10
+ # Optional input:
11
+ # stdin or $ETIQUETTE_HOOK_PAYLOAD = JSON object payload (sanitized server-side)
12
+ #
13
+ # Contract:
14
+ # - disabled-by-default: silent exit 0 when not opted in
15
+ # - fast-fail: any error is swallowed; never blocks the operator
16
+ # - non-authoritative: writes per-workspace outbox only; no ledger writes
17
+ set -eu
18
+
19
+ if [ "${ETIQUETTE_HOOKS_ENABLED:-0}" != "1" ]; then
20
+ exit 0
21
+ fi
22
+
23
+ CLI_BIN=${ETIQUETTE_CLI_BIN:-bun}
24
+ CLI_SCRIPT=${ETIQUETTE_CLI_SCRIPT:-}
25
+
26
+ if [ -z "$CLI_SCRIPT" ] || [ ! -f "$CLI_SCRIPT" ]; then
27
+ # hook enabled but no cli script located; non-blocking exit
28
+ exit 0
29
+ fi
30
+
31
+ # Payload via env var (preferred) or stdin
32
+ if [ -n "${ETIQUETTE_HOOK_PAYLOAD:-}" ]; then
33
+ payload=$ETIQUETTE_HOOK_PAYLOAD
34
+ elif [ ! -t 0 ]; then
35
+ payload=$(cat)
36
+ else
37
+ payload='{}'
38
+ fi
39
+
40
+ if [ -n "${ETIQUETTE_WORKSPACE_ID:-}" ]; then
41
+ set -- hook stop --workspace "$ETIQUETTE_WORKSPACE_ID" --payload "$payload"
42
+ else
43
+ set -- hook stop --payload "$payload"
44
+ fi
45
+
46
+ timeout_seconds=${ETIQUETTE_HOOK_TIMEOUT_SECONDS:-2}
47
+ case "$timeout_seconds" in
48
+ ''|*[!0-9]*) timeout_seconds=2 ;;
49
+ esac
50
+
51
+ # Run with timeout safety. Output goes to /dev/null so a hook never pollutes the
52
+ # runtime's stdout, and timeout/failure never blocks the operator.
53
+ (
54
+ "$CLI_BIN" "$CLI_SCRIPT" "$@" >/dev/null 2>&1
55
+ ) &
56
+ hook_pid=$!
57
+ (
58
+ sleep "$timeout_seconds"
59
+ kill "$hook_pid" 2>/dev/null || true
60
+ ) &
61
+ watchdog_pid=$!
62
+ wait "$hook_pid" 2>/dev/null || true
63
+ kill "$watchdog_pid" 2>/dev/null || true
64
+ exit 0
@@ -0,0 +1,64 @@
1
+ #!/bin/sh
2
+ # bin/hooks/user-prompt-submit — Etiquette Hook Adapter v0.1
3
+ # Maps runtime lifecycle event to etiquette `hook user_prompt_submit`.
4
+ #
5
+ # DISABLED BY DEFAULT. Enable with:
6
+ # export ETIQUETTE_HOOKS_ENABLED=1
7
+ # export ETIQUETTE_WORKSPACE_ID=<opaque-workspace-id>
8
+ # export ETIQUETTE_CLI_SCRIPT=/path/to/etiquette/packages/control/src/cli.ts
9
+ #
10
+ # Optional input:
11
+ # stdin or $ETIQUETTE_HOOK_PAYLOAD = JSON object payload (sanitized server-side)
12
+ #
13
+ # Contract:
14
+ # - disabled-by-default: silent exit 0 when not opted in
15
+ # - fast-fail: any error is swallowed; never blocks the operator
16
+ # - non-authoritative: writes per-workspace outbox only; no ledger writes
17
+ set -eu
18
+
19
+ if [ "${ETIQUETTE_HOOKS_ENABLED:-0}" != "1" ]; then
20
+ exit 0
21
+ fi
22
+
23
+ CLI_BIN=${ETIQUETTE_CLI_BIN:-bun}
24
+ CLI_SCRIPT=${ETIQUETTE_CLI_SCRIPT:-}
25
+
26
+ if [ -z "$CLI_SCRIPT" ] || [ ! -f "$CLI_SCRIPT" ]; then
27
+ # hook enabled but no cli script located; non-blocking exit
28
+ exit 0
29
+ fi
30
+
31
+ # Payload via env var (preferred) or stdin
32
+ if [ -n "${ETIQUETTE_HOOK_PAYLOAD:-}" ]; then
33
+ payload=$ETIQUETTE_HOOK_PAYLOAD
34
+ elif [ ! -t 0 ]; then
35
+ payload=$(cat)
36
+ else
37
+ payload='{}'
38
+ fi
39
+
40
+ if [ -n "${ETIQUETTE_WORKSPACE_ID:-}" ]; then
41
+ set -- hook user_prompt_submit --workspace "$ETIQUETTE_WORKSPACE_ID" --payload "$payload"
42
+ else
43
+ set -- hook user_prompt_submit --payload "$payload"
44
+ fi
45
+
46
+ timeout_seconds=${ETIQUETTE_HOOK_TIMEOUT_SECONDS:-2}
47
+ case "$timeout_seconds" in
48
+ ''|*[!0-9]*) timeout_seconds=2 ;;
49
+ esac
50
+
51
+ # Run with timeout safety. Output goes to /dev/null so a hook never pollutes the
52
+ # runtime's stdout, and timeout/failure never blocks the operator.
53
+ (
54
+ "$CLI_BIN" "$CLI_SCRIPT" "$@" >/dev/null 2>&1
55
+ ) &
56
+ hook_pid=$!
57
+ (
58
+ sleep "$timeout_seconds"
59
+ kill "$hook_pid" 2>/dev/null || true
60
+ ) &
61
+ watchdog_pid=$!
62
+ wait "$hook_pid" 2>/dev/null || true
63
+ kill "$watchdog_pid" 2>/dev/null || true
64
+ exit 0
@@ -0,0 +1,53 @@
1
+ #!/bin/sh
2
+ set -eu
3
+
4
+ ROOT=$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd)
5
+
6
+ if [ "$#" -lt 4 ]; then
7
+ echo "usage: sh bin/local-event <task-id> <actor> <action> <message...>" >&2
8
+ exit 2
9
+ fi
10
+
11
+ task_id=$1
12
+ actor=$2
13
+ action=$3
14
+ shift 3
15
+ message=$*
16
+
17
+ event_log=${ETIQUETTE_EVENT_LOG:-"$ROOT/.etiquette/events/local.ndjson"}
18
+ event_dir=$(dirname -- "$event_log")
19
+ mkdir -p "$event_dir"
20
+
21
+ lock_dir="$event_dir/.local-event.lock"
22
+ lock_attempts=0
23
+ while ! mkdir "$lock_dir" 2>/dev/null; do
24
+ lock_attempts=$((lock_attempts + 1))
25
+ if [ "$lock_attempts" -ge 100 ]; then
26
+ echo "timed out waiting for local event lock: $lock_dir" >&2
27
+ exit 1
28
+ fi
29
+ sleep 0.05
30
+ done
31
+ trap 'rmdir "$lock_dir" 2>/dev/null || true' EXIT INT TERM
32
+
33
+ if [ -f "$event_log" ]; then
34
+ seq=$(wc -l < "$event_log" | tr -d ' ')
35
+ else
36
+ seq=0
37
+ fi
38
+ seq=$((seq + 1))
39
+ at=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
40
+
41
+ json_escape() {
42
+ printf '%s' "$1" | sed 's/\\/\\\\/g; s/"/\\"/g'
43
+ }
44
+
45
+ task_id_json=$(json_escape "$task_id")
46
+ actor_json=$(json_escape "$actor")
47
+ action_json=$(json_escape "$action")
48
+ message_json=$(json_escape "$message")
49
+
50
+ printf '{"seq":%s,"at":"%s","task_id":"%s","actor":"%s","action":"%s","message":"%s"}\n' \
51
+ "$seq" "$at" "$task_id_json" "$actor_json" "$action_json" "$message_json" >> "$event_log"
52
+
53
+ printf 'event: %s seq:%s\n' "$event_log" "$seq"
@@ -0,0 +1,143 @@
1
+ #!/bin/sh
2
+ # post-receipt: push a receipt METADATA pointer to the hosted plane.
3
+ #
4
+ # Per hosted-plane-boundary.v0 — content stays in the repo; only the
5
+ # pointer (task_id / seat_id / status / artifact_ref / receipt_ref /
6
+ # validation_summary capped) is sent. Sanitisers reject path/hostname/IP
7
+ # leakage in the refs.
8
+ #
9
+ # Usage:
10
+ # sh bin/post-receipt --task-id DEMO-001 --status success \
11
+ # --artifact-ref repo:main --receipt-ref docs/work/receipts/DEMO-001.md \
12
+ # --validation-summary "product-doctor: ok"
13
+ set -eu
14
+
15
+ ROOT=$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd)
16
+ # shellcheck source=_lib/sanitize.sh
17
+ . "$ROOT/bin/_lib/sanitize.sh"
18
+ # shellcheck source=_lib/state.sh
19
+ . "$ROOT/bin/_lib/state.sh"
20
+ # shellcheck source=_lib/http.sh
21
+ . "$ROOT/bin/_lib/http.sh"
22
+
23
+ ALLOWED_STATUSES="success partial blocked failed"
24
+
25
+ task_id=""
26
+ status=""
27
+ artifact_ref=""
28
+ receipt_ref=""
29
+ validation_summary=""
30
+
31
+ while [ "$#" -gt 0 ]; do
32
+ case "$1" in
33
+ --task-id) task_id=$2; shift 2 ;;
34
+ --status) status=$2; shift 2 ;;
35
+ --artifact-ref) artifact_ref=$2; shift 2 ;;
36
+ --receipt-ref) receipt_ref=$2; shift 2 ;;
37
+ --validation-summary) validation_summary=$2; shift 2 ;;
38
+ -h|--help)
39
+ cat <<EOF
40
+ post-receipt — push a receipt metadata pointer
41
+
42
+ sh bin/post-receipt --task-id <id> --status <s> \\
43
+ [--artifact-ref <ref>] [--receipt-ref <repo-relative-path>] \\
44
+ [--validation-summary <≤200 chars>]
45
+
46
+ Status (4):
47
+ $(printf ' - %s\n' $ALLOWED_STATUSES)
48
+
49
+ Requires seat-consume-pass to have populated workspace.json.
50
+ Only metadata flows over the wire; receipt body stays in your repo.
51
+ EOF
52
+ exit 0
53
+ ;;
54
+ *) echo "unknown arg: $1" >&2; exit 2 ;;
55
+ esac
56
+ done
57
+
58
+ [ -n "$task_id" ] || { echo "post-receipt: --task-id required" >&2; exit 2; }
59
+ [ -n "$status" ] || { echo "post-receipt: --status required" >&2; exit 2; }
60
+
61
+ # Status allowlist.
62
+ ok=0
63
+ for s in $ALLOWED_STATUSES; do
64
+ if [ "$s" = "$status" ]; then ok=1; break; fi
65
+ done
66
+ if [ "$ok" -ne 1 ]; then
67
+ printf 'post-receipt: --status "%s" not in allowlist:\n' "$status" >&2
68
+ printf ' - %s\n' $ALLOWED_STATUSES >&2
69
+ exit 2
70
+ fi
71
+
72
+ # task_id length cap (defence; api caps at 128).
73
+ if [ "${#task_id}" -gt 128 ]; then
74
+ echo "post-receipt: --task-id length > 128" >&2
75
+ exit 2
76
+ fi
77
+
78
+ # Sanitise optional refs.
79
+ clean_artifact=""
80
+ if [ -n "$artifact_ref" ]; then
81
+ if ! clean_artifact=$(etq_sanitize_artifact_ref "$artifact_ref"); then
82
+ echo "post-receipt: --artifact-ref rejected" >&2
83
+ exit 2
84
+ fi
85
+ fi
86
+ clean_receipt=""
87
+ if [ -n "$receipt_ref" ]; then
88
+ if ! clean_receipt=$(etq_sanitize_repo_relative "$receipt_ref"); then
89
+ echo "post-receipt: --receipt-ref rejected" >&2
90
+ exit 2
91
+ fi
92
+ fi
93
+ # Validation summary cap at 200 chars (server also enforces).
94
+ if [ "${#validation_summary}" -gt 200 ]; then
95
+ validation_summary=$(printf '%s' "$validation_summary" | cut -c1-200)
96
+ fi
97
+
98
+ # Load workspace.json.
99
+ workspace_file=$(etq_state_workspace_path)
100
+ if [ ! -f "$workspace_file" ]; then
101
+ echo "post-receipt: workspace.json missing — run \`sh bin/seat-consume-pass --pass-id <id>\` first" >&2
102
+ exit 1
103
+ fi
104
+ workspace_id=$(etq_state_read_key "$workspace_file" workspace_id) || exit 1
105
+ pass_id=$(etq_state_read_key "$workspace_file" pass_id) || exit 1
106
+ host=$(etq_state_read_key "$workspace_file" hosted_host) || exit 1
107
+ seat_id=$(etq_state_read_key "$(etq_state_seat_path)" seat_id) || exit 1
108
+ seat_id=$(etq_sanitize_opaque_id "$seat_id") || {
109
+ echo "post-receipt: seat.json contains invalid seat_id; re-run seat-init --force" >&2
110
+ exit 1
111
+ }
112
+
113
+ # Build body with jq for safe JSON construction.
114
+ if ! command -v jq >/dev/null 2>&1; then
115
+ echo "post-receipt: jq required" >&2
116
+ exit 1
117
+ fi
118
+ body=$(jq -nc \
119
+ --arg pass_id "$pass_id" \
120
+ --arg task_id "$task_id" \
121
+ --arg seat_id "$seat_id" \
122
+ --arg status "$status" \
123
+ --arg artifact_ref "$clean_artifact" \
124
+ --arg receipt_ref "$clean_receipt" \
125
+ --arg validation_summary "$validation_summary" \
126
+ '
127
+ {pass_id: $pass_id, task_id: $task_id, seat_id: $seat_id, status: $status}
128
+ + (if $artifact_ref != "" then {artifact_ref: $artifact_ref} else {} end)
129
+ + (if $receipt_ref != "" then {receipt_ref: $receipt_ref} else {} end)
130
+ + (if $validation_summary != "" then {validation_summary: $validation_summary} else {} end)
131
+ ')
132
+
133
+ url="$host/workspaces/$workspace_id/receipts"
134
+ printf 'post-receipt: POST %s task=%s status=%s\n' "$url" "$task_id" "$status"
135
+
136
+ response=$(etq_http_post "$url" "$body") || {
137
+ rc=$?
138
+ echo "post-receipt: ingest failed (http rc=$rc)" >&2
139
+ exit "$rc"
140
+ }
141
+
142
+ printf 'post-receipt: ingested\n'
143
+ printf '%s\n' "$response" | jq -c . 2>/dev/null || printf '%s\n' "$response"