[Init] Initial commit - NetMesh terminal manager
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
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
This commit is contained in:
203
components/terminal/autocomplete/figSpecLoader.ts
Normal file
203
components/terminal/autocomplete/figSpecLoader.ts
Normal file
@@ -0,0 +1,203 @@
|
||||
/**
|
||||
* Loader for @withfig/autocomplete command specifications.
|
||||
* Loads specs via Electron main process IPC (Node.js require),
|
||||
* which reliably accesses node_modules in both dev and production.
|
||||
*/
|
||||
|
||||
/** Minimal Fig spec types — mirrors @withfig/autocomplete-types */
|
||||
export interface FigOption {
|
||||
name: string | string[];
|
||||
description?: string;
|
||||
args?: FigArg | FigArg[];
|
||||
isRequired?: boolean;
|
||||
isPersistent?: boolean;
|
||||
exclusiveOn?: string[];
|
||||
}
|
||||
|
||||
export interface FigArg {
|
||||
name?: string;
|
||||
description?: string;
|
||||
suggestions?: (string | FigSuggestion)[];
|
||||
template?: string | string[];
|
||||
isOptional?: boolean;
|
||||
isVariadic?: boolean;
|
||||
generators?: unknown;
|
||||
}
|
||||
|
||||
export interface FigSuggestion {
|
||||
name: string | string[];
|
||||
description?: string;
|
||||
icon?: string;
|
||||
type?: string;
|
||||
priority?: number;
|
||||
}
|
||||
|
||||
export interface FigSubcommand {
|
||||
name: string | string[];
|
||||
description?: string;
|
||||
subcommands?: FigSubcommand[];
|
||||
options?: FigOption[];
|
||||
args?: FigArg | FigArg[];
|
||||
}
|
||||
|
||||
export interface FigSpec extends FigSubcommand {
|
||||
// Top-level spec may include additional metadata
|
||||
}
|
||||
|
||||
// Bridge type augmentation
|
||||
interface FigSpecBridge {
|
||||
listFigSpecs?: () => Promise<string[]>;
|
||||
loadFigSpec?: (commandName: string) => Promise<FigSpec | null>;
|
||||
}
|
||||
|
||||
function getBridge(): FigSpecBridge | undefined {
|
||||
if (typeof window === "undefined") return undefined;
|
||||
return (window as Window & { netcatty?: FigSpecBridge }).netcatty;
|
||||
}
|
||||
|
||||
// Cache loaded specs
|
||||
const specCache = new Map<string, FigSpec | null>();
|
||||
|
||||
// In-flight loading promises to avoid duplicate loads
|
||||
const inFlightLoads = new Map<string, Promise<FigSpec | null>>();
|
||||
|
||||
// All available spec names
|
||||
let availableSpecs: string[] | null = null;
|
||||
let availableSpecsSet: Set<string> | null = null;
|
||||
|
||||
/**
|
||||
* Get the list of all available command specs via IPC.
|
||||
*/
|
||||
export async function getAvailableSpecs(): Promise<string[]> {
|
||||
// Only return cache if it has actual specs (not an empty failure)
|
||||
if (availableSpecs && availableSpecs.length > 0) return availableSpecs;
|
||||
|
||||
try {
|
||||
const bridge = getBridge();
|
||||
if (bridge?.listFigSpecs) {
|
||||
const specs = await bridge.listFigSpecs();
|
||||
if (Array.isArray(specs) && specs.length > 0) {
|
||||
availableSpecs = specs;
|
||||
availableSpecsSet = new Set(specs);
|
||||
return specs;
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
console.warn("[Autocomplete] figspec bridge error:", err);
|
||||
}
|
||||
|
||||
// Don't cache empty — allow retry on next call
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Load a command specification by name via IPC.
|
||||
* Uses in-flight deduplication to avoid loading the same spec twice concurrently.
|
||||
*/
|
||||
export async function loadSpec(commandName: string): Promise<FigSpec | null> {
|
||||
if (specCache.has(commandName)) {
|
||||
return specCache.get(commandName) ?? null;
|
||||
}
|
||||
|
||||
const existing = inFlightLoads.get(commandName);
|
||||
if (existing) return existing;
|
||||
|
||||
const loadPromise = (async (): Promise<FigSpec | null> => {
|
||||
try {
|
||||
const bridge = getBridge();
|
||||
if (!bridge?.loadFigSpec) {
|
||||
// Don't cache — bridge may not be ready yet (dev reload, non-Electron preview)
|
||||
return null;
|
||||
}
|
||||
|
||||
const spec = await bridge.loadFigSpec(commandName);
|
||||
if (spec) {
|
||||
specCache.set(commandName, spec);
|
||||
}
|
||||
// Don't cache null — the load may have failed transiently (bridge not ready, etc.)
|
||||
// Only cache null when we're confident the spec doesn't exist (hasSpec returned false)
|
||||
return spec;
|
||||
} catch {
|
||||
// Don't cache failures — allow retry on next request
|
||||
return null;
|
||||
} finally {
|
||||
inFlightLoads.delete(commandName);
|
||||
}
|
||||
})();
|
||||
|
||||
inFlightLoads.set(commandName, loadPromise);
|
||||
return loadPromise;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a spec exists for a given command name (without loading it).
|
||||
*/
|
||||
export async function hasSpec(commandName: string): Promise<boolean> {
|
||||
// Only trust positive cache hits (spec loaded successfully).
|
||||
// Null entries may be stale failures from preload — ignore them.
|
||||
const cached = specCache.get(commandName);
|
||||
if (cached) return true;
|
||||
|
||||
await getAvailableSpecs();
|
||||
return availableSpecsSet?.has(commandName) ?? false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Common shell commands preloaded when autocomplete is enabled.
|
||||
* Includes local overrides under electron/specs/ (e.g. yum, dnf, awk).
|
||||
*/
|
||||
export const COMMON_FIG_SPECS = [
|
||||
"git", "docker", "kubectl", "npm", "yarn", "pnpm",
|
||||
"ls", "cd", "cat", "grep", "find", "ssh", "scp",
|
||||
"curl", "wget", "tar", "zip", "unzip", "make",
|
||||
"python", "python3", "pip", "pip3", "node",
|
||||
"systemctl", "journalctl", "apt", "yum", "dnf", "brew",
|
||||
"vim", "nano", "less", "head", "tail", "sort",
|
||||
"awk", "sed", "chmod", "chown", "cp", "mv", "rm", "mkdir",
|
||||
] as const;
|
||||
|
||||
/**
|
||||
* Preload commonly used specs in batches to avoid overwhelming IPC.
|
||||
* Only call this when autocomplete is enabled.
|
||||
*/
|
||||
export function preloadCommonSpecs(): void {
|
||||
const BATCH_SIZE = 8;
|
||||
let offset = 0;
|
||||
|
||||
const loadBatch = () => {
|
||||
const batch = COMMON_FIG_SPECS.slice(offset, offset + BATCH_SIZE);
|
||||
if (batch.length === 0) return;
|
||||
|
||||
for (const name of batch) {
|
||||
loadSpec(name).catch(() => {});
|
||||
}
|
||||
|
||||
offset += BATCH_SIZE;
|
||||
if (offset < COMMON_FIG_SPECS.length) {
|
||||
if (typeof requestIdleCallback === "function") {
|
||||
requestIdleCallback(() => loadBatch());
|
||||
} else {
|
||||
setTimeout(loadBatch, 100);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
setTimeout(loadBatch, 200);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get normalized name variants (e.g., "git" from "/usr/bin/git").
|
||||
*/
|
||||
export function normalizeCommandName(rawCommand: string): string {
|
||||
const parts = rawCommand.split("/");
|
||||
let name = parts[parts.length - 1];
|
||||
name = name.replace(/\.(exe|cmd|bat|sh|bash|zsh|fish)$/i, "");
|
||||
return name.toLowerCase();
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve names from a Fig spec name field (which can be string or string[]).
|
||||
*/
|
||||
export function resolveNames(name: string | string[]): string[] {
|
||||
return Array.isArray(name) ? name : [name];
|
||||
}
|
||||
Reference in New Issue
Block a user