da8f557219
GPU Scheduler (gpu.sethpc.xyz): - Live dashboard with 4 GPUs, training monitor, loss sparklines - Preset-based job scheduler with 3 triggers (time, finish_training, cost) - Model selection per GPU, pipeline configuration - Tool self-play and training pipeline types - Behind Google OAuth, live-refresh without page reload Tool Architecture (14 tools): - 3 new tools: world.nearby_entities, memory.read, memory.write - 7 script.* tools: write, validate, execute, read, list, delete, schedule - ScriptManager: full mcfunction datapack CRUD with RCON validation - Training data: 1,430 tool examples (up from 1,159) Plugin Deployment (paper-ai-25567): - WorldGuard 7.0.12, CoreProtect CE 23.1, EssentialsX 2.21.2, Vault 1.7.3 - Fresh greenfield world reset - 104 RCON-validated plugin training examples Event Dispatcher: - Watches server log for deaths, joins, advancements, PvP kills - Configurable trigger probability and cooldowns per event type - Deployed to dev server, fires god_system prompts on events - 21 event-response training examples Training Infrastructure: - train_lora.py: --save-steps 50, --resume from checkpoint - run_training.sh: stops Ollama, activates conda, restarts after - Passwordless sudo for ollama services on steel141 - Dev server added to MCSManager with autoStart Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
205 lines
6.3 KiB
Python
205 lines
6.3 KiB
Python
#!/usr/bin/env python3
|
||
"""Create the playtest application Google Form via the Forms API."""
|
||
|
||
from google.oauth2 import service_account
|
||
from googleapiclient.discovery import build
|
||
|
||
SA_KEY = "/root/bin/Mincecraft-AI-model/sethpc-xyz-f637843c083f.json"
|
||
OWNER_EMAIL = "seth@sethfreiberg.com"
|
||
|
||
SCOPES = [
|
||
"https://www.googleapis.com/auth/forms.body",
|
||
"https://www.googleapis.com/auth/drive",
|
||
]
|
||
|
||
creds = service_account.Credentials.from_service_account_file(SA_KEY, scopes=SCOPES)
|
||
creds = creds.with_subject(OWNER_EMAIL) # impersonate workspace user
|
||
|
||
forms_service = build("forms", "v1", credentials=creds)
|
||
drive_service = build("drive", "v3", credentials=creds)
|
||
|
||
# Step 1: Create blank form
|
||
form = forms_service.forms().create(body={
|
||
"info": {"title": "Minecraft Playtest Application"}
|
||
}).execute()
|
||
form_id = form["formId"]
|
||
print(f"Created form: {form_id}")
|
||
|
||
# Step 2: Add all questions via batchUpdate
|
||
questions = [
|
||
{
|
||
"title": "What's your Minecraft Java Edition username?",
|
||
"required": True,
|
||
"questionItem": {
|
||
"question": {
|
||
"textQuestion": {"paragraph": False}
|
||
}
|
||
}
|
||
},
|
||
{
|
||
"title": "How long have you been playing Minecraft?",
|
||
"required": True,
|
||
"questionItem": {
|
||
"question": {
|
||
"choiceQuestion": {
|
||
"type": "RADIO",
|
||
"options": [
|
||
{"value": "Less than a year"},
|
||
{"value": "1 – 3 years"},
|
||
{"value": "3+ years"},
|
||
]
|
||
}
|
||
}
|
||
}
|
||
},
|
||
{
|
||
"title": "Have you played on community/SMP servers before?",
|
||
"required": True,
|
||
"questionItem": {
|
||
"question": {
|
||
"choiceQuestion": {
|
||
"type": "RADIO",
|
||
"options": [
|
||
{"value": "Yes, regularly"},
|
||
{"value": "A few times"},
|
||
{"value": "No, mostly singleplayer"},
|
||
]
|
||
}
|
||
}
|
||
}
|
||
},
|
||
{
|
||
"title": "What interests you about this? (pick all that apply)",
|
||
"required": True,
|
||
"questionItem": {
|
||
"question": {
|
||
"choiceQuestion": {
|
||
"type": "CHECKBOX",
|
||
"options": [
|
||
{"value": "Curious what the feature actually is"},
|
||
{"value": "Helping test something new"},
|
||
{"value": "Trying to break things (in a helpful way)"},
|
||
{"value": "Looking for a server to hang out on"},
|
||
]
|
||
}
|
||
}
|
||
}
|
||
},
|
||
{
|
||
"title": "You're testing a new server feature and it refuses to do something you asked. What do you do?",
|
||
"required": True,
|
||
"questionItem": {
|
||
"question": {
|
||
"textQuestion": {"paragraph": True}
|
||
}
|
||
}
|
||
},
|
||
{
|
||
"title": "Have you ever been banned from a server? If so, what happened?",
|
||
"required": True,
|
||
"questionItem": {
|
||
"question": {
|
||
"textQuestion": {"paragraph": True}
|
||
}
|
||
}
|
||
},
|
||
{
|
||
"title": "When are you generally available? (timezone + rough hours)",
|
||
"required": True,
|
||
"questionItem": {
|
||
"question": {
|
||
"textQuestion": {"paragraph": False}
|
||
}
|
||
}
|
||
},
|
||
{
|
||
"title": "Anything else?",
|
||
"required": False,
|
||
"questionItem": {
|
||
"question": {
|
||
"textQuestion": {"paragraph": True}
|
||
}
|
||
}
|
||
},
|
||
]
|
||
|
||
requests = []
|
||
for i, q in enumerate(questions):
|
||
requests.append({
|
||
"createItem": {
|
||
"item": {
|
||
"title": q["title"],
|
||
"questionItem": q["questionItem"],
|
||
},
|
||
"location": {"index": i}
|
||
}
|
||
})
|
||
|
||
# Also update the form description
|
||
requests.insert(0, {
|
||
"updateFormInfo": {
|
||
"info": {
|
||
"description": "Quick form to make sure we get a good group. Takes about 2 minutes."
|
||
},
|
||
"updateMask": "description"
|
||
}
|
||
})
|
||
|
||
forms_service.forms().batchUpdate(formId=form_id, body={"requests": requests}).execute()
|
||
print("Added all questions")
|
||
|
||
# Step 3: Set required fields (Forms API sets required via the question object)
|
||
# Need to fetch the form to get question IDs, then update each
|
||
form_data = forms_service.forms().get(formId=form_id).execute()
|
||
update_requests = []
|
||
for item in form_data.get("items", []):
|
||
q_title = item.get("title", "")
|
||
is_required = True
|
||
if q_title == "Anything else?":
|
||
is_required = False
|
||
|
||
question_id = item.get("questionItem", {}).get("question", {}).get("questionId")
|
||
if question_id:
|
||
update_requests.append({
|
||
"updateItem": {
|
||
"item": {
|
||
"itemId": item["itemId"],
|
||
"title": item["title"],
|
||
"questionItem": {
|
||
"question": {
|
||
"questionId": question_id,
|
||
"required": is_required,
|
||
**({k: v for k, v in item["questionItem"]["question"].items()
|
||
if k not in ("questionId", "required")})
|
||
}
|
||
}
|
||
},
|
||
"location": {"index": list(form_data["items"]).index(item)},
|
||
"updateMask": "questionItem.question.required"
|
||
}
|
||
})
|
||
|
||
if update_requests:
|
||
forms_service.forms().batchUpdate(formId=form_id, body={"requests": update_requests}).execute()
|
||
print("Set required flags")
|
||
|
||
# Step 4: Transfer to owner's Drive
|
||
drive_service.permissions().create(
|
||
fileId=form_id,
|
||
body={
|
||
"type": "user",
|
||
"role": "writer",
|
||
"emailAddress": OWNER_EMAIL,
|
||
},
|
||
transferOwnership=False,
|
||
).execute()
|
||
print(f"Shared with {OWNER_EMAIL} (editor)")
|
||
|
||
# Note: service accounts can't transfer ownership directly.
|
||
# The form is now shared with seth as editor. He can copy it or take ownership from Drive.
|
||
|
||
responder_url = f"https://docs.google.com/forms/d/{form_id}/viewform"
|
||
edit_url = f"https://docs.google.com/forms/d/{form_id}/edit"
|
||
print(f"\nForm URL (for applicants): {responder_url}")
|
||
print(f"Edit URL (for you): {edit_url}")
|