Files
NetMesh/domain/snippetTargets.ts
zhaolei 3c72efcb7f
Some checks failed
build-packages / resolve bundled mosh-client (push) Has been cancelled
build-packages / resolve bundled et-client (push) Has been cancelled
build-packages / build-macos (push) Has been cancelled
build-packages / build-windows (push) Has been cancelled
build-packages / build-linux-x64 (push) Has been cancelled
build-packages / build-linux-arm64 (push) Has been cancelled
build-packages / release (push) Has been cancelled
build-packages / update Nix release metadata (push) Has been cancelled
build-packages / bump homebrew tap (push) Has been cancelled
test / lint-and-test (push) Has been cancelled
AI automation / Route event (push) Has been cancelled
AI automation / Hand reopened issue to maintainers (push) Has been cancelled
AI automation / Clean source issue state (push) Has been cancelled
AI automation / Reconcile handoffs (push) Has been cancelled
AI automation / Classify issue (push) Has been cancelled
AI automation / Claude Code smoke (push) Has been cancelled
AI automation / Review issue follow-up (push) Has been cancelled
AI automation / Publish issue follow-up (push) Has been cancelled
AI automation / Implement with Claude Code (push) Has been cancelled
AI automation / Publish implement PR (push) Has been cancelled
AI automation / Continue queued issue comments (push) Has been cancelled
AI automation / Codex review loop (push) Has been cancelled
AI automation / Publish Codex fix (push) Has been cancelled
AI automation / Clear Codex dispatch marker (push) Has been cancelled
AI automation / Own PR re-request Codex (push) Has been cancelled
AI automation / External PR re-request Codex (push) Has been cancelled
AI automation / Poll Codex reaction / retry (push) Has been cancelled
build-et-binaries / build-linux-x64 (push) Has been cancelled
build-et-binaries / build-linux-arm64 (push) Has been cancelled
build-et-binaries / build-macos-universal (push) Has been cancelled
build-et-binaries / build-windows-x64 (push) Has been cancelled
build-et-binaries / release (push) Has been cancelled
[Init] Initial commit - NetMesh terminal manager
2026-09-13 18:24:01 +08:00

140 lines
4.9 KiB
TypeScript

import type { Host, Snippet } from './models';
import { hostMatchesGroupPath } from './selectHostSelection.ts';
import { normalizeGroupTargetPath } from './hostGroupPathMutations.ts';
import { isScriptSnippet } from './snippetScript.ts';
/** Whether the snippet has a runnable scope (explicit targets or all-hosts flag). */
export function snippetHasRunTargets(
snippet: Pick<Snippet, 'targets' | 'targetGroups' | 'targetsAllHosts'>,
): boolean {
if (snippet.targetsAllHosts) return true;
return Boolean(
(snippet.targets && snippet.targets.length > 0)
|| (snippet.targetGroups && snippet.targetGroups.length > 0),
);
}
export type SnippetTargetHost = Pick<Host, 'id' | 'group'>;
export function snippetTargetsHostExplicitly(
snippet: Pick<Snippet, 'targets'>,
hostId: string,
): boolean {
return Boolean(snippet.targets?.includes(hostId));
}
export function snippetTargetsHostGroup(
snippet: Pick<Snippet, 'targetGroups'>,
host: Pick<Host, 'group'>,
): boolean {
return Boolean(snippet.targetGroups?.some(
(groupPath) => hostMatchesGroupPath(host, normalizeGroupTargetPath(groupPath)),
));
}
/** Keep legacy unscoped onOutput semantics distinct from a deleted/cleared group scope. */
export function resolveSnippetTargetGroupsForSave(
snippet: Pick<Snippet, 'targetGroups' | 'targetsAllHosts'>,
selectedGroupPaths: string[],
): string[] | undefined {
if (snippet.targetsAllHosts) return undefined;
if (snippet.targetGroups === undefined && selectedGroupPaths.length === 0) {
return undefined;
}
return selectedGroupPaths;
}
function normalizeTargetHost(hostOrId?: SnippetTargetHost | string): SnippetTargetHost | undefined {
if (typeof hostOrId === 'string') return { id: hostOrId };
return hostOrId;
}
/** Connectable hosts for manual run / save-and-run. */
export function getRunnableHostsForSnippet(
snippet: Pick<Snippet, 'targets' | 'targetGroups' | 'targetsAllHosts'>,
hosts: Host[],
): Host[] {
const connectable = hosts.filter((host) => host.protocol !== 'serial');
if (snippet.targetsAllHosts) return connectable;
const idSet = new Set(snippet.targets ?? []);
return connectable.filter(
(host) => idSet.has(host.id) || snippetTargetsHostGroup(snippet, host),
);
}
/** Whether a snippet/script applies to the given host based on targets or all-hosts flag. */
export function snippetAppliesToHost(
snippet: Pick<Snippet, 'targets' | 'targetGroups' | 'targetsAllHosts'>,
hostOrId?: SnippetTargetHost | string,
): boolean {
const host = normalizeTargetHost(hostOrId);
if (!host) return false;
if (snippet.targetsAllHosts) return true;
return snippetTargetsHostExplicitly(snippet, host.id)
|| snippetTargetsHostGroup(snippet, host);
}
/** Whether a library/shortcut invocation may run in the current terminal. */
export function snippetCanRunInTerminal(
snippet: Pick<Snippet, 'targets' | 'targetGroups' | 'targetsAllHosts'>,
host: SnippetTargetHost,
): boolean {
if (snippet.targetsAllHosts) return true;
const hasScopedTargets = Boolean(
snippet.targets?.length || snippet.targetGroups !== undefined,
);
return !hasScopedTargets || snippetAppliesToHost(snippet, host);
}
/**
* onOutput triggers listen on the active terminal session. When no explicit targets
* are configured, they apply to whichever host the session is connected to.
*/
export function snippetAppliesToOutputTrigger(
snippet: Pick<Snippet, 'trigger' | 'targets' | 'targetGroups' | 'targetsAllHosts'>,
hostOrId?: SnippetTargetHost | string,
): boolean {
if (snippet.trigger !== 'onOutput') return false;
const host = normalizeTargetHost(hostOrId);
if (!host) return false;
if (snippet.targetsAllHosts) return true;
if (snippet.targets?.length || snippet.targetGroups !== undefined) {
return snippetAppliesToHost(snippet, host);
}
return true;
}
/** Scripts explicitly linked to a host via targets (excludes all-hosts scripts). */
export function getScriptsLinkedToHost(snippets: Snippet[], hostId: string): Snippet[] {
return snippets.filter(
(snippet) => isScriptSnippet(snippet)
&& !snippet.targetsAllHosts
&& Boolean(snippet.targets?.includes(hostId)),
);
}
export function linkHostToScript(snippet: Snippet, hostId: string): Snippet {
const targets = snippet.targets ?? [];
if (targets.includes(hostId)) {
return { ...snippet, targetsAllHosts: undefined };
}
return {
...snippet,
targets: [...targets, hostId],
targetsAllHosts: undefined,
};
}
export function unlinkHostFromScript(snippet: Snippet, hostId: string): Snippet {
const targets = snippet.targets ?? [];
if (!targets.includes(hostId)) return snippet;
const nextTargets = targets.filter((id) => id !== hostId);
return { ...snippet, targets: nextTargets.length > 0 ? nextTargets : undefined };
}
export function unlinkHostFromScripts(snippets: Snippet[], hostId: string, scriptId: string): Snippet[] {
return snippets.map((snippet) => (
snippet.id === scriptId ? unlinkHostFromScript(snippet, hostId) : snippet
));
}