Skip to content

AGENTS.md

254 lines · 7.9 KB · Markdown
Raw

@kentcdodds/dropbox — agent notes

Human setup and intent live in README.md. This file is for agents: imports, positive playbooks, and copy-paste execute snippets. Auth is the saved OAuth integration named dropbox — never paste tokens. Do not disable live webhooks or jobs. Do not use the Dropbox website UI — use these MCP package exports.

Auth / integration

ItemValue
Integration namedropbox
Reconnecthttps://kody.codes/connect/oauth?provider=dropbox

Import paths

ExportImport
overviewkody:@kentcdodds/dropbox
smoke-testkody:@kentcdodds/dropbox/smoke-test
get-current-accountkody:@kentcdodds/dropbox/get-current-account
get-space-usagekody:@kentcdodds/dropbox/get-space-usage
list-folderkody:@kentcdodds/dropbox/list-folder
get-metadatakody:@kentcdodds/dropbox/get-metadata
searchkody:@kentcdodds/dropbox/search
downloadkody:@kentcdodds/dropbox/download
uploadkody:@kentcdodds/dropbox/upload
upload-largekody:@kentcdodds/dropbox/upload-large
upload-from-urlkody:@kentcdodds/dropbox/upload-from-url
deletekody:@kentcdodds/dropbox/delete
create-shared-linkkody:@kentcdodds/dropbox/create-shared-link
ensure-shared-direct-urlkody:@kentcdodds/dropbox/ensure-shared-direct-url
pathskody:@kentcdodds/dropbox/paths
requestkody:@kentcdodds/dropbox/request

Prefer static kody:@kentcdodds/dropbox/... imports from execute. Do not lead with packages.invoke.

App-folder path primer

  1. Call listFolder({ path: '', limit: 100 }) first so you see the real root.
  2. Kody-writable files live under /Apps/Kody/… (constant APP_FOLDER_ROOT), not /Kody/….
  3. Use underAppFolder('notes/a.txt') → /Apps/Kody/notes/a.txt.
  4. A 409 / path/not_found on a write often means the path is missing the /Apps/Kody prefix — helpers suggest the corrected path.
import listFolder from 'kody:@kentcdodds/dropbox/list-folder'
import { underAppFolder } from 'kody:@kentcdodds/dropbox/paths'

export default async function main() {
	const root = await listFolder({ path: '', limit: 50 })
	return {
		rootNames: root.entries.map((e) => e.path_display ?? e.name),
		exampleDest: underAppFolder('notes/hello.txt'),
	}
}

Upload a file (small vs large)

Hard cutoff: 120 KiB binary (UPLOAD_SINGLE_MAX_BYTES = 122880). Base64 in MCP params bloats faster than raw bytes (~200KB+ MCP payloads hurt). Put bytesBase64 only in params, never megabytes inside the code string.

SizeExportNotes
Tiny text / < 120 KiB binary./uploadtext or small bytesBase64 in params
≥ 120 KiB binary./upload-largeSession: start → append (~48 KiB chunks) → finish (closes)
Either./uploadAuto-delegates to ./upload-large when over threshold
Small upload (params)
import upload from 'kody:@kentcdodds/dropbox/upload'

export default async function main() {
	return await upload({
		path: '/Apps/Kody/notes/hello.txt',
		text: 'Hello from Kody',
		mode: 'overwrite',
		confirm: true,
	})
}
Large upload (bytesBase64 in params — never in code)
import uploadLarge from 'kody:@kentcdodds/dropbox/upload-large'

export default async function main(params) {
	// Pass { path, bytesBase64, mode, confirm } via execute params.
	return await uploadLarge({
		path: params.path, // e.g. '/Apps/Kody/inbox/report.bin'
		bytesBase64: params.bytesBase64,
		mode: params.mode ?? 'overwrite',
		confirm: true,
	})
}

mode: 'overwrite' maps to Dropbox commit mode: "overwrite" (string tag). Finish closes the session — do not call append after finish.

dryRun plan:

import uploadLarge from 'kody:@kentcdodds/dropbox/upload-large'

