Latchkey
Instructions
Latchkey is a CLI tool that automatically injects credentials into curl commands. Credentials are managed on the outside by the Minds app - sending a permission request also triggers a login flow if necessary.
Use this skill when the user asks you to work on their behalf with services that have HTTP APIs, like AWS, GitLab, Google Drive, Discord or others.
Usage:
- Use
latchkey curlinstead of regularcurlfor supported services.- Pass through all regular curl arguments - latchkey is a transparent wrapper.
- Latchkey comes with a list of builtin supported services.
- Check for
latchkey services listto get a list of supported services. Use--viableto only show the currently configured ones. - Use
latchkey services info <service_name>to get information about a specific service (auth options, credentials status, API docs links, special requirements, etc.). - You can request to register more custom services, although the options for authenticating to custom services are limited. See "Ask for a new connection to a domain latchkey does not know" below.
- Check for
- Submit a permission request to the user by calling
latchkey curl -XPOST http://latchkey-self.invalid/permission-requestswhenlatchkey curlfails with a Latchkey permission error. See "Interpreting Latchkey permission errors" below. One request per tool call, on its own, output untouched. - Look for the newest documentation of the desired public API online. Avoid bot-only endpoints.
Examples
Make an authenticated curl request
latchkey curl [curl arguments]
Creating a Slack channel
latchkey curl -X POST 'https://slack.com/api/conversations.create' \
-H 'Content-Type: application/json' \
-d '{"name":"my-channel"}'
(Notice that -H 'Authorization: Bearer is not present in the invocation.)
Getting Discord user info
latchkey curl 'https://discord.com/api/v10/users/@me'
Interpreting Latchkey permission errors
When latchkey curl comes back with a response like {"error": "..."},
it could be a genuine error from the upstream API endpoint, but it could also be
that Latchkey hasn't granted you permission to access the service. When the
latter is true, you can call
latchkey curl -XPOST http://latchkey-self.invalid/permission-requests
to request the user to grant you the necessary permission.
Inspect the error text (not the status code or exit code) to decide which type of permission request to send.
| Error latchkey returned | What it means | What to send |
|---|---|---|
No service matches URL: <url> |
Latchkey has no service for this domain at all | type: "custom-service" |
No credentials found for <service>. |
The service exists; it is not connected yet | type: "predefined" |
Request not permitted by the user. |
The service exists and is connected; you lack the permission | type: "predefined" |
Ask for a new connection to a domain latchkey does not know
Before you decide to go down this route, note that:
You don't need to ask for a new connection to make requests to URLs that don't require credentials. Latchkey is not necessary at all.
The authentication headers and login flows currently supported by Latchkey are limited (see details below ). If the service needs anything else, fall back to either:
- Ask the user to perform some operations manually.
- If the user really wants automated access, ask the user to type in the credentials and store them within the workspace, with ample warning about its security consequence of that.
This asks the user to create a connection to one domain and to let this machine use it:
# This one must go in a tool call of its own, with nothing else in it and its output untouched.
latchkey curl -XPOST http://latchkey-self.invalid/permission-requests \
-H 'Content-Type: application/json' \
-d '{"agent_id": "'"$MNGR_AGENT_ID"'", "type": "custom-service", "payload": {"domain": "api.example.com", "scheme": "https"}, "rationale": "I'"'"'d like to reach the Example widget API to look up the part numbers you asked about."}'
domainmust be ASCII; encode non-ASCII ones with punycode.schememust be one of"https"and"http".
By default the user is asked to paste a token during approval, and Latchkey
attaches it as an Authorization: Bearer <token> header.
Alternatively, trigger a browser sign-in flow and have Latchkey retrieve and
store credentials from the browser, by adding a login object.
-d '{... "payload": {"domain": "api.example.com", "scheme": "https", "login": {"url": "https://api.example.com/login", "flow": "cookie-capture", "flow_params": {"cookieKeys": ["session"]}}}}'
login.url, login.flow and login.flow_params are the same as the
--login-url, --login-flow and --login-flow-params flags documented in
latchkey services register --help (run it to see each flow's parameters), but
beware that you should use this API instead of the latchkey services register
CLI directly. All three are required inside login: a flow needs a login URL
and its parameters. The parameters are checked against the flow's schema, so an
unknown key is refused, and every URL in them (url, cookieUrl, tokenUrl)
must be on domain or a subdomain of it.
Furthermore, beware that every login flow has limitations: make sure that the login flow you request will actually work for the custom service you're registering.
Ask for user permission
When either there are no valid credentials for the given service or our
requests come back with the "request not permitted by the user"
message, ask the user for permission. The requests are sent to
Latchkey via the reserved latchkey-self.invalid host:
# 1. Retrieve the list of available permissions if necessary.
latchkey curl http://latchkey-self.invalid/permissions/available/discord
# 2. Retrieve the list of your existing permissions if necessary.
latchkey curl http://latchkey-self.invalid/permissions/self | jq .rules
# 3. Ask for the missing permissions.
# This one must go in a tool call of its own, with nothing else in it and its output untouched.
latchkey curl -XPOST http://latchkey-self.invalid/permission-requests \
-H 'Content-Type: application/json' \
-d '{"agent_id": "'"$MNGR_AGENT_ID"'", "type": "predefined", "payload": {"scope": "discord-api", "permissions": ["discord-read-all"]}, "rationale": "I'"'"'d like to access your Discord account to read server and channel information so I can help you summarize conversations."}'
The body must be a JSON object with exactly four fields:
agent_id (use $MNGR_AGENT_ID), type (use "predefined"), payload, and rationale.
payload must be an object with at least two fields: scope (string) and permissions (array of strings). scope needs to be one of the scopes specified in the response to the /permissions/available/<service_name> call.
When you need permissions for a specific account, you can
specify the optional third field on the payload: account (which should be a string).
For example: `-d '{... "payload": {"scope": ..., "permissions": ..., "account": "bob@example.com"}}'.
When not sure (and if applicable), prefer the *-read-all permission variants as they are relatively safe and obvious.
After posting, wait for an automated system message indicating whether the user approved or denied the permission request. If the permission still doesn't appear on your first call after an approval message, sleep for a few seconds and retry - the change can take a moment to propagate.
Do not ask the user to tell you when they respond to a request. Just mention that you'll continue once they do if that's something you need to wait on.
Git operations on GitHub (clone / fetch / push)
The gateway natively proxies GitHub's git smart-HTTP endpoints, so plain
git works through latchkey too: point git at the gateway's proxy URL and
pass the gateway's auth headers (their values are already in this
environment).
git -c "http.extraHeader=X-Latchkey-Gateway-Password: $LATCHKEY_GATEWAY_PASSWORD" \
${LATCHKEY_GATEWAY_PERMISSIONS_OVERRIDE:+-c "http.extraHeader=X-Latchkey-Gateway-Permissions-Override: $LATCHKEY_GATEWAY_PERMISSIONS_OVERRIDE"} \
push "$LATCHKEY_GATEWAY/gateway/https://github.com/<owner>/<repo>.git" <refspec>
(clone, fetch, and ls-remote take the same proxy URL and headers.) The
GitHub credential is injected server-side -- no token enters the container.
This is gated by the github-git scope: github-git-read covers clone and
fetch, github-git-write covers push. Request them like any other permission
(see "Ask for user permission" above). Only https://github.com/<owner>/<repo>[.git]
URLs are supported; prefer one-shot -c options over persisting the gateway
URL or headers into git config.
List usable services
latchkey services list --viable
Lists services that either have stored credentials or can be easily authenticated into via a browser.
Get service-specific info
latchkey services info slack
Returns auth options, credentials status, and developer notes about the service.
Using multiple accounts
It is possible to associate credentials with a specific account (and have credentials for more than a single account per service). The user can do that from the Permissions tab of this machine's options in the Minds app (the key icon in the tabs along the top): "Add connection" lists the services that already have an account here under "Add another account", and the ones that do not under "Connect a new service".
Another way is for you to send a permission request with an "account" in the payload as described above - approving the permission request will prompt the user to sign in. Just double-check the actual resulting account; it may be different than the one requested by you.
You can then reference it in curl calls:
latchkey --account alice@example.com curl ...
The --account option must go right after latchkey.
You can see the existing accounts as keys in the credential
dictionary produced by latchkey services info. An empty string
as the key means "unknown account".
Expired or invalid credentials
When the existing credentials are expired or invalid, there are currently two ways to trigger a new login:
- By re-sending the permission request to the user (use this when there's just a single account for the given service)
- By having the user reconnect the account from the Permissions tab of this machine's options in the Minds app (the key icon in the tabs along the top): "Add connection" then "Add another account" for that service. Tell the user to do that if there is more than one account configured for the given service.
When the gateway is unreachable
Every command above is routed through the Latchkey gateway at
$LATCHKEY_GATEWAY. If it cannot be reached, treat it as
a transient outage. It usually helps if the user restarts the
Minds app. Requests to /permissions and /permission-requests are
routed to the user's computer so they will fail if it's offline.
Notes
- All curl arguments are passed through unchanged
- Return code, stdout and stderr are passed back from curl
- Unless the user explicitly asks about it, don't discuss Latchkey or the technical details (it's easy for the user to get confused).
- Do not ask the user to run Latchkey commands.
- Do not explicitly call
latchkey authcommands! They are run automatically by the Minds app on the user's computer as part of the permission request approval process. Even for services that do not support browser auth, the Minds app usually provides an interface for the user to paste manually obtained credentials (e.g. an API key).
Currently supported services
Latchkey currently offers varying levels of support for the following services: AWS, Calendly, Coolify, Discord, Dropbox, Figma, GitHub, GitLab, Gmail, Google Analytics, Google Calendar, Google Docs, Google Drive, Google Sheets, Google Slides, Linear, Mailchimp, Notion, Ramp, Sentry, Slack, Stripe, Telegram, Todoist, Umami, Yelp, Zoom, and more.