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
494 lines
18 KiB
TypeScript
494 lines
18 KiB
TypeScript
import type { Host, ScriptLanguage, ScriptTrigger, Snippet, SnippetKind } from './models';
|
|
import {
|
|
getHostConnectScriptIds,
|
|
removeHostConnectScript,
|
|
resolveConnectScriptsForHost,
|
|
syncHostsForSnippetTargetChange,
|
|
} from './hostConnectScripts.ts';
|
|
import { isScriptSnippet } from './snippetScript.ts';
|
|
import { getNextVaultOrder } from './vaultOrder.ts';
|
|
import { normalizeGroupTargetPaths } from './hostGroupPathMutations.ts';
|
|
|
|
export type SnippetAgentListItem = ReturnType<typeof serializeSnippetForAgentList>;
|
|
export type SnippetAgentDetail = ReturnType<typeof serializeSnippetForAgentGet>;
|
|
|
|
export function filterScriptSnippets(snippets: Snippet[]): Snippet[] {
|
|
return snippets.filter(isScriptSnippet);
|
|
}
|
|
|
|
export function serializeSnippetForAgentList(snippet: Snippet) {
|
|
return {
|
|
id: snippet.id,
|
|
label: snippet.label,
|
|
kind: snippet.kind ?? 'snippet',
|
|
tags: snippet.tags ?? [],
|
|
targets: snippet.targets ?? [],
|
|
// Preserve the distinction between a legacy unscoped script (missing) and
|
|
// an explicit empty group scope (disabled after its last group is deleted).
|
|
targetGroups: snippet.targetGroups,
|
|
targetsAllHosts: snippet.targetsAllHosts ?? false,
|
|
package: snippet.package,
|
|
shortkey: snippet.shortkey,
|
|
noAutoRun: snippet.noAutoRun,
|
|
multiLineRunMode: snippet.multiLineRunMode,
|
|
language: snippet.language,
|
|
description: snippet.description,
|
|
trigger: snippet.trigger,
|
|
triggerPattern: snippet.triggerPattern,
|
|
};
|
|
}
|
|
|
|
export function serializeSnippetForAgentGet(snippet: Snippet) {
|
|
return {
|
|
...serializeSnippetForAgentList(snippet),
|
|
command: snippet.command,
|
|
};
|
|
}
|
|
|
|
export function serializeScriptForAgentList(snippet: Snippet) {
|
|
return serializeSnippetForAgentList(snippet);
|
|
}
|
|
|
|
export function serializeScriptForAgentGet(snippet: Snippet) {
|
|
return serializeSnippetForAgentGet(snippet);
|
|
}
|
|
|
|
export type SnippetAgentDraft = {
|
|
label?: unknown;
|
|
command?: unknown;
|
|
kind?: unknown;
|
|
tags?: unknown;
|
|
targets?: unknown;
|
|
targetGroups?: unknown;
|
|
targetsAllHosts?: unknown;
|
|
package?: unknown;
|
|
shortkey?: unknown;
|
|
noAutoRun?: unknown;
|
|
multiLineRunMode?: unknown;
|
|
language?: unknown;
|
|
description?: unknown;
|
|
trigger?: unknown;
|
|
triggerPattern?: unknown;
|
|
};
|
|
|
|
export type SnippetAgentPatch = SnippetAgentDraft;
|
|
|
|
const VALID_KINDS = new Set<SnippetKind>(['snippet', 'script']);
|
|
const VALID_TRIGGERS = new Set<ScriptTrigger>(['manual', 'onConnect', 'onOutput']);
|
|
const VALID_LANGUAGES = new Set<ScriptLanguage>(['javascript', 'python']);
|
|
const VALID_MULTI_LINE_RUN_MODES = new Set<NonNullable<Snippet['multiLineRunMode']>>(['paste', 'lineDelay']);
|
|
|
|
function parseKind(raw: unknown, fallback: SnippetKind = 'snippet'): SnippetKind | { error: string } {
|
|
if (raw === undefined || raw === null || raw === '') return fallback;
|
|
const value = String(raw).trim();
|
|
if (VALID_KINDS.has(value as SnippetKind)) return value as SnippetKind;
|
|
return { error: `kind must be "snippet" or "script", got "${value}".` };
|
|
}
|
|
|
|
function parseTrigger(raw: unknown): ScriptTrigger | undefined | { error: string } {
|
|
if (raw === undefined || raw === null || raw === '') return undefined;
|
|
const value = String(raw).trim();
|
|
if (VALID_TRIGGERS.has(value as ScriptTrigger)) return value as ScriptTrigger;
|
|
return { error: `trigger must be manual, onConnect, or onOutput, got "${value}".` };
|
|
}
|
|
|
|
function parseLanguage(raw: unknown): ScriptLanguage | undefined | { error: string } {
|
|
if (raw === undefined || raw === null || raw === '') return undefined;
|
|
const value = String(raw).trim();
|
|
if (VALID_LANGUAGES.has(value as ScriptLanguage)) return value as ScriptLanguage;
|
|
return { error: `language must be javascript or python, got "${value}".` };
|
|
}
|
|
|
|
function parseMultiLineRunMode(raw: unknown): Snippet['multiLineRunMode'] | { error: string } {
|
|
if (raw === undefined || raw === null || raw === '') return undefined;
|
|
const value = String(raw).trim();
|
|
if (VALID_MULTI_LINE_RUN_MODES.has(value as NonNullable<Snippet['multiLineRunMode']>)) {
|
|
return value as NonNullable<Snippet['multiLineRunMode']>;
|
|
}
|
|
return { error: `multiLineRunMode must be paste or lineDelay, got "${value}".` };
|
|
}
|
|
|
|
function parseOptionalBoolean(raw: unknown): boolean | undefined {
|
|
if (raw === undefined || raw === null || raw === '') return undefined;
|
|
if (typeof raw === 'boolean') return raw;
|
|
const normalized = String(raw).trim().toLowerCase();
|
|
if (normalized === 'true' || normalized === '1' || normalized === 'yes') return true;
|
|
if (normalized === 'false' || normalized === '0' || normalized === 'no') return false;
|
|
return undefined;
|
|
}
|
|
|
|
function parseStringArray(raw: unknown, fieldName: string): string[] | undefined | { error: string } {
|
|
if (raw === undefined || raw === null) return undefined;
|
|
if (Array.isArray(raw)) {
|
|
return raw.map((entry) => String(entry).trim()).filter(Boolean);
|
|
}
|
|
if (typeof raw !== 'string') {
|
|
return { error: `${fieldName} must be a string or array.` };
|
|
}
|
|
const trimmed = raw.trim();
|
|
if (!trimmed) return [];
|
|
if (trimmed.startsWith('[')) {
|
|
try {
|
|
const parsed = JSON.parse(trimmed) as unknown;
|
|
if (!Array.isArray(parsed)) return { error: `${fieldName} must be a JSON array.` };
|
|
return parsed.map((entry) => String(entry).trim()).filter(Boolean);
|
|
} catch {
|
|
return { error: `${fieldName} must be valid JSON array.` };
|
|
}
|
|
}
|
|
return trimmed.split(',').map((entry) => entry.trim()).filter(Boolean);
|
|
}
|
|
|
|
function parseTags(raw: unknown): string[] | undefined | { error: string } {
|
|
return parseStringArray(raw, 'tags');
|
|
}
|
|
|
|
function parseTargets(raw: unknown): string[] | undefined | { error: string } {
|
|
return parseStringArray(raw, 'targets');
|
|
}
|
|
|
|
function generateSnippetId(): string {
|
|
return `snippet-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
|
|
}
|
|
|
|
function validateTriggerPattern(trigger: ScriptTrigger | undefined, pattern: string | undefined): string | undefined | { error: string } {
|
|
if (trigger === 'onOutput') {
|
|
if (!pattern?.trim()) {
|
|
return { error: 'triggerPattern is required when trigger is onOutput.' };
|
|
}
|
|
try {
|
|
RegExp(pattern);
|
|
} catch {
|
|
return { error: `triggerPattern is not a valid regex: "${pattern}".` };
|
|
}
|
|
}
|
|
return pattern?.trim() || undefined;
|
|
}
|
|
|
|
export function buildSnippetFromAgentDraft(
|
|
draft: SnippetAgentDraft,
|
|
existingSnippets: Snippet[],
|
|
options: { forceKind?: SnippetKind } = {},
|
|
): { ok: true; snippet: Snippet } | { ok: false; error: string } {
|
|
const label = typeof draft.label === 'string' ? draft.label.trim() : '';
|
|
if (!label) return { ok: false, error: 'label is required.' };
|
|
|
|
const command = typeof draft.command === 'string' ? draft.command : '';
|
|
if (!command.trim()) return { ok: false, error: 'command is required.' };
|
|
|
|
const kindResult = parseKind(draft.kind, options.forceKind ?? 'snippet');
|
|
if (typeof kindResult === 'object' && 'error' in kindResult) {
|
|
return { ok: false, error: kindResult.error };
|
|
}
|
|
const kind = options.forceKind ?? kindResult;
|
|
|
|
const triggerResult = parseTrigger(draft.trigger);
|
|
if (triggerResult && typeof triggerResult === 'object' && 'error' in triggerResult) {
|
|
return { ok: false, error: triggerResult.error };
|
|
}
|
|
const trigger = triggerResult ?? (kind === 'script' ? 'manual' : undefined);
|
|
|
|
const languageResult = parseLanguage(draft.language);
|
|
if (languageResult && typeof languageResult === 'object' && 'error' in languageResult) {
|
|
return { ok: false, error: languageResult.error };
|
|
}
|
|
|
|
const tags = parseTags(draft.tags);
|
|
if (tags && 'error' in tags) return { ok: false, error: tags.error };
|
|
|
|
const targetsAllHosts = parseOptionalBoolean(draft.targetsAllHosts);
|
|
let targets: string[] | undefined;
|
|
let targetGroups: string[] | undefined;
|
|
if (!targetsAllHosts) {
|
|
const targetsResult = parseTargets(draft.targets);
|
|
if (targetsResult && 'error' in targetsResult) return { ok: false, error: targetsResult.error };
|
|
targets = targetsResult && targetsResult.length > 0 ? targetsResult : undefined;
|
|
const targetGroupsResult = parseStringArray(draft.targetGroups, 'targetGroups');
|
|
if (targetGroupsResult && !Array.isArray(targetGroupsResult)) {
|
|
return { ok: false, error: targetGroupsResult.error };
|
|
}
|
|
targetGroups = draft.targetGroups === undefined
|
|
? undefined
|
|
: Array.isArray(targetGroupsResult)
|
|
? normalizeGroupTargetPaths(targetGroupsResult)
|
|
: undefined;
|
|
}
|
|
|
|
const triggerPatternRaw = typeof draft.triggerPattern === 'string' ? draft.triggerPattern : undefined;
|
|
const triggerPattern = validateTriggerPattern(trigger, triggerPatternRaw);
|
|
if (triggerPattern && typeof triggerPattern === 'object' && 'error' in triggerPattern) {
|
|
return { ok: false, error: triggerPattern.error };
|
|
}
|
|
const multiLineRunMode = parseMultiLineRunMode(draft.multiLineRunMode);
|
|
if (multiLineRunMode && typeof multiLineRunMode === 'object' && 'error' in multiLineRunMode) {
|
|
return { ok: false, error: multiLineRunMode.error };
|
|
}
|
|
|
|
const snippet: Snippet = {
|
|
id: generateSnippetId(),
|
|
label,
|
|
command,
|
|
kind,
|
|
tags: tags && tags.length > 0 ? tags : undefined,
|
|
targets: targetsAllHosts ? undefined : targets,
|
|
targetGroups: targetsAllHosts ? undefined : targetGroups,
|
|
targetsAllHosts: targetsAllHosts || undefined,
|
|
package: typeof draft.package === 'string' && draft.package.trim() ? draft.package.trim() : undefined,
|
|
shortkey: typeof draft.shortkey === 'string' && draft.shortkey.trim() ? draft.shortkey.trim() : undefined,
|
|
noAutoRun: parseOptionalBoolean(draft.noAutoRun),
|
|
multiLineRunMode,
|
|
language: languageResult ?? (kind === 'script' ? 'javascript' : undefined),
|
|
description: typeof draft.description === 'string' && draft.description.trim()
|
|
? draft.description.trim()
|
|
: undefined,
|
|
trigger,
|
|
triggerPattern: typeof triggerPattern === 'string' ? triggerPattern : undefined,
|
|
order: getNextVaultOrder(existingSnippets),
|
|
};
|
|
|
|
return { ok: true, snippet };
|
|
}
|
|
|
|
export function applySnippetAgentPatch(
|
|
existing: Snippet,
|
|
patch: SnippetAgentPatch,
|
|
options: { forceKind?: SnippetKind } = {},
|
|
): { ok: true; snippet: Snippet; prevTargetIds?: string[] } | { ok: false; error: string } {
|
|
const label = patch.label !== undefined
|
|
? (typeof patch.label === 'string' ? patch.label.trim() : '')
|
|
: existing.label;
|
|
if (!label) return { ok: false, error: 'label cannot be empty.' };
|
|
|
|
const command = patch.command !== undefined
|
|
? (typeof patch.command === 'string' ? patch.command : '')
|
|
: existing.command;
|
|
if (!command.trim()) return { ok: false, error: 'command cannot be empty.' };
|
|
|
|
let kind = existing.kind ?? 'snippet';
|
|
if (options.forceKind) {
|
|
kind = options.forceKind;
|
|
} else if (patch.kind !== undefined) {
|
|
const kindResult = parseKind(patch.kind, kind);
|
|
if (typeof kindResult === 'object' && 'error' in kindResult) {
|
|
return { ok: false, error: kindResult.error };
|
|
}
|
|
kind = kindResult;
|
|
}
|
|
|
|
let trigger = existing.trigger;
|
|
if (patch.trigger !== undefined) {
|
|
const triggerResult = parseTrigger(patch.trigger);
|
|
if (triggerResult && typeof triggerResult === 'object' && 'error' in triggerResult) {
|
|
return { ok: false, error: triggerResult.error };
|
|
}
|
|
trigger = triggerResult;
|
|
}
|
|
|
|
let language = existing.language;
|
|
if (patch.language !== undefined) {
|
|
const languageResult = parseLanguage(patch.language);
|
|
if (languageResult && typeof languageResult === 'object' && 'error' in languageResult) {
|
|
return { ok: false, error: languageResult.error };
|
|
}
|
|
language = languageResult;
|
|
}
|
|
|
|
const tags = patch.tags !== undefined ? parseTags(patch.tags) : existing.tags;
|
|
if (tags && 'error' in tags) return { ok: false, error: tags.error };
|
|
|
|
const prevTargetIds = existing.targets ? [...existing.targets] : undefined;
|
|
|
|
let targetsAllHosts = existing.targetsAllHosts;
|
|
if (
|
|
(patch.targets !== undefined || patch.targetGroups !== undefined)
|
|
&& patch.targetsAllHosts === undefined
|
|
) {
|
|
targetsAllHosts = false;
|
|
} else if (patch.targetsAllHosts !== undefined) {
|
|
targetsAllHosts = parseOptionalBoolean(patch.targetsAllHosts) ?? false;
|
|
}
|
|
|
|
let targets = existing.targets;
|
|
let targetGroups = existing.targetGroups;
|
|
if (
|
|
patch.targets !== undefined
|
|
|| patch.targetGroups !== undefined
|
|
|| patch.targetsAllHosts !== undefined
|
|
) {
|
|
if (targetsAllHosts) {
|
|
targets = undefined;
|
|
targetGroups = undefined;
|
|
} else {
|
|
const targetsResult = parseTargets(patch.targets ?? existing.targets ?? []);
|
|
if (targetsResult && 'error' in targetsResult) return { ok: false, error: targetsResult.error };
|
|
targets = targetsResult && targetsResult.length > 0 ? targetsResult : undefined;
|
|
const targetGroupsResult = parseStringArray(
|
|
patch.targetGroups ?? existing.targetGroups ?? [],
|
|
'targetGroups',
|
|
);
|
|
if (targetGroupsResult && !Array.isArray(targetGroupsResult)) {
|
|
return { ok: false, error: targetGroupsResult.error };
|
|
}
|
|
targetGroups = patch.targetGroups === undefined
|
|
? existing.targetGroups
|
|
: Array.isArray(targetGroupsResult)
|
|
? normalizeGroupTargetPaths(targetGroupsResult)
|
|
: undefined;
|
|
}
|
|
}
|
|
|
|
const triggerPatternRaw = patch.triggerPattern !== undefined
|
|
? (typeof patch.triggerPattern === 'string' ? patch.triggerPattern : '')
|
|
: existing.triggerPattern;
|
|
const triggerPattern = validateTriggerPattern(trigger, triggerPatternRaw);
|
|
if (triggerPattern && typeof triggerPattern === 'object' && 'error' in triggerPattern) {
|
|
return { ok: false, error: triggerPattern.error };
|
|
}
|
|
const multiLineRunMode = patch.multiLineRunMode !== undefined
|
|
? parseMultiLineRunMode(patch.multiLineRunMode)
|
|
: existing.multiLineRunMode;
|
|
if (multiLineRunMode && typeof multiLineRunMode === 'object' && 'error' in multiLineRunMode) {
|
|
return { ok: false, error: multiLineRunMode.error };
|
|
}
|
|
|
|
const snippet: Snippet = {
|
|
...existing,
|
|
label,
|
|
command,
|
|
kind,
|
|
tags: tags && tags.length > 0 ? tags : undefined,
|
|
targets,
|
|
targetGroups,
|
|
targetsAllHosts: targetsAllHosts || undefined,
|
|
package: patch.package !== undefined
|
|
? (typeof patch.package === 'string' && patch.package.trim() ? patch.package.trim() : undefined)
|
|
: existing.package,
|
|
shortkey: patch.shortkey !== undefined
|
|
? (typeof patch.shortkey === 'string' && patch.shortkey.trim() ? patch.shortkey.trim() : undefined)
|
|
: existing.shortkey,
|
|
noAutoRun: patch.noAutoRun !== undefined
|
|
? parseOptionalBoolean(patch.noAutoRun)
|
|
: existing.noAutoRun,
|
|
multiLineRunMode,
|
|
language: language ?? (kind === 'script' ? 'javascript' : undefined),
|
|
description: patch.description !== undefined
|
|
? (typeof patch.description === 'string' && patch.description.trim() ? patch.description.trim() : undefined)
|
|
: existing.description,
|
|
trigger,
|
|
triggerPattern: typeof triggerPattern === 'string' ? triggerPattern : undefined,
|
|
};
|
|
|
|
return { ok: true, snippet, prevTargetIds };
|
|
}
|
|
|
|
export function deleteSnippetFromVault(
|
|
snippets: Snippet[],
|
|
hosts: Host[],
|
|
snippetId: string,
|
|
): { snippets: Snippet[]; hosts: Host[] } | { error: string } {
|
|
const target = snippets.find((entry) => entry.id === snippetId);
|
|
if (!target) return { error: `Snippet "${snippetId}" was not found.` };
|
|
|
|
const nextSnippets = snippets.filter((entry) => entry.id !== snippetId);
|
|
let nextHosts = hosts.map((host) => {
|
|
let updated = host;
|
|
if (host.loginScriptId === snippetId) {
|
|
updated = { ...updated, loginScriptId: undefined };
|
|
}
|
|
if (host.connectScriptIds?.includes(snippetId)) {
|
|
updated = removeHostConnectScript(updated, snippetId, snippets);
|
|
}
|
|
return updated;
|
|
});
|
|
|
|
if (isScriptSnippet(target) && target.trigger === 'onConnect') {
|
|
nextHosts = syncHostsForSnippetTargetChange(
|
|
nextHosts,
|
|
{ ...target, targets: [] },
|
|
target.targets,
|
|
nextSnippets,
|
|
);
|
|
}
|
|
|
|
return { snippets: nextSnippets, hosts: nextHosts };
|
|
}
|
|
|
|
export function applySnippetCreateToVault(
|
|
snippets: Snippet[],
|
|
hosts: Host[],
|
|
snippet: Snippet,
|
|
): { snippets: Snippet[]; hosts: Host[] } {
|
|
const nextSnippets = [...snippets, snippet];
|
|
if (!isScriptSnippet(snippet) || snippet.trigger !== 'onConnect') {
|
|
return { snippets: nextSnippets, hosts };
|
|
}
|
|
const nextHosts = syncHostsForSnippetTargetChange(
|
|
hosts,
|
|
snippet,
|
|
[],
|
|
nextSnippets,
|
|
);
|
|
return { snippets: nextSnippets, hosts: nextHosts };
|
|
}
|
|
|
|
export function applySnippetUpdateToVault(
|
|
snippets: Snippet[],
|
|
hosts: Host[],
|
|
snippet: Snippet,
|
|
previous: Snippet,
|
|
prevTargetIds?: string[],
|
|
): { snippets: Snippet[]; hosts: Host[] } {
|
|
const nextSnippets = snippets.map((entry) => (entry.id === snippet.id ? snippet : entry));
|
|
const wasOnConnectScript = isScriptSnippet(previous) && previous.trigger === 'onConnect' && Boolean(previous.id);
|
|
const isOnConnectScript = isScriptSnippet(snippet) && snippet.trigger === 'onConnect' && Boolean(snippet.id);
|
|
|
|
let nextHosts = hosts;
|
|
if (wasOnConnectScript && !isOnConnectScript && snippet.id) {
|
|
nextHosts = nextHosts.map((host) => removeHostConnectScript(host, snippet.id, nextSnippets));
|
|
} else if (isOnConnectScript) {
|
|
nextHosts = syncHostsForSnippetTargetChange(
|
|
nextHosts,
|
|
snippet,
|
|
prevTargetIds,
|
|
nextSnippets,
|
|
);
|
|
}
|
|
return { snippets: nextSnippets, hosts: nextHosts };
|
|
}
|
|
|
|
export function setHostConnectScriptIds(
|
|
host: Host,
|
|
scriptIds: string[],
|
|
snippets: Snippet[],
|
|
): { ok: true; host: Host } | { ok: false; error: string } {
|
|
const pruned: string[] = [];
|
|
const seen = new Set<string>();
|
|
for (const id of scriptIds) {
|
|
const trimmed = String(id || '').trim();
|
|
if (!trimmed || seen.has(trimmed)) continue;
|
|
const snippet = snippets.find((entry) => entry.id === trimmed && isScriptSnippet(entry));
|
|
if (!snippet || snippet.trigger !== 'onConnect') {
|
|
return { ok: false, error: `Script "${trimmed}" is not an onConnect automation script.` };
|
|
}
|
|
seen.add(trimmed);
|
|
pruned.push(trimmed);
|
|
}
|
|
return { ok: true, host: { ...host, connectScriptIds: pruned } };
|
|
}
|
|
|
|
export function summarizeConnectScriptsForHost(host: Host, snippets: Snippet[]) {
|
|
const resolved = resolveConnectScriptsForHost(host, snippets);
|
|
return {
|
|
hostId: host.id,
|
|
connectScriptIds: getHostConnectScriptIds(host, snippets),
|
|
resolvedScripts: resolved.map(serializeSnippetForAgentList),
|
|
};
|
|
}
|
|
|
|
export function applyScriptTargetsPatch(
|
|
snippet: Snippet,
|
|
params: { targets?: unknown; targetGroups?: unknown; targetsAllHosts?: unknown },
|
|
): { ok: true; snippet: Snippet; prevTargetIds?: string[] } | { ok: false; error: string } {
|
|
return applySnippetAgentPatch(snippet, params, { forceKind: 'script' });
|
|
}
|