export default async function main(params) {
	return await uploadLarge({
		path: params.path ?? '/Apps/Kody/notes/large.bin',
		bytesBase64: params.bytesBase64 ?? '',
		mode: 'overwrite',
		dryRun: true,
	})
}
Computer agents (local file → Dropbox)

Read the local file on the computer, base64-encode it, then call ./upload or ./upload-large with bytesBase64 in params:

// Pseudocode on the computer agent — not inside Kody Workers:
// const bytesBase64 = Buffer.from(await fs.readFile(localPath)).toString('base64')
import upload from 'kody:@kentcdodds/dropbox/upload'

export default async function main(params) {
	return await upload({
		path: params.path, // '/Apps/Kody/...'
		bytesBase64: params.bytesBase64,
		mode: 'overwrite',
		confirm: true,
	})
}
MCP-only / phone (no large base64)

Chat cannot carry large base64. Options:

  1. Compress / use a smaller WebP (or similar) under ~120 KiB binary, then ./upload.
  2. If you have a public HTTP(S) URL Dropbox can fetch, use ./upload-from-url (files/save_url):
import uploadFromUrl from 'kody:@kentcdodds/dropbox/upload-from-url'

export default async function main(params) {
	return await uploadFromUrl({
		path: params.path, // '/Apps/Kody/inbox/photo.webp'
		url: params.url, // public https://...
		confirm: true,
	})
}
Do not use
  • files/get_temporary_upload_link — dead end for agents; prefer ./upload / ./upload-large.
  • Dropbox website login / UI — use these package exports over MCP instead.

Share → direct download URL (Fable / curl)

After upload, call ./ensure-shared-direct-url for a browser/curl-friendly content URL on dl.dropboxusercontent.com with raw=1. Reuses an existing link via list_shared_links when create returns “already exists”.

import ensureSharedDirectUrl from 'kody:@kentcdodds/dropbox/ensure-shared-direct-url'

export default async function main(params) {
	return await ensureSharedDirectUrl({
		path: params.path, // '/Apps/Kody/notes/guide.pdf'
		confirm: true,
	})
	// => { path, url, directUrl, reused, link }
}

dryRun:

import ensureSharedDirectUrl from 'kody:@kentcdodds/dropbox/ensure-shared-direct-url'

export default async function main() {
	return await ensureSharedDirectUrl({
		path: '/Apps/Kody/notes/guide.pdf',
		dryRun: true,
	})
}

Smoke test (read-only)

import smokeTest from 'kody:@kentcdodds/dropbox/smoke-test'

export default async function main() {
	return await smokeTest()
	// => { ok: true, hasAccountId, hasDisplayName, accountType } — no PII fields
}

dryRun / confirm (writes)

upload, upload-large, upload-from-url, delete, create-shared-link, ensure-shared-direct-url, and unknown/mutating ./request endpoints require confirm: true after explicit user approval. Use dryRun: true to preview without calling Dropbox. Documented read-only ./request endpoints do not need confirmation.

import deleteDropboxPath from 'kody:@kentcdodds/dropbox/delete'

export default async function main() {
	return await deleteDropboxPath({
		path: '/Apps/Kody/notes/old.txt',
		dryRun: true,
	})
}

Generic request (known read-only endpoint — no confirm):

import request from 'kody:@kentcdodds/dropbox/request'

export default async function main() {
	return await request({
		endpoint: 'files/get_metadata',
		body: { path: '/Apps/Kody/notes/todo.txt' },
	})
}

Edge cases

  • Paths start with /; use '' for the account root on list/search.
  • Prefer /Apps/Kody/... for writes; list root before inventing paths.
  • download returns { metadata, bytesBase64, byteLength, contentType }.
  • upload: pass exactly one of text or bytesBase64; mode is add (default) or overwrite. Over 120 KiB binary → auto ./upload-large.
  • upload-large: always pass bytesBase64 in params; session chunks ~48 KiB; finish closes the session.
  • list-folder / search: continue with returned cursor while has_more.
  • Prefer dedicated helpers over assembling ./request when one exists.
  • Never paste Dropbox OAuth tokens into chat or logs.