feat: finish the tailscale plugin, including SSH auth (B3)

This commit is contained in:
LukeGus committed 2026-09-23 09:41:20 -05:00
1 parent c77301577f
commit d2ff5fc78f
35 files changed
+1251 -526

No files matched your search

+26
View File
@@ -448,6 +448,20 @@ export type PluginSshAuthOutcome =
message: string;
};
export type PluginSshBannerDecision =
| {
/** Hold the connect timeout open and wait for the out-of-band step. */
action: "hold";
timeoutMs: number;
message: string;
details?: Record<string, unknown>;
}
| {
/** The out-of-band step finished; resume the normal connect timeout. */
action: "release";
details?: Record<string, unknown>;
};
export interface PluginKeyboardInteractivePrompt {
prompt: string;
echo?: boolean;
@@ -644,6 +658,18 @@ export interface PluginSshAuthProvider {
methodNotAvailable: boolean;
},
) => PluginSshAuthOutcome | undefined;
/**
* Claims an ssh2 "banner" event during the handshake, for a server that
* holds the connection open pending an out-of-band step (Tailscale SSH
* check mode). A transport that can show this sends `<type>_check_required`
* / `<type>_check_completed` over its socket, with `details` merged in.
* Return nothing to leave the banner unhandled.
*/
onBanner?: (
banner: string,
host: PluginSshHost,
env: PluginSshAuthEnv,
) => PluginSshBannerDecision | undefined;
/** Starts the browser step behind an interaction-required outcome. */
startInteraction?: (request: {
userId: string;
+12
View File
@@ -610,6 +610,7 @@ export interface PluginHostBridge {
useTabs: () => TabsApi;
invokeAction: (id: string, ...args: unknown[]) => Promise<unknown>;
useSshAuthTypes: () => { types: SshAuthTypeInfo[]; loaded: boolean };
useSlotContributions: (slotId: string) => SlotContribution[];
}
let host: PluginHostBridge | null = null;
@@ -712,6 +713,17 @@ export function useSshAuthTypes(): {
return requireHost().useSshAuthTypes();
}
/**
* The visible contributions to a slot another plugin owns, with their
* metadata (id, titleKey, component). For an owner that needs to build a
* catalog from what was contributed rather than just render it in place, e.g.
* host-metrics listing manager cards plugins added to "host-metrics.managers"
* next to its own. Same permission and `when` filtering as ComponentSlot.
*/
export function useSlotContributions(slotId: string): SlotContribution[] {
return requireHost().useSlotContributions(slotId);
}
/**
* Runs an action another plugin registered, from anywhere in a plugin's code.
* Resolves undefined when nobody registered it, so an optional dependency
+1
View File
@@ -111,6 +111,7 @@ const offline = () =>
})),
loaded: true,
}),
useSlotContributions: () => [],
};
afterEach(() => {