Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
66 changes: 62 additions & 4 deletions src/cli/commands/data_search.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,10 @@ import {
type DataSearchItem,
parseTags,
} from "../../libswamp/mod.ts";
import { createDataSearchRenderer } from "../../presentation/renderers/data_search.tsx";
import {
createDataSearchRenderer,
type DataPreviewDetail,
} from "../../presentation/renderers/data_search.tsx";
import { renderDataGet } from "../../presentation/renderers/data_get.ts";
import {
createContext,
Expand All @@ -46,6 +49,53 @@ import type { FileSystemUnifiedDataRepository } from "../../infrastructure/persi
// deno-lint-ignore no-explicit-any
type AnyOptions = any;

/**
* Creates a fetchPreview closure for the data search picker.
* Reads data content from disk for display in the preview pane.
*/
function createDataFetchPreview(
dataRepo: FileSystemUnifiedDataRepository,
repoDir: string,
): (item: DataSearchItem) => Promise<DataPreviewDetail> {
return async (item: DataSearchItem): Promise<DataPreviewDetail> => {
const modelType = ModelType.create(item.modelType);
const absoluteContentPath = dataRepo.getContentPath(
modelType,
item.modelId,
item.name,
item.version,
);
const contentPath = toRelativePath(repoDir, absoluteContentPath);

// Only read text content for preview
const isText = item.contentType.startsWith("text/") ||
item.contentType === "application/json" ||
item.contentType === "application/yaml" ||
item.contentType === "application/x-yaml";

if (!isText) {
return { content: undefined, contentPath };
}

try {
const rawContent = await dataRepo.getContent(
modelType,
item.modelId,
item.name,
item.version,
);
if (rawContent) {
const content = new TextDecoder().decode(rawContent);
return { content, contentPath };
}
} catch {
// Silently handle read errors
}

return { content: undefined, contentPath };
};
}

/**
* Fetches and displays full data details after selection from interactive search.
*/
Expand Down Expand Up @@ -185,7 +235,12 @@ export const dataSearchCommand = new Command()
findDefinitionByIdOrName(definitionRepo, idOrName),
};

const renderer = createDataSearchRenderer(effectiveMode);
const repoDir = options.repoDir ?? ".";
const fetchPreview = effectiveMode === "log"
? createDataFetchPreview(dataRepo, repoDir)
: undefined;

