deep-links
Skill173 starsChanged 5 months agoApache-2.0View on GitHub
Deep links in ToolHive Studio. Use when implementing, debugging, or asking about deep link features (toolhive-gui:// protocol), adding new deep link intents, understanding the deep link architecture, IPC model, or platform/packaging support.
In this repository20 other files it gives its agents.
- AGENTS.mdAGENTS.md
- CLAUDE.mdCLAUDE.md
- .github/copilot-instructions.mdCopilot instructions
- bug-fix-tddSkill
- devcontainer-devSkill
- security-vuln-remediationSkill
- skill-creatorSkill
- skill-editorSkill
- testing-api-assertionsSkill
- testing-api-overridesSkill
- testing-with-api-mocksSkill
- bug-fix-tddSkill
- deep-linksSkill
- devcontainer-devSkill
- security-vuln-remediationSkill
- skill-creatorSkill
- skill-editorSkill
- testing-api-assertionsSkill
- testing-api-overridesSkill
- testing-with-api-mocksSkill
The file
We split this skill into the rules it gives an agent
1 rule. About 0 are common, 0 are distinctive, and 1 is very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈0
Distinctive · estimate
This author's own choices. Worth a look.
1
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Very specific: line 66
All changes happen in common/deep-links.ts:
≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
Deep Links in ToolHive Studio
Deep links allow external systems (browsers, terminals, other apps) to trigger navigation inside ToolHive Desktop via the toolhive-gui:// custom protocol.
About this document: Much of the content in the reference docs is the result of research into how other Electron apps implement deep links. Some design decisions are implemented; others describe the intended direction but are not yet in the codebase. The base skill reflects the current implementation. The reference docs reflect the research and design intent — read them with that in mind, and update them when relevant implementation decisions change.
URL Schema
toolhive-gui://v1/<intent>[?<query>]Examples:
toolhive-gui://v1/open-registry-server-detail?serverName=fetch— open a registry server detail pagetoolhive-gui://v1/open-registry-server-install?serverName=fetch— open the registry server detail page and auto-open the install dialogtoolhive-gui://v1/open-registry-skill-detail?namespace=io.github.stacklok&skillName=skill-creator— open a registry skill detail pagetoolhive-gui://v1/open-registry-skill-install?namespace=io.github.stacklok&skillName=skill-creator&version=v1.0.0— open the skill detail page and auto-open the install dialog with the reference (and optional?versiontag) prefilled. Tag-only — OCI digests (sha256:…) are intentionally not supported becausesafeIdentifierrejects colons; users wanting digest pinning can paste it into the dialog directly.
The v1 segment is the version. The intent is a kebab-case action name. Query params carry intent-specific data.
Current Implementation
Key Files
| File | Role |
common/deep-links.ts | Single source of truth. All deep link definitions: intent name, Zod param schema, navigation target. |
main/src/deep-links/parse.ts | Parses and validates a raw URL string using the schemas from common/deep-links.ts. |
main/src/deep-links/index.ts | Entry point: extracts URL from argv (Windows/Linux), waits for window ready, dispatches via IPC. |
main/src/deep-links/squirrel.ts | Squirrel.Windows-specific protocol registration. |
IPC Channel
deep-link-navigation — sent main → renderer as a NavigateTarget ({ to: string; params?: Record<string, string> }).
The renderer receives this and calls the TanStack Router navigate() directly.
How It Works (Current)
- Protocol registration: On app start,
app.setAsDefaultProtocolClient('toolhive-gui')registers the protocol. On Windows with Squirrel,registerProtocolWithSquirrel()is called instead (seesquirrel.ts). - URL extraction: On Windows/Linux, the URL arrives in
process.argv.extractDeepLinkFromArgs()scans for the firsttoolhive-gui://argument (safe against argv injection — see patterns doc). - Parse + validate:
parseDeepLinkUrl()parses the URL and runs it through the Zod discriminated union schema defined incommon/deep-links.ts. Invalid links resolve toshowNotFound. - Window ready:
waitForMainWindowReady()polls until the window is visible and not loading before dispatching. - Dispatch:
resolveDeepLinkTarget()converts the validated intent to aNavigateTarget, which is sent to the renderer via thedeep-link-navigationIPC channel. - Renderer: The renderer listens for
deep-link-navigationand callsnavigate(target).
Current Limitations vs. Design Intent
The current implementation only supports read (navigate) operations. The design doc proposes a confirmation flow for write/destructive operations (C/U/D), but this is not yet implemented. The IPC sends a pre-resolved NavigateTarget rather than a raw parsed intent — this simplified the initial implementation. See design doc for the full intended model.
How to Add a New Deep Link
All changes happen in common/deep-links.ts:
// 1. Define the new intent using v1DeepLink()
export const myNewIntent = v1DeepLink({
intent: 'my-new-intent', // kebab-case, matches URL path segment
params: z.object({
someParam: safeIdentifier, // use safeIdentifier for user-supplied strings
}),
navigate: (params) => ({
to: '/some-route/$id', // TanStack Router route
params: { id: params.someParam },
}),
})
// 2. Add to allDeepLinks array
const allDeepLinks = [
openRegistryServerDetail,
showNotFound,
myNewIntent,
] as const
// 3. Add to deepLinkSchema discriminated union
export const deepLinkSchema = z.discriminatedUnion('intent', [
openRegistryServerDetail.schema,
showNotFound.schema,
myNewIntent.schema, // ← add here
])Test manually:
./node_modules/.bin/electron . "toolhive-gui://v1/my-new-intent?someParam=value"
safeIdentifieris defined asz.string().regex(/^[a-zA-Z0-9_.-]+$/)— use it for any param that could be user-supplied to prevent injection.
Reference Documents
For deeper background, see:
- OS & Packaging Support — Platform-specific registration requirements (Windows, Linux, macOS) and packaging format considerations (Squirrel, Flatpak, .deb, .rpm, .dmg, AppImage, MSIX, etc.). Largely research/prior art.
- Observed Patterns — Patterns from VS Code, GitHub Desktop, Mattermost, Element, and others: URL sanitization, argv injection, security confirmations, waiting-for-readiness patterns, telemetry.
- Design & Decisions — Full design rationale, IPC model, error handling strategy, queue management, testing approach, and the decisions log. Some sections describe planned future behaviour not yet implemented.
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

