askeroo
Version:
A modern CLI prompt library with flow control, history navigation, and conditional prompts
144 lines • 5.3 kB
JavaScript
import { createPrompt } from "../../core/registry.js";
import { StreamDisplay } from "./Stream.js";
import { streamStore } from "./stream-store.js";
// Re-export color helper
export { spawnWithColors, } from "./spawn-with-colors.js";
// Track the most recent stream ID
let currentStreamId = null;
// Internal plugin implementation
const streamInternal = createPrompt({
type: "stream",
component: StreamDisplay,
});
// Function to update stream state
function updateStreamState(streamId, updates) {
streamStore.update((s) => {
const currentState = s.streams.get(streamId) || {
status: "active",
lines: [],
};
s.streams.set(streamId, {
status: updates.status !== undefined
? updates.status
: currentState.status,
lines: updates.lines !== undefined
? updates.lines
: currentState.lines,
label: updates.label !== undefined
? updates.label
: currentState.label,
});
s.revision++;
});
}
// Implementation
export async function stream(labelOrOptions, options) {
// Determine if first arg is label or options
const isOptionsObject = typeof labelOrOptions === "object";
const label = isOptionsObject ? labelOrOptions?.label : labelOrOptions;
const finalOptions = isOptionsObject ? labelOrOptions : options;
// Generate unique stream ID
const streamId = `stream_${Date.now()}_${Math.random()
.toString(36)
.substring(2, 11)}`;
currentStreamId = streamId;
// Initialize stream as active in the store
streamStore.update((s) => {
s.streams.set(streamId, {
status: "active",
lines: [],
label,
});
s.revision++;
});
// Start the prompt in the background
// Always auto-complete to avoid blocking the runtime, but keep the stream visible
const promptPromise = streamInternal({
label,
streamId,
maxLines: finalOptions?.maxLines,
hideOnCompletion: finalOptions?.hideOnCompletion,
submitDelay: finalOptions?.submitDelay,
showLineNumbers: finalOptions?.showLineNumbers,
prefixSymbol: finalOptions?.prefixSymbol,
});
// Buffer for incomplete lines
let lineBuffer = "";
// Create controller object with async methods
const controller = {
write: async (text) => {
// Append text to buffer
lineBuffer += text;
// Split by newlines but keep incomplete line in buffer
const parts = lineBuffer.split("\n");
if (parts.length > 1) {
// We have complete lines
lineBuffer = parts[parts.length - 1]; // Keep last incomplete part
const completeLines = parts.slice(0, -1);
streamStore.update((s) => {
const currentState = s.streams.get(streamId) || {
status: "active",
lines: [],
};
s.streams.set(streamId, {
...currentState,
lines: [...currentState.lines, ...completeLines],
});
s.revision++;
});
}
},
writeLine: async (text) => {
// Flush any buffered content with this line
const lineToAdd = lineBuffer + text;
lineBuffer = "";
streamStore.update((s) => {
const currentState = s.streams.get(streamId) || {
status: "active",
lines: [],
};
s.streams.set(streamId, {
...currentState,
lines: [...currentState.lines, lineToAdd],
});
s.revision++;
});
},
clear: async () => {
lineBuffer = "";
updateStreamState(streamId, { lines: [] });
},
setLabel: async (newLabel) => {
updateStreamState(streamId, { label: newLabel });
},
complete: async (finalMessage) => {
// Flush any remaining buffer
if (lineBuffer) {
await controller.writeLine(lineBuffer);
}
if (finalMessage) {
await controller.setLabel(finalMessage);
}
updateStreamState(streamId, { status: "completed" });
// Wait for the prompt to complete
await promptPromise;
},
error: async (errorMessage) => {
// Flush any remaining buffer
if (lineBuffer) {
await controller.writeLine(lineBuffer);
}
if (errorMessage) {
await controller.setLabel(errorMessage);
}
updateStreamState(streamId, { status: "error" });
// Wait for the prompt to complete
await promptPromise;
},
};
// Wait a bit to ensure the component is mounted
await new Promise((resolve) => setTimeout(resolve, 50));
// Return controller immediately so user can control it
return controller;
}
//# sourceMappingURL=index.js.map