DeepSeek Harness Remote
DeepSeek Harness Remote 可將手機或另一台電腦與你運行 DeepSeek Harness 的電腦安全連接,隨時隨地繼續你的工作,方便隨時查看進度並繼續工作。
AI 翻譯
製作者的介紹僅有上面的一句話。 更多詳情請閱讀儲存庫 README。 儲存庫 README ↗
正在載入完整 README。
檢視完整 README ↗A pen-first note-taking app for Android phones and tablets. Write with a stylus or your finger, organise notes into multi-page files, and sync everything to a folder in your Google Drive or Microsoft OneDrive, with a local cache for offline use.
This app is built with Google Play distribution in mind; the debug and (when configured) signed release builds from CI can also be sideloaded directly.
index.inkfold manifest;
pages are independently portrait or landscape..md), rendered inline in the same
continuous scroll. Tap a Markdown page to edit it (# headings, **bold**,
*italic*, - bullets, `code`).View
(DrawingView) for the drawing surface (reliable MotionEvent/stylus
handling).documents/<id>/
index.inkfold # JSON manifest: ordered list of pages, each with a type
<pageId>.svg # one self-describing SVG per page
The index.inkfold manifest (kotlinx.serialization) lists pages in render
order; each entry has a type, either svg (handwriting) or markdown
(text). Each page SVG renders in any viewer and embeds the exact
stroke data (pressure, tool, colour) in <metadata>, so the app reloads it
losslessly. This folder is also the offline cache. See model/Notebook.kt,
storage/DocumentRepository.kt, and drawing/SvgPage.kt.AuthorizationClient API to grant the
drive.file scope (a system account picker chooses the Google account, then
a consent screen requests Drive access if it isn't already granted), and the
Drive v3 REST API over OkHttp for sync itself. Each document is a sub-folder
of an Inkfold Drive folder; the app lists those sub-folders as documents.
The app can only see files it creates.Files.ReadWrite.AppFolder scope,
and the Microsoft Graph REST API over OkHttp for sync itself. Each document
is a sub-folder of the app's dedicated OneDrive "special" folder, which only
this app can see. Drive and OneDrive are mutually exclusive: signing into
one signs out of the other, since each keeps its own local<->remote sync
state.PdfDocument and Bitmap APIs. On-screen and exported
rendering share one code path (drawing/StrokeRenderer.kt).app/src/main/java/uk/co/mfrost/inkfold/
├── MainActivity.kt # Compose host + sign-in launcher
├── model/Notebook.kt # Notebook / Page / Stroke + manifest model
├── drawing/
│ ├── DrawingView.kt # custom canvas: input, scroll/zoom, undo/redo
│ ├── StrokeRenderer.kt # shared stroke painting (editor + export)
│ └── SvgPage.kt # page <-> self-describing SVG (round-trip)
├── storage/DocumentRepository.kt # folder + SVG store / offline cache
├── sync/DriveSync.kt # Google Drive folder-per-document sync
├── sync/OneDriveSync.kt # Microsoft OneDrive folder-per-document sync
├── export/Exporter.kt # PDF + JPG export
└── ui/ # Compose screens, view model, theme
You need JDK 17 and the Android SDK (compileSdk 36). The Gradle wrapper is
checked in.
# Debug APK
./gradlew assembleDebug
# Output: app/build/outputs/apk/debug/app-debug.apk
# Install on a connected device
./gradlew installDebug
Open the folder in Android Studio (Koala or newer) for day-to-day development.
See CLAUDE.md for the project's style guide (Kotlin/Android
conventions and commit message format) — it applies to human and AI
contributions alike.
.github/workflows/build.yml runs on every push and pull request (and via
manual dispatch). It:
assembleDebug),lintDebug),inkfold-debug-apk),inkfold-release-apk) and a signed release AAB
(inkfold-release-aab) — the AAB is the format the Play Console requires
for a Play Store upload.Download the APK from the Artifacts section of a completed run and sideload it onto your device.
Two workflows let Claude turn a GitHub issue into a pull request, with a human approval step before any AI call is made, so nothing costs API usage or gets implemented unasked:
claude-issue-plan.yml — when an issue is opened, posts a static
comment: "This will be addressed by Claude. The repository owner needs to
comment /approve to continue." No AI call here — it's a plain GitHub
Actions script, so this step is free and doesn't need any secret.claude-issue-implement.yml — only when the repo owner comments
exactly /approve on that issue does Claude get invoked at all: it reads
the issue, implements it, pushes a branch, and opens a pull request
referencing the issue. It never merges; the PR itself is the second
approval gate, reviewed and merged like any other.claude-issue-implement.yml needs an ANTHROPIC_API_KEY repository secret
(Settings → Secrets and variables → Actions) to call Claude; without it,
approving an issue just fails harmlessly (the build.yml CI workflow is
unaffected, and no AI usage is incurred either way until you comment
/approve).
The app works without sign-in; sync is optional. To enable it you must create your own OAuth client, because Google ties OAuth credentials to your app's signing certificate.
.../auth/drive.file scope.uk.co.mfrost.inkfoldkeytool -list -v -keystore ~/.android/debug.keystore \
-alias androiddebugkey -storepass android -keypass android
(CI-built APKs are signed with the runner's debug keystore, so register the
SHA-1 of whichever keystore you actually install from — typically build and
install locally for a stable fingerprint.)No API keys or secrets are stored in the repo — Android OAuth clients are identified by package name + signing certificate, not a secret.
If a sync pass seems stuck or fails, open the overflow menu and choose Sync log to see what the last few sync attempts actually did (each pass now also times out after 2 minutes instead of hanging indefinitely).
The debug APK from CI is signed with the runner's throwaway keystore, whose SHA-1 you can't register. To make Drive sync work from a CI build, sign a release APK with your own keystore stored in GitHub Secrets:
keytool -genkeypair -v -keystore inkfold-release.jks \
-alias inkfold -keyalg RSA -keysize 2048 -validity 10000
Choose a store password and key password (they may be the same).keytool -list -v -keystore inkfold-release.jks -alias inkfold
base64 -w0 inkfold-release.jks # Linux
base64 -i inkfold-release.jks # macOS
KEYSTORE_BASE64 — the base64 string from step 3KEYSTORE_PASSWORD — the store passwordKEY_ALIAS — inkfoldKEY_PASSWORD — the key passwordinkfold-release-apk artifact, signed with your keystore. Install that
one. (Uninstall any earlier debug build first — different signature.)The same keystore SHA-1 also works for a local ./gradlew installDebug only if
you point the debug build at it; simplest is to just use the release artifact.
If the Drive consent screen appears, flickers, and closes without granting
access, the app will show "Drive sign-in failed (error 10)". Error 10 is
DEVELOPER_ERROR: the certificate the installed APK is signed with has no
matching Android OAuth client in your Google Cloud project. Register the SHA-1
of the exact keystore you installed from (see step 3 above) and add your
account as a test user on the consent screen. Other codes: 12501 =
cancelled, 7 = network error.
OneDrive sync is a separate, optional backend to Drive (only one can be signed in at a time). Unlike Drive, enabling it requires a build-time app ID, because Microsoft identity platform apps are identified by an application ID rather than by the APK's signing certificate.
uk.co.mfrost.inkfold://auth
Files.ReadWrite.AppFolder and offline_access
(User.Read is included by default). No admin consent is needed for these
for personal use.echo "oneDriveClientId=<your-client-id>" >> ~/.gradle/gradle.properties
or pass it directly: ./gradlew assembleDebug -PoneDriveClientId=<your-client-id>.
(Gradle also recognises the ORG_GRADLE_PROJECT_oneDriveClientId environment
variable, which is handy for CI.)No client secret is stored in the app or the repo: this is a public client using the PKCE authorization code flow, so the client ID alone (not secret by itself) is all that's needed.