const renderer = createDataSearchRenderer(effectiveMode, fetchPreview);
await consumeStream(
dataSearch(libCtx, deps, {
query,
Expand All @@ -207,8 +262,11 @@ export const dataSearchCommand = new Command()

const selected = renderer.selectedItem();
if (selected) {
const repoDir = options.repoDir ?? ".";
await displayDataDetail(selected, dataRepo, repoDir, effectiveMode);
// In JSON mode, display full data detail after selection
if (effectiveMode === "json") {
await displayDataDetail(selected, dataRepo, repoDir, effectiveMode);
}
// In interactive mode, scrollback from the picker already has the detail
}

ctx.logger.debug("Data search command completed");
Expand Down
56 changes: 49 additions & 7 deletions src/cli/commands/model_output_search.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,10 @@ import {
createLibSwampContext,
createModelOutputGetDeps,
modelOutputGet,
type ModelOutputGetData,
modelOutputSearch,
type ModelOutputSearchDeps,
type ModelOutputSearchItem,
} from "../../libswamp/mod.ts";
import { createModelOutputSearchRenderer } from "../../presentation/renderers/model_output_search.tsx";
import { createModelOutputGetRenderer } from "../../presentation/renderers/model_output_get.ts";
Expand All @@ -40,6 +42,33 @@ import { createDefinitionId } from "../../domain/definitions/definition.ts";
// deno-lint-ignore no-explicit-any
type AnyOptions = any;

/**
* Creates a fetchPreview closure that fetches full model output detail data.
* This bridges the presentation layer to the libswamp modelOutputGet application
* service, capturing the repoDir dependency.
*/
function createOutputFetchPreview(
repoDir: string,
): (item: ModelOutputSearchItem) => Promise<ModelOutputGetData> {
const libCtx = createLibSwampContext();
const getDeps = createModelOutputGetDeps(repoDir);

return async (item: ModelOutputSearchItem): Promise<ModelOutputGetData> => {
let result: ModelOutputGetData | undefined;
await consumeStream(modelOutputGet(libCtx, getDeps, item.id), {
resolving: () => {},
completed: (e) => {
result = e.data;
},
error: () => {},
});
if (!result) {
throw new Error(`Output not found: ${item.id}`);
}
return result;
};
}

export const modelOutputSearchCommand = new Command()
.name("search")
.description("Search for model outputs")
Expand Down Expand Up @@ -69,7 +98,15 @@ export const modelOutputSearchCommand = new Command()
),
};

const renderer = createModelOutputSearchRenderer(effectiveMode);
const repoDir = options.repoDir ?? ".";
const fetchPreview = effectiveMode === "log"
? createOutputFetchPreview(repoDir)
: undefined;

const renderer = createModelOutputSearchRenderer(
effectiveMode,
fetchPreview,
);
await consumeStream(
modelOutputSearch(libCtx, deps, { query }),
renderer.handlers(),
Expand All @@ -78,12 +115,17 @@ export const modelOutputSearchCommand = new Command()
const selected = renderer.selectedItem();
if (selected) {
ctx.logger.debug`Selected output: ${selected.id}`;
const getRenderer = createModelOutputGetRenderer(effectiveMode);
const getDeps = createModelOutputGetDeps(options.repoDir ?? ".");
await consumeStream(
modelOutputGet(libCtx, getDeps, selected.id),
getRenderer.handlers(),
);
// In JSON mode, still display the full output get after auto-select
if (effectiveMode === "json") {
const getRenderer = createModelOutputGetRenderer(effectiveMode);
const getDeps = createModelOutputGetDeps(repoDir);
await consumeStream(
modelOutputGet(libCtx, getDeps, selected.id),
getRenderer.handlers(),
);
}
// In interactive mode, the scrollback from the picker already contains
// the output detail, so no additional modelOutputGet call is needed.
} else {
ctx.logger.debug`Search cancelled`;
}
Expand Down
53 changes: 46 additions & 7 deletions src/cli/commands/model_search.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,10 @@ import {
createLibSwampContext,
createModelGetDeps,
modelGet,
type ModelGetData,
modelSearch,
type ModelSearchDeps,
type ModelSearchItem,
} from "../../libswamp/mod.ts";
import { createModelSearchRenderer } from "../../presentation/renderers/model_search.tsx";
import { createModelGetRenderer } from "../../presentation/renderers/model_get.ts";
Expand All @@ -38,6 +40,33 @@ import { requireInitializedRepoReadOnly } from "../repo_context.ts";
// deno-lint-ignore no-explicit-any
type AnyOptions = any;

/**
* Creates a fetchPreview closure that fetches full model detail data.
* This bridges the presentation layer to the libswamp modelGet application
* service, capturing the repoDir dependency.
*/
function createModelFetchPreview(
repoDir: string,
): (item: ModelSearchItem) => Promise<ModelGetData> {
const libCtx = createLibSwampContext();
const getDeps = createModelGetDeps(repoDir);

return async (item: ModelSearchItem): Promise<ModelGetData> => {
let result: ModelGetData | undefined;
await consumeStream(modelGet(libCtx, getDeps, item.name), {
resolving: () => {},
completed: (e) => {
result = e.data;
},
error: () => {},
});
if (!result) {
throw new Error(`Model not found: ${item.name}`);
}
return result;
};
}

export async function modelSearchAction(
options: AnyOptions,
query?: string,
Expand All @@ -56,7 +85,12 @@ export async function modelSearchAction(
findAllGlobal: () => repoContext.definitionRepo.findAllGlobal(),
};

const renderer = createModelSearchRenderer(effectiveMode);
const repoDir = options.repoDir ?? ".";
const fetchPreview = effectiveMode === "log"
? createModelFetchPreview(repoDir)
: undefined;

const renderer = createModelSearchRenderer(effectiveMode, fetchPreview);
await consumeStream(
modelSearch(libCtx, deps, { query }),
renderer.handlers(),
Expand All @@ -65,12 +99,17 @@ export async function modelSearchAction(
const selected = renderer.selectedItem();
if (selected) {
ctx.logger.debug`Selected model: ${selected.name} (${selected.id})`;
const getRenderer = createModelGetRenderer(effectiveMode);
const getDeps = createModelGetDeps(options.repoDir ?? ".");
await consumeStream(
modelGet(libCtx, getDeps, selected.name),
getRenderer.handlers(),
);
// In JSON mode, still display the full model get output after auto-select
if (effectiveMode === "json") {
const getRenderer = createModelGetRenderer(effectiveMode);
const getDeps = createModelGetDeps(repoDir);
await consumeStream(
modelGet(libCtx, getDeps, selected.name),
getRenderer.handlers(),
);
}
// In interactive mode, the scrollback from the picker already contains
// the model detail, so no additional modelGet call is needed.
} else {
ctx.logger.debug`Search cancelled`;
}
Expand Down
53 changes: 52 additions & 1 deletion src/cli/commands/report_search.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ import {
type ReportGetDeps,
reportSearch,
type ReportSearchDeps,
type StoredReportDetail,
type StoredReportSummary,
} from "../../libswamp/mod.ts";
import type { RepositoryContext } from "../../infrastructure/persistence/repository_factory.ts";
Expand Down Expand Up @@ -94,6 +95,41 @@ function buildGetDeps(repoContext: RepositoryContext): ReportGetDeps {
};
}

/**
* Creates a fetchPreview closure that fetches full report detail data.
* This bridges the presentation layer to the libswamp reportGet application
* service, capturing the repository context dependency.
*/
function createReportFetchPreview(
repoContext: RepositoryContext,
): (item: StoredReportSummary) => Promise<StoredReportDetail> {
const libCtx = createLibSwampContext();
const getDeps = buildGetDeps(repoContext);

return async (item: StoredReportSummary): Promise<StoredReportDetail> => {
let result: StoredReportDetail | undefined;
await consumeStream(
reportGet(libCtx, getDeps, {
reportName: item.reportName,
model: item.workflowName ? undefined : item.modelName,
workflow: item.workflowName,
variant: item.varySuffix,
}),
{
resolving: () => {},
completed: (e) => {
result = e.data;
},
error: () => {},
},
);
if (!result) {
throw new Error(`Report not found: ${item.reportName}`);
}
return result;
};
}

/**
* Fetches and displays full report content for a selected summary.
*/
Expand Down Expand Up @@ -146,9 +182,14 @@ export const reportSearchCommand = new Command()

const libCtx = createLibSwampContext({ logger: ctx.logger });

const fetchPreview = effectiveMode === "log"
? createReportFetchPreview(repoContext)
: undefined;

const searchRenderer = createReportSearchRenderer(
effectiveMode,
query ?? "",
fetchPreview,
);
await consumeStream(
reportSearch(libCtx, buildSearchDeps(repoContext), {
Expand All @@ -164,7 +205,17 @@ export const reportSearchCommand = new Command()
const selected = searchRenderer.selectedItem();
if (selected) {
ctx.logger.debug`Selected report: ${selected.reportName}`;
await displayReportDetail(selected, repoContext, libCtx, effectiveMode);
// In JSON mode, still display the full report detail after auto-select
if (effectiveMode === "json") {
await displayReportDetail(
selected,
repoContext,
libCtx,
effectiveMode,
);
}
// In interactive mode, the scrollback from the picker already contains
// the report detail, so no additional displayReportDetail call is needed.
} else {
ctx.logger.debug`Search cancelled`;
}
Expand Down
Loading
Loading