Skip to content

Recipes

m4bwav edited this page Oct 25, 2026 · 2 revisions

Each recipe was run against the 0.1.1 release zip, in the order shown, on the set from Getting started: "Render tonight" picked, a note on the music question.

A second round

The person's note asked for the composer first; the composer sent two cuts. Write the new question to a file with supersedes naming the old one:

{
  "id": "music2",
  "kind": "choice",
  "title": "Credits music, again",
  "supersedes": "music",
  "question": "Which of the two piano cuts should play over the credits?",
  "why": "The composer sent two lengths; the credits run 40 seconds.",
  "options": [
    {"id": "short", "label": "The 38-second cut"},
    {"id": "long", "label": "The 52-second cut"}
  ],
  "guess": {"option": "short", "why": "It fits without a fade.", "confidence": "high"}
}
$ python ask.py add ask/first round2.json
{
  "ok": true,
  "added": [
    "music2"
  ],
  "warnings": []
}
$ echo $?
0

music is now superseded and off the page; the running server picks up the new question on the page's next load, with no restart. add also writes an "added" mark, so a set the person had finished reports "done": false again until they press "All done" once more.

Changing a question that may already be answered

The render now takes nine hours instead of six. Replace the question with --replace, which raises its rev:

python ask.py add ask/first render2.json --replace

The answer the person gave to the six-hour version is now stale. It still counts as answered, so check stale before acting on it:

$ python ask.py status ask/first
{
  "ok": true,
  "set": "first",
  "title": "Try askwright",
  "open": 2,
  "rev": 2,
  "done": false,
  "paused": false,
  "last_mark": {
    "t": "<time>",
    "type": "added",
    "q": [
      "render"
    ]
  },
  "server": {
    "url": "http://127.0.0.1:<port>/",
    "pid": <pid>,
    "port": <port>,
    "set_dir": "/work/proj/ask/first",
    "started": "<time>",
    "alive": true
  },
  "answered": [
    "render"
  ],
  "unsure": [],
  "note_only": [],
  "unanswered": [
    "music2"
  ],
  "waiting": [],
  "not_applicable": [],
  "stale": [
    "render"
  ]
}
$ echo $?
0

Change a question in place without --replace (by editing questions.json) only for a typo: the old answer would not be flagged.

Taking a question back

python ask.py close ask/first music2 --withdrawn "The credits were cut"

A withdrawn question leaves the page; its reason stays in the file. --superseded-by <id> points to the question that replaces it, and no option just closes it.

Waiting for the answers from a script

status --wait-done exits 0 whether the person finished or the wait timed out, so test timed_out. With ask.py's path in ASK:

import json, subprocess, sys

ASK = "skills/askwright/scripts/ask.py"


def ask(*args):
    out = subprocess.run([sys.executable, ASK, *args], capture_output=True, text=True).stdout
    return json.loads(out)


status = ask("status", "ask/first", "--wait-done", "--timeout", "5", "--interval", "1")
if status["timed_out"]:
    print("not done yet; unanswered:", status["unanswered"], "stale:", status["stale"])
for a in ask("read", "ask/first")["answers"]:
    print(a["qid"], a["state"], a.get("choice"), a["note"] or "-")

Run after the two recipes above (the second round added, then withdrawn), it printed:

not done yet; unanswered: [] stale: ['render']
render answered tonight -
music note_only None Ask the composer first

Nothing is unanswered, but the last mark is the "added" one, so the wait ran out; the render answer is stale; and read still returns the superseded music question's note.

Asking in chat instead

For one to four plain questions, a page may be more than the person wants. ask.py inline turns them into a numbered chat message and an AskUserQuestion mapping, or says why it cannot (Commands). inline records nothing: the chat reply is the answer, and the set's answers.json stays as it was.

Media that lives elsewhere

Keep the set small and point it at the render folder with media_roots, relative to the set folder:

{"media_roots": ["../../renders"]}

lint checks that each root exists and that every src is found inside the set folder or a root. A src such as ../outside.png that climbs out fails as media-outside, and the server refuses such a path with 403 (Server and security).

Clone this wiki locally