@mirnoorata/codexa 0.2.2 → 0.3.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 +93 -29
- package/dist/cli/hooks.js +11 -6
- package/dist/cli/hooks.js.map +1 -1
- package/dist/cli.js +13 -4
- package/dist/cli.js.map +1 -1
- package/dist/implicit-baseline.d.ts +8 -0
- package/dist/implicit-baseline.js +94 -0
- package/dist/implicit-baseline.js.map +1 -0
- package/dist/init.d.ts +3 -0
- package/dist/init.js +124 -15
- package/dist/init.js.map +1 -1
- package/dist/mcp/compaction.d.ts +1 -0
- package/dist/mcp/compaction.js +24 -0
- package/dist/mcp/compaction.js.map +1 -1
- package/dist/mcp/envelope.d.ts +4 -1
- package/dist/mcp/envelope.js +45 -5
- package/dist/mcp/envelope.js.map +1 -1
- package/dist/mcp/prompts.d.ts +1 -1
- package/dist/mcp/prompts.js +5 -2
- package/dist/mcp/prompts.js.map +1 -1
- package/dist/mcp/tool-registry.d.ts +1 -0
- package/dist/mcp/tool-registry.js +5 -0
- package/dist/mcp/tool-registry.js.map +1 -1
- package/dist/mcp/tools.d.ts +1 -0
- package/dist/mcp/tools.js +6 -0
- package/dist/mcp/tools.js.map +1 -1
- package/dist/mcp-tool-catalog.d.ts +1 -1
- package/dist/mcp-tool-catalog.js +1 -1
- package/dist/mcp-tool-catalog.js.map +1 -1
- package/dist/mcp.js +10 -5
- package/dist/mcp.js.map +1 -1
- package/dist/query/post-edit/decision.d.ts +1 -0
- package/dist/query/post-edit/decision.js +13 -4
- package/dist/query/post-edit/decision.js.map +1 -1
- package/dist/query/post-edit.js +10 -2
- package/dist/query/post-edit.js.map +1 -1
- package/dist/task-snapshots.js +29 -0
- package/dist/task-snapshots.js.map +1 -1
- package/dist/types.d.ts +2 -0
- package/dist/types.js.map +1 -1
- package/integrations/.claude-plugin/marketplace.json +23 -0
- package/integrations/claude-code/.claude-plugin/plugin.json +16 -0
- package/integrations/claude-code/.mcp.json +8 -0
- package/integrations/claude-code/README.md +177 -0
- package/integrations/claude-code/commands/codexa-brief.md +14 -0
- package/integrations/claude-code/commands/codexa-impact.md +14 -0
- package/integrations/claude-code/commands/codexa-plan.md +20 -0
- package/integrations/claude-code/commands/codexa-review.md +23 -0
- package/integrations/claude-code/commands/codexa-status.md +10 -0
- package/integrations/claude-code/hooks/hooks.json +39 -0
- package/integrations/claude-code/scripts/cmd/brief.sh +18 -0
- package/integrations/claude-code/scripts/cmd/impact.sh +35 -0
- package/integrations/claude-code/scripts/cmd/lib.sh +136 -0
- package/integrations/claude-code/scripts/cmd/plan.sh +52 -0
- package/integrations/claude-code/scripts/cmd/review.sh +66 -0
- package/integrations/claude-code/scripts/cmd/status.sh +52 -0
- package/integrations/claude-code/scripts/codexa-mcp.js +111 -0
- package/integrations/claude-code/scripts/lib/codexa-repo.sh +773 -0
- package/integrations/claude-code/scripts/pre-edit.sh +116 -0
- package/integrations/claude-code/scripts/session-start.sh +201 -0
- package/integrations/claude-code/scripts/stop.sh +443 -0
- package/integrations/claude-code/tests/cmd-smoke.sh +310 -0
- package/integrations/claude-code/tests/hook-smoke.sh +1412 -0
- package/package.json +4 -2
- package/plugins/codexa/.codex-plugin/plugin.json +1 -1
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# PreToolUse hook. When an Edit/Write/MultiEdit/NotebookEdit targets a file
|
|
3
|
+
# inside a codexa-wired repo AND that repo has no change-plan snapshot yet,
|
|
4
|
+
# save an implicit pre-edit baseline via `codexa hook-pre-edit` so the
|
|
5
|
+
# post-edit drift review always has a pre-edit reference, then print a
|
|
6
|
+
# short advisory to stderr. Never blocks — always exits 0.
|
|
7
|
+
#
|
|
8
|
+
# Rules:
|
|
9
|
+
# - The CLI call only happens when no snapshot exists (once per task),
|
|
10
|
+
# capped at 8s inside the 10s hook timeout. Repos that already have a
|
|
11
|
+
# snapshot exit in milliseconds without spawning the CLI.
|
|
12
|
+
# - Fail-open: CLI unavailable/slow degrades to the advisory text.
|
|
13
|
+
# - Non-edit tools exit immediately.
|
|
14
|
+
# - Missing or malformed payload exits 0.
|
|
15
|
+
|
|
16
|
+
set -u
|
|
17
|
+
|
|
18
|
+
CLAUDIO_ROOT="${CLAUDE_PLUGIN_ROOT:-$(cd "$(dirname "$0")/.." && pwd -P)}"
|
|
19
|
+
# shellcheck source=lib/codexa-repo.sh
|
|
20
|
+
. "$CLAUDIO_ROOT/scripts/lib/codexa-repo.sh"
|
|
21
|
+
|
|
22
|
+
payload="$(cat)"
|
|
23
|
+
if [[ -z "$payload" ]]; then
|
|
24
|
+
exit 0
|
|
25
|
+
fi
|
|
26
|
+
|
|
27
|
+
tool_name="$(printf '%s' "$payload" | claudio_json_field tool_name)"
|
|
28
|
+
if ! claudio_is_edit_tool "$tool_name"; then
|
|
29
|
+
exit 0
|
|
30
|
+
fi
|
|
31
|
+
|
|
32
|
+
file_path="$(printf '%s' "$payload" | claudio_tool_input_field file_path)"
|
|
33
|
+
if [[ -z "$file_path" ]]; then
|
|
34
|
+
# NotebookEdit uses notebook_path
|
|
35
|
+
file_path="$(printf '%s' "$payload" | claudio_tool_input_field notebook_path)"
|
|
36
|
+
fi
|
|
37
|
+
if [[ -z "$file_path" ]]; then
|
|
38
|
+
exit 0
|
|
39
|
+
fi
|
|
40
|
+
|
|
41
|
+
# Absolute paths only — relative paths can't be located without a cwd that
|
|
42
|
+
# the hook payload may not carry reliably.
|
|
43
|
+
if [[ "$file_path" != /* ]]; then
|
|
44
|
+
exit 0
|
|
45
|
+
fi
|
|
46
|
+
|
|
47
|
+
resolved="$(claudio_realpath "$file_path")"
|
|
48
|
+
[[ -z "$resolved" ]] && resolved="$file_path"
|
|
49
|
+
|
|
50
|
+
target_dir="$(dirname "$resolved")"
|
|
51
|
+
repo="$(claudio_find_codexa_repo "$target_dir")"
|
|
52
|
+
if [[ -z "$repo" ]]; then
|
|
53
|
+
exit 0
|
|
54
|
+
fi
|
|
55
|
+
|
|
56
|
+
if claudio_has_snapshot "$repo"; then
|
|
57
|
+
exit 0
|
|
58
|
+
fi
|
|
59
|
+
|
|
60
|
+
rel="${resolved#"$repo/"}"
|
|
61
|
+
safe_repo="$(claudio_display_path "$repo")"
|
|
62
|
+
safe_rel="$(claudio_display_path "$rel")"
|
|
63
|
+
|
|
64
|
+
# Negative cache: when the baseline save persistently skips (degraded
|
|
65
|
+
# worktree, blocked plan), every edit would otherwise pay the up-to-8s CLI
|
|
66
|
+
# spawn in the PreToolUse critical path. A recent skip marker (5 min TTL)
|
|
67
|
+
# short-circuits straight to the advisory.
|
|
68
|
+
_pe_state_dir="${CLAUDE_PLUGIN_DATA:-${XDG_STATE_HOME:-$HOME/.local/state}/codexa-claude-code}"
|
|
69
|
+
_pe_repo_key="$(printf '%s' "$repo" | shasum -a 256 2>/dev/null | awk '{print $1}')"
|
|
70
|
+
_pe_skip_marker="$_pe_state_dir/pre-edit-skip-$_pe_repo_key"
|
|
71
|
+
_pe_marker_fresh=0
|
|
72
|
+
if [[ -n "$_pe_repo_key" && -f "$_pe_skip_marker" ]]; then
|
|
73
|
+
_pe_now="$(date +%s 2>/dev/null)"
|
|
74
|
+
_pe_mtime="$(stat -c '%Y' "$_pe_skip_marker" 2>/dev/null || stat -f '%m' "$_pe_skip_marker" 2>/dev/null)"
|
|
75
|
+
if [[ -n "$_pe_now" && -n "$_pe_mtime" ]] && (( _pe_now - _pe_mtime < 300 )); then
|
|
76
|
+
_pe_marker_fresh=1
|
|
77
|
+
fi
|
|
78
|
+
fi
|
|
79
|
+
|
|
80
|
+
# Arm the drift loop: save an implicit baseline of the pre-edit dirty tree.
|
|
81
|
+
# The CLI's own output is discarded; success is re-verified by checking the
|
|
82
|
+
# snapshot on disk so a misbehaving CLI cannot fake the success message.
|
|
83
|
+
if [[ "$_pe_marker_fresh" -eq 0 ]]; then
|
|
84
|
+
if claudio_codexa_run 8 hook-pre-edit "$repo" >/dev/null 2>&1; then
|
|
85
|
+
:
|
|
86
|
+
fi
|
|
87
|
+
if claudio_has_snapshot "$repo"; then
|
|
88
|
+
rm -f "$_pe_skip_marker" 2>/dev/null || true
|
|
89
|
+
cat >&2 <<EOF
|
|
90
|
+
[codexa] Saved an implicit pre-edit baseline for $safe_repo.
|
|
91
|
+
[codexa] /codexa-review can now diff the final tree against this baseline.
|
|
92
|
+
[codexa] Run /codexa-plan "<task>" $safe_rel to upgrade it with planned
|
|
93
|
+
[codexa] scope and tests before anything non-trivial.
|
|
94
|
+
EOF
|
|
95
|
+
exit 0
|
|
96
|
+
fi
|
|
97
|
+
if [[ -n "$_pe_repo_key" ]]; then
|
|
98
|
+
mkdir -p "$_pe_state_dir" 2>/dev/null || true
|
|
99
|
+
touch "$_pe_skip_marker" 2>/dev/null || true
|
|
100
|
+
fi
|
|
101
|
+
fi
|
|
102
|
+
|
|
103
|
+
# Advisory-only fallback. Stderr surfaces in the transcript UI; stdout is
|
|
104
|
+
# reserved for JSON the harness parses. Filesystem-controlled paths flow
|
|
105
|
+
# through claudio_display_path so a hostile filename cannot inject extra
|
|
106
|
+
# advisory lines or prose into the transcript.
|
|
107
|
+
cat >&2 <<EOF
|
|
108
|
+
[codexa] No codexa change-plan snapshot found for $safe_repo.
|
|
109
|
+
[codexa] Before editing $safe_rel, run:
|
|
110
|
+
[codexa] /codexa-plan "<task>" $safe_rel
|
|
111
|
+
[codexa] This saves a baseline under .codex/cache/codexa-tasks/latest.json so
|
|
112
|
+
[codexa] /codexa-review can compute post-edit drift. Skipping the snapshot is
|
|
113
|
+
[codexa] fine for trivial edits; run the planner for anything meaningful.
|
|
114
|
+
EOF
|
|
115
|
+
|
|
116
|
+
exit 0
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# SessionStart hook. Behaves in two modes depending on cwd:
|
|
3
|
+
#
|
|
4
|
+
# (1) Single-repo mode — cwd is inside a codexa-wired repo (walks up to
|
|
5
|
+
# find `.codex/config.toml`). Emits a structured packet with that
|
|
6
|
+
# repo's freshness + top read-first files, all allowlist-validated.
|
|
7
|
+
#
|
|
8
|
+
# (2) Parent-scan mode — cwd has no wired repo above it but contains
|
|
9
|
+
# wired child repos one level down (for example, cwd=~/code with
|
|
10
|
+
# ~/code/myproject wired). Emits a structured roll-up listing each
|
|
11
|
+
# child with its parsed status fields.
|
|
12
|
+
#
|
|
13
|
+
# (3) Silent — cwd has no wired repo either way. Exit 0, no output.
|
|
14
|
+
#
|
|
15
|
+
# Design rules:
|
|
16
|
+
# - Must never block. 5s hard timeout per subprocess; total budget is
|
|
17
|
+
# bounded by the 6s hook timeout in hooks.json.
|
|
18
|
+
# - Never write to disk. Read-only.
|
|
19
|
+
# - No free-form repo text in model-visible fields. Structured only.
|
|
20
|
+
|
|
21
|
+
set -u
|
|
22
|
+
|
|
23
|
+
CLAUDIO_ROOT="${CLAUDE_PLUGIN_ROOT:-$(cd "$(dirname "$0")/.." && pwd -P)}"
|
|
24
|
+
# shellcheck source=lib/codexa-repo.sh
|
|
25
|
+
. "$CLAUDIO_ROOT/scripts/lib/codexa-repo.sh"
|
|
26
|
+
|
|
27
|
+
payload="$(cat)"
|
|
28
|
+
if [[ -z "$payload" ]]; then
|
|
29
|
+
exit 0
|
|
30
|
+
fi
|
|
31
|
+
|
|
32
|
+
cwd="$(printf '%s' "$payload" | claudio_json_field cwd)"
|
|
33
|
+
if [[ -z "$cwd" ]]; then
|
|
34
|
+
exit 0
|
|
35
|
+
fi
|
|
36
|
+
|
|
37
|
+
# --- Mode 1: Single repo (existing behavior). ------------------------------
|
|
38
|
+
repo="$(claudio_find_codexa_repo "$cwd")"
|
|
39
|
+
if [[ -n "$repo" ]]; then
|
|
40
|
+
status_raw=""
|
|
41
|
+
if claudio_codexa_available; then
|
|
42
|
+
status_raw="$(claudio_codexa_run 5 status "$repo" 2>/dev/null || true)"
|
|
43
|
+
fi
|
|
44
|
+
|
|
45
|
+
readme_raw=""
|
|
46
|
+
readme_path="$repo/.codex/codebase/README.md"
|
|
47
|
+
if [[ -f "$readme_path" ]]; then
|
|
48
|
+
readme_raw="$(head -c 65536 "$readme_path" 2>/dev/null || true)"
|
|
49
|
+
fi
|
|
50
|
+
|
|
51
|
+
status_fields="$(claudio_parse_codexa_status "$status_raw")"
|
|
52
|
+
read_first_entries="$(claudio_parse_read_first "$readme_raw" 8)"
|
|
53
|
+
|
|
54
|
+
context="$(
|
|
55
|
+
printf 'codexa/plugin v0.1.0 — validated session context.\n'
|
|
56
|
+
printf '(All values below were parsed against strict allowlists; repo prose is not forwarded.)\n'
|
|
57
|
+
printf '\nStatus:\n'
|
|
58
|
+
if [[ -n "$status_fields" ]]; then
|
|
59
|
+
printf '%s\n' "$status_fields" | sed 's/^/ /'
|
|
60
|
+
else
|
|
61
|
+
printf ' (unavailable)\n'
|
|
62
|
+
fi
|
|
63
|
+
printf '\nRead-first (top-ranked files):\n'
|
|
64
|
+
if [[ -n "$read_first_entries" ]]; then
|
|
65
|
+
while IFS=$'\t' read -r p r; do
|
|
66
|
+
[[ -z "$p" ]] && continue
|
|
67
|
+
printf ' - %s (rank %s)\n' "$p" "$r"
|
|
68
|
+
done <<<"$read_first_entries"
|
|
69
|
+
else
|
|
70
|
+
printf ' (none parsed)\n'
|
|
71
|
+
fi
|
|
72
|
+
printf '\nNext calls:\n'
|
|
73
|
+
printf ' - /codexa-status — refresh this packet\n'
|
|
74
|
+
printf ' - /codexa-brief — task brief + diff impact\n'
|
|
75
|
+
printf ' - /codexa-plan — save a change-plan snapshot\n'
|
|
76
|
+
printf ' - /codexa-review — post-edit drift review\n'
|
|
77
|
+
printf ' - /codexa-impact — blast-radius for a file or symbol\n'
|
|
78
|
+
)"
|
|
79
|
+
|
|
80
|
+
python3 - "$repo" "$context" <<'PY'
|
|
81
|
+
import json
|
|
82
|
+
import sys
|
|
83
|
+
|
|
84
|
+
repo_path = sys.argv[1]
|
|
85
|
+
additional_context = sys.argv[2]
|
|
86
|
+
payload = {
|
|
87
|
+
"hookSpecificOutput": {
|
|
88
|
+
"hookEventName": "SessionStart",
|
|
89
|
+
"additionalContext": additional_context,
|
|
90
|
+
"codexaRepoPath": repo_path,
|
|
91
|
+
},
|
|
92
|
+
"systemMessage": "Codexa-wired repo detected. See hookSpecificOutput for details.",
|
|
93
|
+
}
|
|
94
|
+
sys.stdout.write(json.dumps(payload, ensure_ascii=False))
|
|
95
|
+
sys.stdout.write("\n")
|
|
96
|
+
PY
|
|
97
|
+
exit 0
|
|
98
|
+
fi
|
|
99
|
+
|
|
100
|
+
# --- Mode 2: Parent-scan fallback. -----------------------------------------
|
|
101
|
+
# cwd has no wired repo above; look for wired repos one level down.
|
|
102
|
+
declare -a child_repos=()
|
|
103
|
+
while IFS= read -r child; do
|
|
104
|
+
[[ -z "$child" ]] && continue
|
|
105
|
+
child_repos+=("$child")
|
|
106
|
+
done < <(claudio_list_child_codexa_repos "$cwd")
|
|
107
|
+
|
|
108
|
+
# Mode 3: Silent exit if no children either.
|
|
109
|
+
if [[ ${#child_repos[@]} -eq 0 ]]; then
|
|
110
|
+
exit 0
|
|
111
|
+
fi
|
|
112
|
+
|
|
113
|
+
# Parent-scan mode deliberately does NOT run `codexa status` per child.
|
|
114
|
+
# Up to 8 wired children × 2s-per-call can chew through the 6s outer hook
|
|
115
|
+
# budget before the banner is emitted at all. Instead we list the child
|
|
116
|
+
# repo paths with no per-repo status — the user can `/codexa-status`
|
|
117
|
+
# after `cd`-ing into a repo to get full detail.
|
|
118
|
+
declare -a child_paths=()
|
|
119
|
+
for child in "${child_repos[@]:0:8}"; do
|
|
120
|
+
child_paths+=("$child")
|
|
121
|
+
done
|
|
122
|
+
|
|
123
|
+
# Build the multi-repo additionalContext. Repo names are validated as
|
|
124
|
+
# basename-only (no paths), which means ${child##*/}. Those are
|
|
125
|
+
# filesystem-controlled but already pass through the structured
|
|
126
|
+
# additionalContext field, and downstream we never re-interpret them.
|
|
127
|
+
# The parent cwd is also filesystem-controlled, so it runs through the
|
|
128
|
+
# display-safe quoter before reaching the banner — same threat as a
|
|
129
|
+
# hostile repo directory name would otherwise smuggle in newlines.
|
|
130
|
+
safe_cwd="$(claudio_display_path "$cwd")"
|
|
131
|
+
|
|
132
|
+
# Privacy knob: by default, list each wired child by basename so the user
|
|
133
|
+
# can see which projects are discoverable. Set CLAUDIO_PARENT_SCAN_NAMES=0
|
|
134
|
+
# to emit a count-only banner instead — useful when Claude Code is
|
|
135
|
+
# started from a shared parent directory where exposing sibling project
|
|
136
|
+
# names to the session context would leak information.
|
|
137
|
+
reveal_names="${CLAUDIO_PARENT_SCAN_NAMES:-1}"
|
|
138
|
+
|
|
139
|
+
context="$(
|
|
140
|
+
printf 'codexa/plugin v0.1.0 — parent-scan session context.\n'
|
|
141
|
+
printf '(cwd is above any wired repo. Reporting direct children that are codexa-wired.)\n'
|
|
142
|
+
if [[ "$reveal_names" == "0" ]]; then
|
|
143
|
+
# Privacy mode: NO cwd, NO child names. Just the count. Nothing in
|
|
144
|
+
# this banner can identify the parent directory or any sibling
|
|
145
|
+
# project once CLAUDIO_PARENT_SCAN_NAMES=0 is set.
|
|
146
|
+
printf '\nWired repos:\n'
|
|
147
|
+
printf ' (%d wired child repo(s) detected; names and parent cwd redacted via CLAUDIO_PARENT_SCAN_NAMES=0)\n' "${#child_paths[@]}"
|
|
148
|
+
else
|
|
149
|
+
printf '\nWired repos under %s:\n' "$safe_cwd"
|
|
150
|
+
for repo_path in "${child_paths[@]}"; do
|
|
151
|
+
short_name="$(basename "$repo_path")"
|
|
152
|
+
# Basename must pass a strict allowlist. Anything else — prose,
|
|
153
|
+
# punctuation, control chars — is replaced with a stable placeholder
|
|
154
|
+
# so a hostile directory name cannot inject prompt text.
|
|
155
|
+
if [[ "$short_name" =~ ^[A-Za-z0-9._-]{1,64}$ ]]; then
|
|
156
|
+
safe_name="$short_name"
|
|
157
|
+
else
|
|
158
|
+
safe_name="(unsafe-name)"
|
|
159
|
+
fi
|
|
160
|
+
printf ' - %s\n' "$safe_name"
|
|
161
|
+
done
|
|
162
|
+
printf '\n(Run /codexa-status after cd-ing into any repo for freshness / commit / dirty-file detail.)\n'
|
|
163
|
+
fi # end reveal_names branch
|
|
164
|
+
printf '\nNext calls:\n'
|
|
165
|
+
printf ' - cd into any wired repo to enable auto-nudges on Edit/Write/MultiEdit\n'
|
|
166
|
+
printf ' - /codexa-status — full status for the repo containing your cwd\n'
|
|
167
|
+
printf ' - /codexa-brief — task brief + diff impact\n'
|
|
168
|
+
printf ' - /codexa-plan — save a change-plan snapshot before editing\n'
|
|
169
|
+
printf ' - /codexa-review — post-edit drift review\n'
|
|
170
|
+
printf ' - codexa init <repo> — wire an unwired project\n'
|
|
171
|
+
)"
|
|
172
|
+
|
|
173
|
+
# Emit multi-repo envelope. `codexaRepoPaths` is a JSON array of the raw
|
|
174
|
+
# paths for structured-field consumers (e.g. Claude tool calls that want
|
|
175
|
+
# to enumerate available projects).
|
|
176
|
+
python3 - "$cwd" "$context" "$reveal_names" "${child_paths[@]}" <<'PY'
|
|
177
|
+
import json
|
|
178
|
+
import sys
|
|
179
|
+
|
|
180
|
+
cwd = sys.argv[1]
|
|
181
|
+
additional_context = sys.argv[2]
|
|
182
|
+
reveal_names = sys.argv[3] != "0"
|
|
183
|
+
child_paths = list(sys.argv[4:])
|
|
184
|
+
output = {
|
|
185
|
+
"hookEventName": "SessionStart",
|
|
186
|
+
"additionalContext": additional_context,
|
|
187
|
+
"codexaRepoCount": len(child_paths),
|
|
188
|
+
}
|
|
189
|
+
# Count-only privacy mode: suppress BOTH codexaRepoPaths and codexaCwd so
|
|
190
|
+
# no path-level identifier (raw paths, parent cwd) leaks to downstream
|
|
191
|
+
# structured consumers. Only the count survives.
|
|
192
|
+
if reveal_names:
|
|
193
|
+
output["codexaCwd"] = cwd
|
|
194
|
+
output["codexaRepoPaths"] = child_paths
|
|
195
|
+
payload = {
|
|
196
|
+
"hookSpecificOutput": output,
|
|
197
|
+
"systemMessage": "Codexa-wired child repos detected. See hookSpecificOutput for details.",
|
|
198
|
+
}
|
|
199
|
+
sys.stdout.write(json.dumps(payload, ensure_ascii=False))
|
|
200
|
+
sys.stdout.write("\n")
|
|
201
|
+
PY
|