Hand over for a code
The AI signs in with your username and a password kept as a credential; when the site asks for a code sent by SMS or an app, it pauses and asks you, then types the code and carries on. The run names the credential (credentials: ["SIGNIN_PASSWORD"]) and the model only ever sees %SIGNIN_PASSWORD%.
When to use it: The AI signs in for you, but the site sends a one-time code to your phone.
| Uses | credentials.create (the password as a secret, limited to the sign-in site with origins), agent.run with credentials and variables, agent.stream, agent.resume with a note |
|---|---|
| Needs | a plan with room for credentials, and a model on the API |
| Site | your sign-in page (SIGNIN_URL). The runner uses its stand-in (password, then a 6-digit code) and types the code |
| Output | output/result.json |
| Languages | Node and Python |
You take part: When the AI asks, type the code the site sent you.
The code
The example as it is in its folder. Both versions do the same job and write the same output/, so one result check covers both.
/** * Hand over for a code: the AI signs in with your username and a password kept as a credential, and when the * site asks for a code sent by SMS (or an app), it pauses and asks you for it, then carries on. * * SIGNIN_URL=https://… SITE_USERNAME=… SITE_PASSWORD=… npx tsx node/index.ts (BOXLINE_API_KEY) * * The password goes into the credential SIGNIN_PASSWORD, a secret (SECRET_NAME picks another name), limited to the * sign-in site; after that SITE_PASSWORD is not needed (set the credential in the console instead, if you like). The run * names the credential, and the model only ever sees %username% and %SIGNIN_PASSWORD%. Writes output/result.json. */import { mkdirSync, writeFileSync } from "node:fs";import { join } from "node:path";import { createInterface } from "node:readline/promises";import { Boxline, CredentialExistsError } from "@boxline/sdk";const out = process.env.OUTPUT_DIR ?? "output";const signin = process.env.SIGNIN_URL;const username = process.env.SITE_USERNAME;const secretName = process.env.SECRET_NAME ?? "SIGNIN_PASSWORD";if (!signin || !username) throw new Error("Set SIGNIN_URL and SITE_USERNAME (and SITE_PASSWORD the first time)");const bx = new Boxline();// The password as a credential (a secret): stored sealed, never shown again, and typed only into fields on the sign-in site.let secretCreated = false;if (process.env.SITE_PASSWORD) { const origins = [new URL(signin).origin]; try { await bx.credentials.create({ name: secretName, type: "secret", value: process.env.SITE_PASSWORD, origins, description: `Password for ${new URL(signin).host}` }); secretCreated = true; } catch (err) { if (!(err instanceof CredentialExistsError)) throw err; await bx.credentials.update(secretName, { value: process.env.SITE_PASSWORD, origins }); }}console.log(`Credential ${secretName}: ${JSON.stringify(await bx.credentials.get(secretName).then(({ origins, scope }) => ({ origins, scope })))}`);const run = await bx.agent.run({ task: `Open ${signin} and sign in with the username %username% and the password %${secretName}%. When the site asks for a ` + "verification code, ask me for it with ask_user_for_help and wait; then type the code I give you. When I'm signed in, " + "tell me the name shown on the page.", variables: { username }, credentials: [secretName], // the model sees %SIGNIN_PASSWORD%; the platform types the value maxSteps: 25,});console.log(`Agent run ${run.id} (${run.provider}/${run.model}) · Session: ${run.sessionId}`);const rl = createInterface({ input: process.stdin, output: process.stdout });let handovers = 0;for await (const e of bx.agent.stream(run.id)) { if (e.type === "tool") console.log(`→ ${e.name} ${JSON.stringify(e.input ?? {}).slice(0, 100)}`); // placeholders, never values else if (e.type === "handover" && e.by === "agent") { handovers++; console.log(`\nThe AI asks: ${e.text}`); const code = await rl.question("Type the code the site sent you: "); await bx.agent.resume(run.id, { note: `The verification code is ${code.trim()}` }); } else if (e.type === "done") console.log(`\n${e.status}: ${e.result ?? e.error}`);}rl.close();const done = await bx.agent.get(run.id);mkdirSync(out, { recursive: true });writeFileSync( join(out, "result.json"), JSON.stringify( { signinUrl: signin, secret: secretName, agentRuns: [{ id: run.id }], status: done.status, answer: done.result, handovers, steps: done.steps.length, model: done.model, made: { credentials: secretCreated ? [secretName] : [] } }, null, 2, ),);"""Hand over for a code: the AI signs in with your username and a password kept as a credential, and whenthe site asks for a code sent by SMS (or an app), it pauses and asks you for it, then carries on. SIGNIN_URL=https://… SITE_USERNAME=… SITE_PASSWORD=… python python/main.py (BOXLINE_API_KEY)The password goes into the credential SIGNIN_PASSWORD, a secret (SECRET_NAME picks another name), limited to thesign-in site; after that SITE_PASSWORD is not needed (set the credential in the console instead, if you like). The runnames the credential, and the model only ever sees %username% and %SIGNIN_PASSWORD%. Writes output/result.json."""import jsonimport osfrom pathlib import Pathfrom urllib.parse import urlparsefrom boxline import Boxline, CredentialExistsErrorout = Path(os.environ.get("OUTPUT_DIR", "output"))signin = os.environ.get("SIGNIN_URL")username = os.environ.get("SITE_USERNAME")secret_name = os.environ.get("SECRET_NAME", "SIGNIN_PASSWORD")if not (signin and username): raise SystemExit("Set SIGNIN_URL and SITE_USERNAME (and SITE_PASSWORD the first time)")origin = "{0.scheme}://{0.netloc}".format(urlparse(signin))bx = Boxline()# The password as a credential (a secret): stored sealed, never shown again, and typed only into fields on the sign-in site.secret_created = Falseif os.environ.get("SITE_PASSWORD"): try: bx.credentials.create(secret_name, "secret", value=os.environ["SITE_PASSWORD"], description=f"Password for {urlparse(signin).netloc}", origins=[origin]) secret_created = True except CredentialExistsError: bx.credentials.update(secret_name, value=os.environ["SITE_PASSWORD"], origins=[origin])secret = bx.credentials.get(secret_name)print(f"Credential {secret_name}: origins {secret['origins']}, scope {secret['scope']}")run = bx.agent.run( f"Open {signin} and sign in with the username %username% and the password %{secret_name}%. When the site asks for a " "verification code, ask me for it with ask_user_for_help and wait; then type the code I give you. When I'm signed in, " "tell me the name shown on the page.", variables={"username": username}, credentials=[secret_name], # the model sees %SIGNIN_PASSWORD%; the platform types the value max_steps=25,)print(f"Agent run {run['id']} ({run['provider']}/{run['model']}) · Session: {run['sessionId']}", flush=True)handovers = 0for e in bx.agent.stream(run["id"]): if e["type"] == "tool": print(f"-> {e['name']} {json.dumps(e.get('input') or {})[:100]}") # placeholders, never values elif e["type"] == "handover" and e.get("by") == "agent": handovers += 1 print(f"\nThe AI asks: {e.get('text')}") code = input("Type the code the site sent you: ") bx.agent.resume(run["id"], note=f"The verification code is {code.strip()}") elif e["type"] == "done": print(f"\n{e['status']}: {e.get('result') or e.get('error')}")done = bx.agent.get(run["id"])out.mkdir(parents=True, exist_ok=True)result = {"signinUrl": signin, "secret": secret_name, "agentRuns": [{"id": run["id"]}], "status": done["status"], "answer": done["result"], "handovers": handovers, "steps": len(done["steps"]), "model": done["model"], "made": {"credentials": [secret_name] if secret_created else []}}(out / "result.json").write_text(json.dumps(result, indent=2, ensure_ascii=False))Inputs
Environment variables the example reads:
SIGNIN_URL: your sign-in page (required).SITE_USERNAME: your username (required).SITE_PASSWORD: your password, stored into the credential the first time (keep it in the environment, never in code; later runs need only the credential).SECRET_NAME: the credential's name (defaultSIGNIN_PASSWORD).
Run it
Get the examples with git clone https://github.com/Boxline-dev/examples.git and open this one: cd examples/hand-over-for-a-code. Put your API key in BOXLINE_API_KEY (and BOXLINE_API_URL for another API), then run either version from the example’s folder. Both write to output/ and stop their sessions when they finish.
# Node 18 or newer, in the example's folderexport BOXLINE_API_KEY=… # your API keynpm install @boxline/sdk tsxnpx tsx node/index.ts# Python 3.9 or newer, in the example's folderexport BOXLINE_API_KEY=… # your API keypip install boxline-sdkpython python/main.pyThen check the result, from the same folder: npx tsx check.ts output. It exits with 0 only when the result is right.
What its check proves
check.ts reads what the example wrote and passes only when the result is right, not merely when the example ran.
The site saw the password and then the code, the AI asked at least once and named the signed-in user, the run the API keeps never holds the password, and the credential's use by the run is in its audit log.
Source
github.com/Boxline-dev/examples/hand-over-for-a-code on GitHub, with its README.md, check.ts and .env.example. The examples are open source. See all examples.
These docs describe a beta API. Endpoints, fields and SDK methods may change before general availability; breaking changes will be listed in the changelog.