Feat: cli for core (#24)

* Feat: cli for cor

* Fix: add deploy registry namespace to Docker configurations

* Feat: cli for init, start and stop

---------

Co-authored-by: Manoj K <saimanoj58@gmail.com>
This commit is contained in:
Harshith Mullapudi 2025-07-18 22:30:46 +05:30 committed by GitHub
parent d11cc5dc93
commit 6e308408be
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
55 changed files with 1420 additions and 759 deletions

5
.gitignore vendored
View File

@ -26,7 +26,7 @@ coverage
out/
build
dist
.tshy/
# Debug
npm-debug.log*
@ -46,4 +46,5 @@ registry/
.cursor
CLAUDE.md
.claude
.claude

View File

@ -55,7 +55,7 @@ export const ConversationNew = ({
</h1>
<p className="text-muted-foreground mx-1 mb-4">
Demo UI: basic conversation to showcase memory integration.
Update your memory or ask about anything it already knows
</p>
<div className="bg-background-3 rounded-lg border-1 border-gray-300 py-2">
<EditorRoot>

View File

@ -83,9 +83,9 @@ export function LogTextCollapse({
isLong ? "max-h-16 overflow-hidden" : "",
)}
style={{ lineHeight: "1.5" }}
>
{displayText}
</p>
dangerouslySetInnerHTML={{ __html: text }}
/>
{isLong && (
<>
<Dialog open={dialogOpen} onOpenChange={setDialogOpen}>

View File

@ -55,7 +55,7 @@ export const getInitials = (name: string, noOfChar?: number | undefined) => {
return words
.map((word) => word.charAt(0))
.filter((char) => char !== "")
.slice(0, noOfChar ? noOfChar : 2)
.slice(0, noOfChar ? noOfChar : 1)
.join("")
.toUpperCase();
};

View File

@ -30,7 +30,7 @@ export default function LogsActivity() {
return (
<AppContainer>
<PageContainer>
<div className="flex h-64 items-center justify-center">
<div className="flex h-[calc(100vh_-_16px)] items-center justify-center">
<LoaderCircle className="text-primary h-4 w-4 animate-spin" />
</div>
</PageContainer>
@ -58,44 +58,52 @@ export default function LogsActivity() {
]}
/>
<div className="flex h-[calc(100vh_-_56px)] flex-col space-y-6 p-4 px-5">
{logs.length > 0 && (
<LogsFilters
availableSources={availableSources}
selectedSource={selectedSource}
selectedStatus={selectedStatus}
onSourceChange={setSelectedSource}
onStatusChange={setSelectedStatus}
/>
)}
{isInitialLoad ? (
<>
<LoaderCircle className="text-primary h-4 w-4 animate-spin" />{" "}
</>
) : (
<>
{logs.length > 0 && (
<LogsFilters
availableSources={availableSources}
selectedSource={selectedSource}
selectedStatus={selectedStatus}
onSourceChange={setSelectedSource}
onStatusChange={setSelectedStatus}
/>
)}
{/* Logs List */}
<div className="space-y-4">
{logs.length === 0 ? (
<Card>
<CardContent className="bg-background-2 flex items-center justify-center py-16">
<div className="text-center">
<Activity className="text-muted-foreground mx-auto mb-4 h-12 w-12" />
<h3 className="mb-2 text-lg font-semibold">
No activity logs found
</h3>
<p className="text-muted-foreground">
{selectedSource || selectedStatus
? "Try adjusting your filters to see more results."
: "No activity ingestion logs are available yet."}
</p>
</div>
</CardContent>
</Card>
) : (
<VirtualLogsList
logs={logs}
hasMore={hasMore}
loadMore={loadMore}
isLoading={isLoading}
height={600}
/>
)}
</div>
{/* Logs List */}
<div className="space-y-4">
{logs.length === 0 ? (
<Card>
<CardContent className="bg-background-2 flex items-center justify-center py-16">
<div className="text-center">
<Activity className="text-muted-foreground mx-auto mb-4 h-12 w-12" />
<h3 className="mb-2 text-lg font-semibold">
No activity logs found
</h3>
<p className="text-muted-foreground">
{selectedSource || selectedStatus
? "Try adjusting your filters to see more results."
: "No activity ingestion logs are available yet."}
</p>
</div>
</CardContent>
</Card>
) : (
<VirtualLogsList
logs={logs}
hasMore={hasMore}
loadMore={loadMore}
isLoading={isLoading}
height={600}
/>
)}
</div>
</>
)}
</div>
</div>
);

View File

@ -26,18 +26,6 @@ export default function LogsAll() {
status: selectedStatus,
});
if (isInitialLoad) {
return (
<AppContainer>
<PageContainer>
<div className="flex h-64 items-center justify-center">
<LoaderCircle className="text-primary h-4 w-4 animate-spin" />
</div>
</PageContainer>
</AppContainer>
);
}
return (
<>
<PageHeader
@ -58,43 +46,53 @@ export default function LogsAll() {
]}
/>
<div className="h-[calc(100vh_-_56px)] space-y-6 p-4 px-5">
{/* Filters */}
{logs.length > 0 && (
<LogsFilters
availableSources={availableSources}
selectedSource={selectedSource}
selectedStatus={selectedStatus}
onSourceChange={setSelectedSource}
onStatusChange={setSelectedStatus}
/>
{isInitialLoad ? (
<>
<LoaderCircle className="text-primary h-4 w-4 animate-spin" />{" "}
</>
) : (
<>
{" "}
{/* Filters */}
{logs.length > 0 && (
<LogsFilters
availableSources={availableSources}
selectedSource={selectedSource}
selectedStatus={selectedStatus}
onSourceChange={setSelectedSource}
onStatusChange={setSelectedStatus}
/>
)}
{/* Logs List */}
<div className="space-y-4">
{logs.length === 0 ? (
<Card>
<CardContent className="bg-background-2 flex items-center justify-center py-16">
<div className="text-center">
<Database className="text-muted-foreground mx-auto mb-4 h-12 w-12" />
<h3 className="mb-2 text-lg font-semibold">
No logs found
</h3>
<p className="text-muted-foreground">
{selectedSource || selectedStatus
? "Try adjusting your filters to see more results."
: "No ingestion logs are available yet."}
</p>
</div>
</CardContent>
</Card>
) : (
<VirtualLogsList
logs={logs}
hasMore={hasMore}
loadMore={loadMore}
isLoading={isLoading}
height={600}
/>
)}
</div>
</>
)}
{/* Logs List */}
<div className="space-y-4">
{logs.length === 0 ? (
<Card>
<CardContent className="bg-background-2 flex items-center justify-center py-16">
<div className="text-center">
<Database className="text-muted-foreground mx-auto mb-4 h-12 w-12" />
<h3 className="mb-2 text-lg font-semibold">No logs found</h3>
<p className="text-muted-foreground">
{selectedSource || selectedStatus
? "Try adjusting your filters to see more results."
: "No ingestion logs are available yet."}
</p>
</div>
</CardContent>
</Card>
) : (
<VirtualLogsList
logs={logs}
hasMore={hasMore}
loadMore={loadMore}
isLoading={isLoading}
height={600}
/>
)}
</div>
</div>
</>
);

View File

@ -748,7 +748,7 @@ export class KnowledgeGraphService {
// Phase 2: Find semantically similar statements
const semanticMatches = await findSimilarStatements({
factEmbedding: triple.statement.factEmbedding,
threshold: 0.7,
threshold: 0.85,
excludeIds: checkedStatementIds,
userId: triple.provenance.userId,
});

View File

@ -17,7 +17,7 @@ import {
const chatQueue = queue({
name: "chat-queue",
concurrencyLimit: 10,
concurrencyLimit: 50,
});
/**

View File

@ -0,0 +1,334 @@
import { task, logger, queue } from "@trigger.dev/sdk";
import { runQuery } from "~/lib/neo4j.server";
import { getEmbedding } from "~/lib/model.server";
import type { EntityNode } from "@core/types";
interface EntityUpdateResult {
uuid: string;
name: string;
type: string;
success: boolean;
error?: string;
}
interface BatchResult {
batchId: string;
entities: number;
successful: number;
failed: number;
results: EntityUpdateResult[];
}
export const entity = queue({
name: "entity-queue",
concurrencyLimit: 10,
});
/**
* Main orchestrator task that fans out batches of 100 entities
*/
export const updateAllEntityEmbeddings = task({
id: "update-all-entity-embeddings",
machine: "large-1x",
run: async (payload: { userId?: string; batchSize?: number } = {}) => {
const { userId, batchSize = 100 } = payload;
logger.info("Starting entity embeddings update with fan-out approach", {
userId,
batchSize,
targetScope: userId ? `user ${userId}` : "all users",
});
try {
// Step 1: Fetch all entities
const entities = await getAllEntities(userId);
logger.info(`Found ${entities.length} entities to update`);
if (entities.length === 0) {
return {
success: true,
totalEntities: 0,
totalBatches: 0,
updated: 0,
failed: 0,
batchResults: [],
};
}
// Step 2: Split entities into batches and fan out
const batches: EntityNode[][] = [];
for (let i = 0; i < entities.length; i += batchSize) {
batches.push(entities.slice(i, i + batchSize));
}
logger.info(
`Fanning out ${batches.length} batches of ~${batchSize} entities each`,
);
// Step 3: Fan out batch processing tasks in parallel
const batchPromises = batches.map((batch, index) =>
updateEntityBatch.trigger({
entities: batch,
batchId: `batch-${index + 1}`,
batchNumber: index + 1,
totalBatches: batches.length,
}),
);
// Wait for all batch tasks to complete
const batchRuns = await Promise.all(batchPromises);
// Step 4: Collect results from all batches
const batchResults: BatchResult[] = [];
let totalUpdated = 0;
let totalFailed = 0;
for (const run of batchRuns) {
try {
// Note: In a real implementation, you'd need to wait for the run to complete
// and fetch its result. This is a simplified version.
logger.info(`Batch run ${run.id} started successfully`);
} catch (error) {
logger.error(`Failed to start batch run:`, { error });
}
}
logger.info("All batches have been dispatched", {
totalBatches: batches.length,
totalEntities: entities.length,
batchRunIds: batchRuns.map((r) => r.id),
});
return {
success: true,
totalEntities: entities.length,
totalBatches: batches.length,
batchRunIds: batchRuns.map((r) => r.id),
message:
"All batches dispatched successfully. Check individual batch runs for detailed results.",
};
} catch (error) {
logger.error(
"Fatal error during entity embeddings update orchestration:",
{ error },
);
throw error;
}
},
});
/**
* Worker task that processes a single batch of entities
*/
export const updateEntityBatch = task({
id: "update-entity-batch",
queue: entity,
run: async (payload: {
entities: EntityNode[];
batchId: string;
batchNumber: number;
totalBatches: number;
}) => {
const { entities, batchId, batchNumber, totalBatches } = payload;
logger.info(`Processing ${batchId} (${batchNumber}/${totalBatches})`, {
entityCount: entities.length,
});
const results: EntityUpdateResult[] = [];
try {
// Process all entities in this batch in parallel
const entityPromises = entities.map((entity) =>
updateEntityEmbeddings(entity),
);
const entityResults = await Promise.allSettled(entityPromises);
// Collect results
entityResults.forEach((result, index) => {
const entity = entities[index];
if (result.status === "fulfilled") {
results.push(result.value);
} else {
logger.error(
`Failed to update entity ${entity.uuid} in ${batchId}:`,
{ error: result.reason },
);
results.push({
uuid: entity.uuid,
name: entity.name,
type: entity.type,
success: false,
error: result.reason?.message || "Unknown error",
});
}
});
const successful = results.filter((r) => r.success).length;
const failed = results.filter((r) => !r.success).length;
logger.info(`Completed ${batchId}`, {
total: entities.length,
successful,
failed,
successRate: `${((successful / entities.length) * 100).toFixed(2)}%`,
});
return {
batchId,
batchNumber,
totalBatches,
entities: entities.length,
successful,
failed,
results,
};
} catch (error) {
logger.error(`Fatal error in ${batchId}:`, { error });
throw error;
}
},
});
/**
* Fetch all entities from Neo4j database
*/
async function getAllEntities(userId?: string): Promise<EntityNode[]> {
try {
const query = userId
? `MATCH (entity:Entity {userId: $userId}) RETURN entity ORDER BY entity.createdAt`
: `MATCH (entity:Entity) RETURN entity ORDER BY entity.createdAt`;
const params = userId ? { userId } : {};
const records = await runQuery(query, params);
return records.map((record) => {
const entityProps = record.get("entity").properties;
return {
uuid: entityProps.uuid,
name: entityProps.name,
type: entityProps.type,
attributes: JSON.parse(entityProps.attributes || "{}"),
nameEmbedding: entityProps.nameEmbedding || [],
typeEmbedding: entityProps.typeEmbedding || [],
createdAt: new Date(entityProps.createdAt),
userId: entityProps.userId,
space: entityProps.space,
};
});
} catch (error) {
logger.error("Error fetching entities:", { error });
throw new Error(`Failed to fetch entities: ${error}`);
}
}
/**
* Update embeddings for a single entity
*/
async function updateEntityEmbeddings(
entity: EntityNode,
): Promise<EntityUpdateResult> {
try {
logger.info(
`Updating embeddings for entity: ${entity.name} (${entity.type})`,
);
// Generate new embeddings
const [nameEmbedding, typeEmbedding] = await Promise.all([
getEmbedding(entity.name),
getEmbedding(entity.type),
]);
// Update entity in Neo4j
const updateQuery = `
MATCH (entity:Entity {uuid: $uuid})
SET
entity.nameEmbedding = $nameEmbedding,
entity.typeEmbedding = $typeEmbedding,
entity.updatedAt = $updatedAt
RETURN entity.uuid as uuid
`;
const updateParams = {
uuid: entity.uuid,
nameEmbedding,
typeEmbedding,
updatedAt: new Date().toISOString(),
};
const result = await runQuery(updateQuery, updateParams);
if (result.length === 0) {
throw new Error(`Entity ${entity.uuid} not found during update`);
}
return {
uuid: entity.uuid,
name: entity.name,
type: entity.type,
success: true,
};
} catch (error) {
return {
uuid: entity.uuid,
name: entity.name,
type: entity.type,
success: false,
error: error instanceof Error ? error.message : "Unknown error", // TODO: fix this
};
}
}
/**
* Helper function to trigger the entity embeddings update
*/
export async function triggerEntityEmbeddingsUpdate(
options: {
userId?: string;
batchSize?: number;
} = {},
) {
try {
const result = await updateAllEntityEmbeddings.trigger(options);
logger.info(`Triggered entity embeddings update with run ID: ${result.id}`);
return result;
} catch (error) {
logger.error("Failed to trigger entity embeddings update:", { error });
throw error;
}
}
/**
* Update a single entity's embeddings (useful for individual updates)
*/
export async function updateSingleEntityEmbeddings(
entityUuid: string,
): Promise<EntityUpdateResult> {
try {
const query = `MATCH (entity:Entity {uuid: $uuid}) RETURN entity`;
const records = await runQuery(query, { uuid: entityUuid });
if (records.length === 0) {
throw new Error(`Entity with UUID ${entityUuid} not found`);
}
const entityProps = records[0].get("entity").properties;
const entity: EntityNode = {
uuid: entityProps.uuid,
name: entityProps.name,
type: entityProps.type,
attributes: JSON.parse(entityProps.attributes || "{}"),
nameEmbedding: entityProps.nameEmbedding || [],
typeEmbedding: entityProps.typeEmbedding || [],
createdAt: new Date(entityProps.createdAt),
userId: entityProps.userId,
space: entityProps.space,
};
return await updateEntityEmbeddings(entity);
} catch (error) {
logger.error(`Error updating single entity ${entityUuid}:`, { error });
throw error;
}
}

2
packages/core-cli/.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
.tshy/
.tshy-build/

File diff suppressed because one or more lines are too long

View File

@ -1,19 +0,0 @@
import { Command } from "commander";
import { z } from "zod";
export declare const CommonCommandOptions: z.ZodObject<{
logLevel: z.ZodDefault<z.ZodEnum<["debug", "info", "log", "warn", "error", "none"]>>;
}, "strip", z.ZodTypeAny, {
logLevel: "error" | "none" | "warn" | "info" | "log" | "debug";
}, {
logLevel?: "error" | "none" | "warn" | "info" | "log" | "debug" | undefined;
}>;
export type CommonCommandOptions = z.infer<typeof CommonCommandOptions>;
export declare function commonOptions(command: Command): Command;
export declare class SkipLoggingError extends Error {
}
export declare class SkipCommandError extends Error {
}
export declare class OutroCommandError extends SkipCommandError {
}
export declare function wrapCommandAction<T extends z.AnyZodObject, TResult>(name: string, schema: T, options: unknown, action: (opts: z.output<T>) => Promise<TResult>): Promise<TResult | undefined>;
export declare function installExitHandler(): void;

View File

@ -1,54 +0,0 @@
import { z } from "zod";
import { fromZodError } from "zod-validation-error";
import { logger } from "../utils/logger.js";
import { outro } from "@clack/prompts";
import { chalkError } from "../utils/cliOutput.js";
export const CommonCommandOptions = z.object({
logLevel: z.enum(["debug", "info", "log", "warn", "error", "none"]).default("log"),
});
export function commonOptions(command) {
return command.option("-l, --log-level <level>", "The CLI log level to use (debug, info, log, warn, error, none).", "log");
}
export class SkipLoggingError extends Error {
}
export class SkipCommandError extends Error {
}
export class OutroCommandError extends SkipCommandError {
}
export async function wrapCommandAction(name, schema, options, action) {
try {
const parsedOptions = schema.safeParse(options);
if (!parsedOptions.success) {
throw new Error(fromZodError(parsedOptions.error).toString());
}
logger.loggerLevel = parsedOptions.data.logLevel;
logger.debug(`Running "${name}" with the following options`, {
options: options,
});
const result = await action(parsedOptions.data);
return result;
}
catch (e) {
if (e instanceof SkipLoggingError) {
}
else if (e instanceof OutroCommandError) {
outro("Operation cancelled");
}
else if (e instanceof SkipCommandError) {
// do nothing
}
else {
logger.log(`${chalkError("X Error:")} ${e instanceof Error ? e.message : String(e)}`);
}
throw e;
}
}
export function installExitHandler() {
process.on("SIGINT", () => {
process.exit(0);
});
process.on("SIGTERM", () => {
process.exit(0);
});
}
//# sourceMappingURL=common.js.map

View File

@ -1 +0,0 @@
{"version":3,"file":"common.js","sourceRoot":"","sources":["../../../src/cli/common.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AACpD,OAAO,EAAE,MAAM,EAAE,MAAM,oBAAoB,CAAC;AAC5C,OAAO,EAAE,KAAK,EAAE,MAAM,gBAAgB,CAAC;AACvC,OAAO,EAAE,UAAU,EAAE,MAAM,uBAAuB,CAAC;AAEnD,MAAM,CAAC,MAAM,oBAAoB,GAAG,CAAC,CAAC,MAAM,CAAC;IAC3C,QAAQ,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,CAAC,CAAC,CAAC,OAAO,CAAC,KAAK,CAAC;CACnF,CAAC,CAAC;AAIH,MAAM,UAAU,aAAa,CAAC,OAAgB;IAC5C,OAAO,OAAO,CAAC,MAAM,CACnB,yBAAyB,EACzB,iEAAiE,EACjE,KAAK,CACN,CAAC;AACJ,CAAC;AAED,MAAM,OAAO,gBAAiB,SAAQ,KAAK;CAAG;AAC9C,MAAM,OAAO,gBAAiB,SAAQ,KAAK;CAAG;AAC9C,MAAM,OAAO,iBAAkB,SAAQ,gBAAgB;CAAG;AAE1D,MAAM,CAAC,KAAK,UAAU,iBAAiB,CACrC,IAAY,EACZ,MAAS,EACT,OAAgB,EAChB,MAA+C;IAE/C,IAAI,CAAC;QACH,MAAM,aAAa,GAAG,MAAM,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC;QAEhD,IAAI,CAAC,aAAa,CAAC,OAAO,EAAE,CAAC;YAC3B,MAAM,IAAI,KAAK,CAAC,YAAY,CAAC,aAAa,CAAC,KAAK,CAAC,CAAC,QAAQ,EAAE,CAAC,CAAC;QAChE,CAAC;QAED,MAAM,CAAC,WAAW,GAAG,aAAa,CAAC,IAAI,CAAC,QAAQ,CAAC;QAEjD,MAAM,CAAC,KAAK,CAAC,YAAY,IAAI,8BAA8B,EAAE;YAC3D,OAAO,EAAE,OAAO;SACjB,CAAC,CAAC;QAEH,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,aAAa,CAAC,IAAI,CAAC,CAAC;QAEhD,OAAO,MAAM,CAAC;IAChB,CAAC;IAAC,OAAO,CAAC,EAAE,CAAC;QACX,IAAI,CAAC,YAAY,gBAAgB,EAAE,CAAC;QACpC,CAAC;aAAM,IAAI,CAAC,YAAY,iBAAiB,EAAE,CAAC;YAC1C,KAAK,CAAC,qBAAqB,CAAC,CAAC;QAC/B,CAAC;aAAM,IAAI,CAAC,YAAY,gBAAgB,EAAE,CAAC;YACzC,aAAa;QACf,CAAC;aAAM,CAAC;YACN,MAAM,CAAC,GAAG,CAAC,GAAG,UAAU,CAAC,UAAU,CAAC,IAAI,CAAC,YAAY,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;QACxF,CAAC;QAED,MAAM,CAAC,CAAC;IACV,CAAC;AACH,CAAC;AAED,MAAM,UAAU,kBAAkB;IAChC,OAAO,CAAC,EAAE,CAAC,QAAQ,EAAE,GAAG,EAAE;QACxB,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC,CAAC,CAAC;IAEH,OAAO,CAAC,EAAE,CAAC,SAAS,EAAE,GAAG,EAAE;QACzB,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC,CAAC,CAAC;AACL,CAAC"}

View File

@ -1 +0,0 @@
export {};

View File

@ -1,2 +0,0 @@
export {};
//# sourceMappingURL=index.js.map

View File

@ -1 +0,0 @@
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/cli/index.ts"],"names":[],"mappings":""}

View File

@ -1 +0,0 @@
export {};

View File

@ -1,2 +0,0 @@
export {};
//# sourceMappingURL=init.js.map

View File

@ -1 +0,0 @@
{"version":3,"file":"init.js","sourceRoot":"","sources":["../../../src/commands/init.ts"],"names":[],"mappings":""}

View File

@ -1 +0,0 @@
export {};

View File

@ -1,2 +0,0 @@
export {};
//# sourceMappingURL=index.js.map

View File

@ -1 +0,0 @@
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":""}

View File

@ -1,25 +0,0 @@
import { TerminalLinkOptions } from "./terminalLink.js";
export declare const isInteractive: boolean;
export declare const isLinksSupported: boolean;
export declare const green = "#4FFF54";
export declare const purple = "#735BF3";
export declare function chalkGreen(text: string): string;
export declare function chalkPurple(text: string): string;
export declare function chalkGrey(text: string): string;
export declare function chalkError(text: string): string;
export declare function chalkWarning(text: string): string;
export declare function chalkSuccess(text: string): string;
export declare function chalkLink(text: string): string;
export declare function chalkWorker(text: string): string;
export declare function chalkTask(text: string): string;
export declare function chalkRun(text: string): string;
export declare function logo(): string;
export declare function prettyPrintDate(date?: Date): string;
export declare function prettyError(header: string, body?: string, footer?: string): void;
export declare function prettyWarning(header: string, body?: string, footer?: string): void;
export declare function aiHelpLink({ dashboardUrl, project, query, }: {
dashboardUrl: string;
project: string;
query: string;
}): void;
export declare function cliLink(text: string, url: string, options?: TerminalLinkOptions): string;

View File

@ -1,97 +0,0 @@
import { log } from "@clack/prompts";
import chalk from "chalk";
import { terminalLink } from "./terminalLink.js";
import { hasTTY } from "std-env";
export const isInteractive = hasTTY;
export const isLinksSupported = terminalLink.isSupported;
export const green = "#4FFF54";
export const purple = "#735BF3";
export function chalkGreen(text) {
return chalk.hex(green)(text);
}
export function chalkPurple(text) {
return chalk.hex(purple)(text);
}
export function chalkGrey(text) {
return chalk.hex("#878C99")(text);
}
export function chalkError(text) {
return chalk.hex("#E11D48")(text);
}
export function chalkWarning(text) {
return chalk.yellow(text);
}
export function chalkSuccess(text) {
return chalk.hex("#28BF5C")(text);
}
export function chalkLink(text) {
return chalk.underline.hex("#D7D9DD")(text);
}
export function chalkWorker(text) {
return chalk.hex("#FFFF89")(text);
}
export function chalkTask(text) {
return chalk.hex("#60A5FA")(text);
}
export function chalkRun(text) {
return chalk.hex("#A78BFA")(text);
}
export function logo() {
return `${chalk.hex(green).bold("Trigger")}${chalk.hex(purple).bold(".dev")}`;
}
// Mar 27 09:17:25.653
export function prettyPrintDate(date = new Date()) {
let formattedDate = new Intl.DateTimeFormat("en-US", {
month: "short",
day: "2-digit",
hour: "2-digit",
minute: "2-digit",
second: "2-digit",
hour12: false,
}).format(date);
// Append milliseconds
formattedDate += "." + ("00" + date.getMilliseconds()).slice(-3);
return formattedDate;
}
export function prettyError(header, body, footer) {
const prefix = "Error: ";
const indent = Array(prefix.length).fill(" ").join("");
const spacing = "\n\n";
const prettyPrefix = chalkError(prefix);
const withIndents = (text) => text
?.split("\n")
.map((line) => `${indent}${line}`)
.join("\n");
const prettyBody = withIndents(body?.trim());
const prettyFooter = withIndents(footer);
log.error(`${prettyPrefix}${header}${prettyBody ? `${spacing}${prettyBody}` : ""}${prettyFooter ? `${spacing}${prettyFooter}` : ""}`);
}
export function prettyWarning(header, body, footer) {
const prefix = "Warning: ";
const indent = Array(prefix.length).fill(" ").join("");
const spacing = "\n\n";
const prettyPrefix = chalkWarning(prefix);
const withIndents = (text) => text
?.split("\n")
.map((line) => `${indent}${line}`)
.join("\n");
const prettyBody = withIndents(body);
const prettyFooter = withIndents(footer);
log.warn(`${prettyPrefix}${header}${prettyBody ? `${spacing}${prettyBody}` : ""}${prettyFooter ? `${spacing}${prettyFooter}` : ""}`);
}
export function aiHelpLink({ dashboardUrl, project, query, }) {
const searchParams = new URLSearchParams();
//the max length for a URL is 1950 characters
const clippedQuery = query.slice(0, 1950);
searchParams.set("q", clippedQuery);
const url = new URL(`/projects/${project}/ai-help`, dashboardUrl);
url.search = searchParams.toString();
log.message(chalkLink(cliLink("💡 Get a fix for this error using AI", url.toString())));
}
export function cliLink(text, url, options) {
return terminalLink(text, url, {
fallback: (text, url) => `${text} ${url}`,
...options,
});
}
//# sourceMappingURL=cliOutput.js.map

View File

@ -1 +0,0 @@
{"version":3,"file":"cliOutput.js","sourceRoot":"","sources":["../../../src/utils/cliOutput.ts"],"names":[],"mappings":"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"}

View File

@ -1,42 +0,0 @@
import type { Message } from "esbuild";
export declare const LOGGER_LEVELS: {
readonly none: -1;
readonly error: 0;
readonly warn: 1;
readonly info: 2;
readonly log: 3;
readonly debug: 4;
};
export type LoggerLevel = keyof typeof LOGGER_LEVELS;
export type TableRow<Keys extends string> = Record<Keys, string>;
export declare class Logger {
constructor();
loggerLevel: "error" | "none" | "warn" | "info" | "log" | "debug";
columns: number;
debug: (...args: unknown[]) => void;
ignore: (...args: unknown[]) => void;
debugWithSanitization: (label: string, ...args: unknown[]) => void;
info: (...args: unknown[]) => void;
log: (...args: unknown[]) => void;
/** @deprecated **ONLY USE THIS IN THE CLI** - It will hang the process when used in deployed code (!) */
warn: (...args: unknown[]) => void;
/** @deprecated **ONLY USE THIS IN THE CLI** - It will hang the process when used in deployed code (!) */
error: (...args: unknown[]) => void;
table<Keys extends string>(data: TableRow<Keys>[], level?: Exclude<LoggerLevel, "none">): void;
private doLog;
private formatMessage;
}
/**
* A drop-in replacement for `console` for outputting logging messages.
*
* Errors and Warnings will get additional formatting to highlight them to the user.
* You can also set a `logger.loggerLevel` value to one of "debug", "log", "warn" or "error",
* to filter out logging messages.
*/
export declare const logger: Logger;
export declare function logBuildWarnings(warnings: Message[]): void;
/**
* Logs all errors/warnings associated with an esbuild BuildFailure in the same
* style esbuild would.
*/
export declare function logBuildFailure(errors: Message[], warnings: Message[]): void;

View File

@ -1,111 +0,0 @@
// This is a copy of the logger utility from the wrangler repo: https://github.com/cloudflare/workers-sdk/blob/main/packages/wrangler/src/logger.ts
import { format } from "node:util";
import chalk from "chalk";
import CLITable from "cli-table3";
import { formatMessagesSync } from "esbuild";
import { env } from "std-env";
export const LOGGER_LEVELS = {
none: -1,
error: 0,
warn: 1,
info: 2,
log: 3,
debug: 4,
};
/** A map from LOGGER_LEVEL to the error `kind` needed by `formatMessagesSync()`. */
const LOGGER_LEVEL_FORMAT_TYPE_MAP = {
error: "error",
warn: "warning",
info: undefined,
log: undefined,
debug: undefined,
};
function getLoggerLevel() {
const fromEnv = env.TRIGGER_LOG_LEVEL?.toLowerCase();
if (fromEnv !== undefined) {
if (fromEnv in LOGGER_LEVELS)
return fromEnv;
const expected = Object.keys(LOGGER_LEVELS)
.map((level) => `"${level}"`)
.join(" | ");
console.warn(`Unrecognised TRIGGER_LOG_LEVEL value ${JSON.stringify(fromEnv)}, expected ${expected}, defaulting to "log"...`);
}
return "log";
}
export class Logger {
constructor() { }
loggerLevel = getLoggerLevel();
columns = process.stdout.columns;
debug = (...args) => this.doLog("debug", args);
ignore = (...args) => { };
debugWithSanitization = (label, ...args) => {
this.doLog("debug", [label, ...args]);
};
info = (...args) => this.doLog("info", args);
log = (...args) => this.doLog("log", args);
/** @deprecated **ONLY USE THIS IN THE CLI** - It will hang the process when used in deployed code (!) */
warn = (...args) => this.doLog("warn", args);
/** @deprecated **ONLY USE THIS IN THE CLI** - It will hang the process when used in deployed code (!) */
error = (...args) => this.doLog("error", args);
table(data, level) {
const keys = data.length === 0 ? [] : Object.keys(data[0]);
const t = new CLITable({
head: keys,
style: {
head: chalk.level ? ["blue"] : [],
border: chalk.level ? ["gray"] : [],
},
});
t.push(...data.map((row) => keys.map((k) => row[k])));
return this.doLog(level ?? "log", [t.toString()]);
}
doLog(messageLevel, args) {
const message = this.formatMessage(messageLevel, format(...args));
// only send logs to the terminal if their level is at least the configured log-level
if (LOGGER_LEVELS[this.loggerLevel] >= LOGGER_LEVELS[messageLevel]) {
console[messageLevel](message);
}
}
formatMessage(level, message) {
const kind = LOGGER_LEVEL_FORMAT_TYPE_MAP[level];
if (kind) {
// Format the message using the esbuild formatter.
// The first line of the message is the main `text`,
// subsequent lines are put into the `notes`.
const [firstLine, ...otherLines] = message.split("\n");
const notes = otherLines.length > 0 ? otherLines.map((text) => ({ text })) : undefined;
return formatMessagesSync([{ text: firstLine, notes }], {
color: true,
kind,
terminalWidth: this.columns,
})[0];
}
else {
return message;
}
}
}
/**
* A drop-in replacement for `console` for outputting logging messages.
*
* Errors and Warnings will get additional formatting to highlight them to the user.
* You can also set a `logger.loggerLevel` value to one of "debug", "log", "warn" or "error",
* to filter out logging messages.
*/
export const logger = new Logger();
export function logBuildWarnings(warnings) {
const logs = formatMessagesSync(warnings, { kind: "warning", color: true });
for (const log of logs)
console.warn(log);
}
/**
* Logs all errors/warnings associated with an esbuild BuildFailure in the same
* style esbuild would.
*/
export function logBuildFailure(errors, warnings) {
const logs = formatMessagesSync(errors, { kind: "error", color: true });
for (const log of logs)
console.error(log);
logBuildWarnings(warnings);
}
//# sourceMappingURL=logger.js.map

View File

@ -1 +0,0 @@
{"version":3,"file":"logger.js","sourceRoot":"","sources":["../../../src/utils/logger.ts"],"names":[],"mappings":"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"}

View File

@ -1,15 +0,0 @@
/**
Creates a supports hyperlinks check for a given stream.
@param stream - Optional stream to check for hyperlink support.
@returns boolean indicating whether hyperlinks are supported.
*/
export declare function createSupportsHyperlinks(stream: NodeJS.WriteStream): boolean;
/** Object containing hyperlink support status for stdout and stderr. */
declare const supportsHyperlinks: {
/** Whether stdout supports hyperlinks. */
stdout: boolean;
/** Whether stderr supports hyperlinks. */
stderr: boolean;
};
export default supportsHyperlinks;

View File

@ -1,122 +0,0 @@
/*
MIT License
Copyright (c) Sindre Sorhus <sindresorhus@gmail.com> (https://sindresorhus.com)
Copyright (c) James Talmage <james@talmage.io> (https://github.com/jamestalmage)
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
import { createSupportsColor } from "supports-color";
import hasFlag from "has-flag";
function parseVersion(versionString = "") {
if (/^\d{3,4}$/.test(versionString)) {
// Env var doesn't always use dots. example: 4601 => 46.1.0
const match = /(\d{1,2})(\d{2})/.exec(versionString) ?? [];
return {
major: 0,
minor: Number.parseInt(match[1] ?? "0", 10),
patch: Number.parseInt(match[2] ?? "0", 10),
};
}
const versions = (versionString ?? "").split(".").map((n) => Number.parseInt(n, 10));
return {
major: versions[0] ?? 0,
minor: versions[1] ?? 0,
patch: versions[2] ?? 0,
};
}
/**
Creates a supports hyperlinks check for a given stream.
@param stream - Optional stream to check for hyperlink support.
@returns boolean indicating whether hyperlinks are supported.
*/
export function createSupportsHyperlinks(stream) {
const { CI, CURSOR_TRACE_ID, FORCE_HYPERLINK, NETLIFY, TEAMCITY_VERSION, TERM_PROGRAM, TERM_PROGRAM_VERSION, VTE_VERSION, TERM, } = process.env;
if (FORCE_HYPERLINK) {
return !(FORCE_HYPERLINK.length > 0 && Number.parseInt(FORCE_HYPERLINK, 10) === 0);
}
if (hasFlag("no-hyperlink") ||
hasFlag("no-hyperlinks") ||
hasFlag("hyperlink=false") ||
hasFlag("hyperlink=never")) {
return false;
}
if (hasFlag("hyperlink=true") || hasFlag("hyperlink=always")) {
return true;
}
// Netlify does not run a TTY, it does not need `supportsColor` check
if (NETLIFY) {
return true;
}
// If they specify no colors, they probably don't want hyperlinks.
if (!createSupportsColor(stream)) {
return false;
}
if (stream && !stream.isTTY) {
return false;
}
// Windows Terminal
if ("WT_SESSION" in process.env) {
return true;
}
if (process.platform === "win32") {
return false;
}
if (CI) {
return false;
}
if (TEAMCITY_VERSION) {
return false;
}
if (CURSOR_TRACE_ID) {
return true;
}
if (TERM_PROGRAM) {
const version = parseVersion(TERM_PROGRAM_VERSION);
switch (TERM_PROGRAM) {
case "iTerm.app": {
if (version.major === 3) {
return version.minor >= 1;
}
return version.major > 3;
}
case "WezTerm": {
return version.major >= 20_200_620;
}
case "vscode": {
// eslint-disable-next-line no-mixed-operators
return version.major > 1 || (version.major === 1 && version.minor >= 72);
}
case "ghostty": {
return true;
}
// No default
}
}
if (VTE_VERSION) {
// 0.50.0 was supposed to support hyperlinks, but throws a segfault
if (VTE_VERSION === "0.50.0") {
return false;
}
const version = parseVersion(VTE_VERSION);
return version.major > 0 || version.minor >= 50;
}
switch (TERM) {
case "alacritty": {
// Support added in v0.11 (2022-10-13)
return true;
}
// No default
}
return false;
}
/** Object containing hyperlink support status for stdout and stderr. */
const supportsHyperlinks = {
/** Whether stdout supports hyperlinks. */
stdout: createSupportsHyperlinks(process.stdout),
/** Whether stderr supports hyperlinks. */
stderr: createSupportsHyperlinks(process.stderr),
};
export default supportsHyperlinks;
//# sourceMappingURL=supportsHyperlinks.js.map

View File

@ -1 +0,0 @@
{"version":3,"file":"supportsHyperlinks.js","sourceRoot":"","sources":["../../../src/utils/supportsHyperlinks.ts"],"names":[],"mappings":"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"}

View File

@ -1,56 +0,0 @@
export type TerminalLinkOptions = {
/**
Override the default fallback. If false, the fallback will be disabled.
@default `${text} (${url})`
*/
readonly fallback?: ((text: string, url: string) => string) | boolean;
};
/**
Create a clickable link in the terminal's stdout.
[Supported terminals.](https://gist.github.com/egmontkob/eb114294efbcd5adb1944c9f3cb5feda)
For unsupported terminals, the link will be printed in parens after the text: `My website (https://sindresorhus.com)`,
unless the fallback is disabled by setting the `fallback` option to `false`.
@param text - Text to linkify.
@param url - URL to link to.
@example
```
import terminalLink from 'terminal-link';
const link = terminalLink('My Website', 'https://sindresorhus.com');
console.log(link);
```
@deprecated The default fallback is broken in some terminals. Please use `cliLink` instead.
*/
declare function terminalLink(text: string, url: string, { target, ...options }?: {
target?: "stdout" | "stderr";
} & TerminalLinkOptions): string;
declare namespace terminalLink {
var isSupported: boolean;
var stderr: typeof terminalLinkStderr;
}
/**
Create a clickable link in the terminal's stderr.
[Supported terminals.](https://gist.github.com/egmontkob/eb114294efbcd5adb1944c9f3cb5feda)
For unsupported terminals, the link will be printed in parens after the text: `My website (https://sindresorhus.com)`.
@param text - Text to linkify.
@param url - URL to link to.
@example
```
import terminalLink from 'terminal-link';
const link = terminalLink.stderr('My Website', 'https://sindresorhus.com');
console.error(link);
```
*/
declare function terminalLinkStderr(text: string, url: string, options?: TerminalLinkOptions): string;
declare namespace terminalLinkStderr {
var isSupported: boolean;
}
export { terminalLink };

View File

@ -1,76 +0,0 @@
/*
MIT License
Copyright (c) Sindre Sorhus <sindresorhus@gmail.com> (https://sindresorhus.com)
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
import ansiEscapes from "ansi-escapes";
import supportsHyperlinks from "./supportsHyperlinks.js";
/**
Create a clickable link in the terminal's stdout.
[Supported terminals.](https://gist.github.com/egmontkob/eb114294efbcd5adb1944c9f3cb5feda)
For unsupported terminals, the link will be printed in parens after the text: `My website (https://sindresorhus.com)`,
unless the fallback is disabled by setting the `fallback` option to `false`.
@param text - Text to linkify.
@param url - URL to link to.
@example
```
import terminalLink from 'terminal-link';
const link = terminalLink('My Website', 'https://sindresorhus.com');
console.log(link);
```
@deprecated The default fallback is broken in some terminals. Please use `cliLink` instead.
*/
function terminalLink(text, url, { target = "stdout", ...options } = {}) {
if (!supportsHyperlinks[target]) {
// If the fallback has been explicitly disabled, don't modify the text itself.
if (options.fallback === false) {
return text;
}
return typeof options.fallback === "function"
? options.fallback(text, url)
: `${text} (\u200B${url}\u200B)`;
}
return ansiEscapes.link(text, url);
}
/**
Check whether the terminal supports links.
Prefer just using the default fallback or the `fallback` option whenever possible.
*/
terminalLink.isSupported = supportsHyperlinks.stdout;
terminalLink.stderr = terminalLinkStderr;
/**
Create a clickable link in the terminal's stderr.
[Supported terminals.](https://gist.github.com/egmontkob/eb114294efbcd5adb1944c9f3cb5feda)
For unsupported terminals, the link will be printed in parens after the text: `My website (https://sindresorhus.com)`.
@param text - Text to linkify.
@param url - URL to link to.
@example
```
import terminalLink from 'terminal-link';
const link = terminalLink.stderr('My Website', 'https://sindresorhus.com');
console.error(link);
```
*/
function terminalLinkStderr(text, url, options = {}) {
return terminalLink(text, url, { target: "stderr", ...options });
}
/**
Check whether the terminal's stderr supports links.
Prefer just using the default fallback or the `fallback` option whenever possible.
*/
terminalLinkStderr.isSupported = supportsHyperlinks.stderr;
export { terminalLink };
//# sourceMappingURL=terminalLink.js.map

View File

@ -1 +0,0 @@
{"version":3,"file":"terminalLink.js","sourceRoot":"","sources":["../../../src/utils/terminalLink.ts"],"names":[],"mappings":"AAAA;;;;;;EAME;AAEF,OAAO,WAAW,MAAM,cAAc,CAAC;AACvC,OAAO,kBAAkB,MAAM,yBAAyB,CAAC;AAUzD;;;;;;;;;;;;;;;;;;;EAmBE;AACF,SAAS,YAAY,CACnB,IAAY,EACZ,GAAW,EACX,EAAE,MAAM,GAAG,QAAQ,EAAE,GAAG,OAAO,KAA6D,EAAE;IAE9F,IAAI,CAAC,kBAAkB,CAAC,MAAM,CAAC,EAAE,CAAC;QAChC,8EAA8E;QAC9E,IAAI,OAAO,CAAC,QAAQ,KAAK,KAAK,EAAE,CAAC;YAC/B,OAAO,IAAI,CAAC;QACd,CAAC;QAED,OAAO,OAAO,OAAO,CAAC,QAAQ,KAAK,UAAU;YAC3C,CAAC,CAAC,OAAO,CAAC,QAAQ,CAAC,IAAI,EAAE,GAAG,CAAC;YAC7B,CAAC,CAAC,GAAG,IAAI,WAAW,GAAG,SAAS,CAAC;IACrC,CAAC;IAED,OAAO,WAAW,CAAC,IAAI,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC;AACrC,CAAC;AACD;;;;EAIE;AACF,YAAY,CAAC,WAAW,GAAG,kBAAkB,CAAC,MAAM,CAAC;AACrD,YAAY,CAAC,MAAM,GAAG,kBAAkB,CAAC;AAEzC;;;;;;;;;;;;;;;;EAgBE;AACF,SAAS,kBAAkB,CAAC,IAAY,EAAE,GAAW,EAAE,UAA+B,EAAE;IACtF,OAAO,YAAY,CAAC,IAAI,EAAE,GAAG,EAAE,EAAE,MAAM,EAAE,QAAQ,EAAE,GAAG,OAAO,EAAE,CAAC,CAAC;AACnE,CAAC;AAED;;;;EAIE;AACF,kBAAkB,CAAC,WAAW,GAAG,kBAAkB,CAAC,MAAM,CAAC;AAE3D,OAAO,EAAE,YAAY,EAAE,CAAC"}

View File

@ -1,8 +0,0 @@
{
"extends": "../tsconfig.json",
"compilerOptions": {
"rootDir": "../src",
"module": "nodenext",
"moduleResolution": "nodenext"
}
}

View File

@ -1,16 +0,0 @@
{
"extends": "./build.json",
"include": [
"../src/**/*.ts",
"../src/**/*.mts",
"../src/**/*.tsx",
"../src/**/*.json"
],
"exclude": [
"../**/*.test.ts",
"../src/package.json"
],
"compilerOptions": {
"outDir": "../.tshy-build/esm"
}
}

193
packages/core-cli/README.md Normal file
View File

@ -0,0 +1,193 @@
# Core CLI
🧠 **CORE - Contextual Observation & Recall Engine**
A Command-Line Interface for setting up and managing the Core development environment.
## Installation
```bash
npm install -g @redplanethq/core
```
## Commands
### `core init`
**One-time setup command** - Initializes the Core development environment with full configuration.
### `core start`
**Daily usage command** - Starts all Core services (Docker containers).
### `core stop`
**Daily usage command** - Stops all Core services (Docker containers).
## Getting Started
### Prerequisites
- **Node.js** (v18.20.0 or higher)
- **Docker** and **Docker Compose**
- **Git**
- **pnpm** package manager
### Initial Setup
1. **Run the initialization command:**
```bash
core init
```
2. **The CLI will guide you through the complete setup process:**
#### Step 1: Repository Validation
- The CLI checks if you're in the Core repository
- If not, it offers to clone the repository for you
- Choose **Yes** to clone automatically, or **No** to clone manually
#### Step 2: Environment Configuration
- Copies `.env.example` to `.env` in the root directory
- Copies `trigger/.env.example` to `trigger/.env`
- Skips copying if `.env` files already exist
#### Step 3: Docker Services Startup
- Starts main Core services: `docker compose up -d`
- Starts Trigger.dev services: `docker compose up -d` (in trigger/ directory)
- Shows real-time output with progress indicators
#### Step 4: Database Health Check
- Verifies PostgreSQL is running on `localhost:5432`
- Retries for up to 60 seconds if needed
#### Step 5: Trigger.dev Setup (Interactive)
- **If Trigger.dev is not configured:**
1. Prompts you to open http://localhost:8030
2. Asks you to login to Trigger.dev
3. Guides you to create an organization and project
4. Collects your Project ID and Secret Key
5. Updates `.env` with your Trigger.dev configuration
6. Restarts Core services with new configuration
- **If Trigger.dev is already configured:**
- Skips setup and shows "Configuration already exists" message
#### Step 6: Docker Registry Login
- Displays docker login command with credentials from `.env`
- Waits for you to complete the login process
#### Step 7: Trigger.dev Task Deployment
- Automatically runs: `npx trigger.dev@v4-beta login -a http://localhost:8030`
- Deploys tasks with: `pnpm trigger:deploy`
- Shows manual deployment instructions if automatic deployment fails
#### Step 8: Setup Complete!
- Confirms all services are running
- Shows service URLs and connection information
## Daily Usage
After initial setup, use these commands for daily development:
### Start Services
```bash
core start
```
Starts all Docker containers for Core development.
### Stop Services
```bash
core stop
```
Stops all Docker containers.
## Service URLs
After setup, these services will be available:
- **Core Application**: http://localhost:3033
- **Trigger.dev**: http://localhost:8030
- **PostgreSQL**: localhost:5432
## Troubleshooting
### Repository Not Found
If you run commands outside the Core repository:
- The CLI will offer to clone the repository automatically
- Choose **Yes** to clone in the current directory
- Or navigate to the Core repository manually
### Docker Issues
- Ensure Docker is running
- Check Docker Compose is installed
- Verify you have sufficient system resources
### Trigger.dev Setup Issues
- Check container logs: `docker logs trigger-webapp --tail 50`
- Ensure you can access http://localhost:8030
- Verify your network allows connections to localhost
### Environment Variables
The CLI automatically manages these environment variables:
- `TRIGGER_PROJECT_ID` - Your Trigger.dev project ID
- `TRIGGER_SECRET_KEY` - Your Trigger.dev secret key
- Docker registry credentials for deployment
### Manual Trigger.dev Deployment
If automatic deployment fails, run manually:
```bash
npx trigger.dev@v4-beta login -a http://localhost:8030
pnpm trigger:deploy
```
## Development Workflow
1. **First time setup:** `core init`
2. **Daily development:**
- `core start` - Start your development environment
- Do your development work
- `core stop` - Stop services when done
## Support
For issues and questions:
- Check the main Core repository: https://github.com/redplanethq/core
- Review Docker container logs for troubleshooting
- Ensure all prerequisites are properly installed
## Features
- 🚀 **One-command setup** - Complete environment initialization
- 🔄 **Smart configuration** - Skips already configured components
- 📱 **Real-time feedback** - Live progress indicators and output
- 🐳 **Docker integration** - Full container lifecycle management
- 🔧 **Interactive setup** - Guided configuration process
- 🎯 **Error handling** - Graceful failure with recovery instructions
---
**Happy coding with Core!** 🎉

View File

@ -1,6 +1,6 @@
{
"name": "@redplanethq/core",
"version": "0.1.0",
"version": "0.1.1",
"description": "A Command-Line Interface for Core",
"type": "module",
"license": "MIT",
@ -60,11 +60,10 @@
"scripts": {
"clean": "rimraf dist .tshy .tshy-build .turbo",
"typecheck": "tsc -p tsconfig.src.json --noEmit",
"build": "tshy && pnpm run update-version",
"build": "tshy",
"dev": "tshy --watch",
"test": "vitest",
"test:e2e": "vitest --run -c ./e2e/vitest.config.ts",
"update-version": "tsx ../../scripts/updateVersion.ts"
"test:e2e": "vitest --run -c ./e2e/vitest.config.ts"
},
"dependencies": {
"@clack/prompts": "^0.10.0",

View File

@ -1 +1,25 @@
import { Command } from "commander";
import { initCommand } from "../commands/init.js";
import { startCommand } from "../commands/start.js";
import { stopCommand } from "../commands/stop.js";
const program = new Command();
program.name("core").description("Core CLI - A Command-Line Interface for Core").version("0.1.0");
program
.command("init")
.description("Initialize Core development environment (run once)")
.action(initCommand);
program
.command("start")
.description("Start Core development environment")
.action(startCommand);
program
.command("stop")
.description("Stop Core development environment")
.action(stopCommand);
program.parse(process.argv);

View File

@ -0,0 +1,324 @@
import { intro, outro, text, confirm, spinner, note, log } from "@clack/prompts";
import { isValidCoreRepo } from "../utils/git.js";
import { fileExists, updateEnvFile } from "../utils/file.js";
import { checkPostgresHealth } from "../utils/docker.js";
import { executeDockerCommandInteractive } from "../utils/docker-interactive.js";
import { printCoreBrainLogo } from "../utils/ascii.js";
import { setupEnvFile } from "../utils/env.js";
import { hasTriggerConfig } from "../utils/env-checker.js";
import path from "path";
export async function initCommand() {
// Display the CORE brain logo
printCoreBrainLogo();
intro("🚀 Core Development Environment Setup");
// Step 1: Validate repository
if (!isValidCoreRepo()) {
log.warning("This directory is not a Core repository");
note(
"The Core repository is required to run the development environment.\nWould you like to clone it in the current directory?",
"🔍 Repository Not Found"
);
const shouldClone = await confirm({
message: "Clone the Core repository here?",
});
if (!shouldClone) {
outro("❌ Setup cancelled. Please navigate to the Core repository or clone it first.");
process.exit(1);
}
// Clone the repository
try {
await executeDockerCommandInteractive("git clone https://github.com/redplanethq/core.git .", {
cwd: process.cwd(),
message: "Cloning Core repository...",
showOutput: true,
});
log.success("Core repository cloned successfully!");
note(
'Please run "core init" again to initialize the development environment.',
"✅ Repository Ready"
);
outro("🎉 Core repository is now available!");
process.exit(0);
} catch (error: any) {
outro(`❌ Failed to clone repository: ${error.message}`);
process.exit(1);
}
}
const rootDir = process.cwd();
const triggerDir = path.join(rootDir, "trigger");
try {
// Step 2: Setup .env file in root
const s1 = spinner();
s1.start("Setting up .env file in root folder...");
const envPath = path.join(rootDir, ".env");
const envExists = await fileExists(envPath);
try {
await setupEnvFile(rootDir, "root");
if (envExists) {
s1.stop("✅ .env file already exists in root");
} else {
s1.stop("✅ Copied .env.example to .env");
}
} catch (error: any) {
s1.stop(error.message);
process.exit(1);
}
// Step 3: Docker compose up -d in root
try {
await executeDockerCommandInteractive("docker compose up -d", {
cwd: rootDir,
message: "Starting Docker containers in root...",
showOutput: true,
});
} catch (error: any) {
throw error;
}
// Step 4: Check if postgres is running
const s3 = spinner();
s3.start("Checking PostgreSQL connection...");
let retries = 0;
const maxRetries = 30;
while (retries < maxRetries) {
if (await checkPostgresHealth()) {
s3.stop("PostgreSQL is running on localhost:5432");
break;
}
await new Promise((resolve) => setTimeout(resolve, 2000));
retries++;
}
if (retries >= maxRetries) {
s3.stop("L PostgreSQL not accessible on localhost:5432");
outro("Please check your Docker setup and try again");
process.exit(1);
}
// Step 5: Setup .env file in trigger
const s4 = spinner();
s4.start("Setting up .env file in trigger folder...");
const triggerEnvPath = path.join(triggerDir, ".env");
const triggerEnvExists = await fileExists(triggerEnvPath);
try {
await setupEnvFile(triggerDir, "trigger");
if (triggerEnvExists) {
s4.stop("✅ .env file already exists in trigger");
} else {
s4.stop("✅ Copied trigger .env.example to trigger/.env");
}
} catch (error: any) {
s4.stop(error.message);
process.exit(1);
}
// Step 6: Docker compose up for trigger
try {
await executeDockerCommandInteractive("docker compose up -d", {
cwd: triggerDir,
message: "Starting Trigger.dev containers...",
showOutput: true,
});
} catch (error: any) {
throw error;
}
// Step 7: Check if Trigger.dev configuration already exists
const triggerConfigExists = await hasTriggerConfig(envPath);
if (triggerConfigExists) {
note(
"✅ Trigger.dev configuration already exists in .env file\n Skipping Trigger.dev setup steps...",
"Configuration Found"
);
} else {
// Step 8: Show login instructions
outro("🎉 Docker containers are now running!");
note(
"1. Open http://localhost:8030 in your browser\n2. Login to Trigger.dev (check container logs with: docker logs trigger-webapp --tail 50)",
"Next Steps"
);
const loginConfirmed = await confirm({
message: "Have you logged in to Trigger.dev successfully?",
});
if (!loginConfirmed) {
outro("❌ Setup cancelled. Please login to Trigger.dev first and run the command again.");
process.exit(1);
}
// Step 9: Get project details
note(
"1. Create a new organization and project\n2. Go to project settings\n3. Copy the Project ID and Secret Key",
"In Trigger.dev (http://localhost:8030)"
);
const projectCreated = await confirm({
message: "Have you created an organization and project in Trigger.dev?",
});
if (!projectCreated) {
outro(
"❌ Setup cancelled. Please create an organization and project first and run the command again."
);
process.exit(1);
}
// Step 10: Get project ID and secret
const projectId = await text({
message: "Enter your Trigger.dev Project ID:",
validate: (value) => {
if (!value || value.length === 0) {
return "Project ID is required";
}
return;
},
});
const secretKey = await text({
message: "Enter your Trigger.dev Secret Key for production:",
validate: (value) => {
if (!value || value.length === 0) {
return "Secret Key is required";
}
return;
},
});
// Step 11: Update .env with project details
const s6 = spinner();
s6.start("Updating .env with Trigger.dev configuration...");
try {
await updateEnvFile(envPath, "TRIGGER_PROJECT_ID", projectId as string);
await updateEnvFile(envPath, "TRIGGER_SECRET_KEY", secretKey as string);
s6.stop("✅ Updated .env with Trigger.dev configuration");
} catch (error: any) {
s6.stop("❌ Failed to update .env file");
throw error;
}
// Step 12: Restart root docker-compose with new configuration
try {
await executeDockerCommandInteractive("docker compose down", {
cwd: rootDir,
message: "Stopping Core services...",
showOutput: true,
});
await executeDockerCommandInteractive("docker compose up -d", {
cwd: rootDir,
message: "Starting Core services with new Trigger.dev configuration...",
showOutput: true,
});
} catch (error: any) {
throw error;
}
}
// Step 13: Show docker login instructions
note("Run the following command to login to Docker registry:", "🐳 Docker Registry Login");
try {
// Read env file to get docker registry details
const envContent = await import("fs").then((fs) =>
fs.promises.readFile(triggerEnvPath, "utf8")
);
const envLines = envContent.split("\n");
const getEnvValue = (key: string) => {
const line = envLines.find((l) => l.startsWith(`${key}=`));
return line ? line.split("=")[1] : "";
};
const dockerRegistryUrl = getEnvValue("DOCKER_REGISTRY_URL");
const dockerRegistryUsername = getEnvValue("DOCKER_REGISTRY_USERNAME");
const dockerRegistryPassword = getEnvValue("DOCKER_REGISTRY_PASSWORD");
log.info(
`docker login ${dockerRegistryUrl} -u ${dockerRegistryUsername} -p ${dockerRegistryPassword}`
);
} catch (error) {
log.info("docker login <REGISTRY_URL> -u <USERNAME> -p <PASSWORD>");
}
const dockerLoginConfirmed = await confirm({
message: "Have you completed the Docker login successfully?",
});
if (!dockerLoginConfirmed) {
outro("❌ Setup cancelled. Please complete Docker login first and run the command again.");
process.exit(1);
}
// Step 14: Deploy Trigger.dev tasks
note(
"We'll now deploy the trigger tasks to your Trigger.dev instance.",
"🚀 Deploying Trigger.dev tasks"
);
try {
// Login to trigger.dev CLI
await executeDockerCommandInteractive(
"npx -y trigger.dev@v4-beta login -a http://localhost:8030",
{
cwd: rootDir,
message: "Logging in to Trigger.dev CLI...",
showOutput: true,
}
);
// Deploy trigger tasks
await executeDockerCommandInteractive("pnpm trigger:deploy", {
cwd: rootDir,
message: "Deploying Trigger.dev tasks...",
showOutput: true,
});
log.success("Trigger.dev tasks deployed successfully!");
} catch (error: any) {
log.warning("Failed to deploy Trigger.dev tasks:");
note(
`${error.message}\n\nYou can deploy them manually later with:\n1. npx trigger.dev@v4-beta login -a http://localhost:8030\n2. pnpm trigger:deploy`,
"Manual Deployment"
);
}
// Step 15: Final instructions
outro("🎉 Setup Complete!");
note(
[
"Your services are now running:",
"",
"• Core Application: http://localhost:3033",
"• Trigger.dev: http://localhost:8030",
"• PostgreSQL: localhost:5432",
"",
"You can now start developing with Core!",
"",
" When logging in to the Core Application, you can find the login URL in the Docker container logs:",
" docker logs core-app --tail 50",
].join("\n"),
"🚀 Services Running"
);
} catch (error: any) {
outro(`❌ Setup failed: ${error.message}`);
process.exit(1);
}
}

View File

@ -0,0 +1,72 @@
import { intro, outro, note, log, confirm } from '@clack/prompts';
import { isValidCoreRepo } from '../utils/git.js';
import { executeDockerCommandInteractive } from '../utils/docker-interactive.js';
import { printCoreBrainLogo } from '../utils/ascii.js';
import path from 'path';
export async function startCommand() {
// Display the CORE brain logo
printCoreBrainLogo();
intro('🚀 Starting Core Development Environment');
// Step 1: Validate repository
if (!isValidCoreRepo()) {
log.warning('This directory is not a Core repository');
note('The Core repository is required to run the development environment.\nWould you like to clone it in the current directory?', '🔍 Repository Not Found');
const shouldClone = await confirm({
message: 'Clone the Core repository here?',
});
if (!shouldClone) {
outro('❌ Setup cancelled. Please navigate to the Core repository or clone it first.');
process.exit(1);
}
// Clone the repository
try {
await executeDockerCommandInteractive('git clone https://github.com/redplanethq/core.git .', {
cwd: process.cwd(),
message: 'Cloning Core repository...',
showOutput: true
});
log.success('Core repository cloned successfully!');
note('You can now run "core start" to start the development environment.', '✅ Repository Ready');
outro('🎉 Core repository is now available!');
process.exit(0);
} catch (error: any) {
outro(`❌ Failed to clone repository: ${error.message}`);
process.exit(1);
}
}
const rootDir = process.cwd();
const triggerDir = path.join(rootDir, 'trigger');
try {
// Start main services
await executeDockerCommandInteractive('docker compose up -d', {
cwd: rootDir,
message: 'Starting Core services...',
showOutput: true
});
// Start trigger services
await executeDockerCommandInteractive('docker compose up -d', {
cwd: triggerDir,
message: 'Starting Trigger.dev services...',
showOutput: true
});
// Final success message
outro('🎉 Core Development Environment Started!');
note('• Core Application: http://localhost:3033\n• Trigger.dev: http://localhost:8030\n• PostgreSQL: localhost:5432', '🌐 Your services are now running');
log.success('Happy coding!');
} catch (error: any) {
outro(`❌ Failed to start services: ${error.message}`);
process.exit(1);
}
}

View File

@ -0,0 +1,72 @@
import { intro, outro, log, confirm, note } from '@clack/prompts';
import { isValidCoreRepo } from '../utils/git.js';
import { executeDockerCommandInteractive } from '../utils/docker-interactive.js';
import { printCoreBrainLogo } from '../utils/ascii.js';
import path from 'path';
export async function stopCommand() {
// Display the CORE brain logo
printCoreBrainLogo();
intro('🛑 Stopping Core Development Environment');
// Step 1: Validate repository
if (!isValidCoreRepo()) {
log.warning('This directory is not a Core repository');
note('The Core repository is required to stop the development environment.\nWould you like to clone it in the current directory?', '🔍 Repository Not Found');
const shouldClone = await confirm({
message: 'Clone the Core repository here?',
});
if (!shouldClone) {
outro('❌ Setup cancelled. Please navigate to the Core repository or clone it first.');
process.exit(1);
}
// Clone the repository
try {
await executeDockerCommandInteractive('git clone https://github.com/redplanethq/core.git .', {
cwd: process.cwd(),
message: 'Cloning Core repository...',
showOutput: true
});
log.success('Core repository cloned successfully!');
note('You can now run "core stop" to stop the development environment.', '✅ Repository Ready');
outro('🎉 Core repository is now available!');
process.exit(0);
} catch (error: any) {
outro(`❌ Failed to clone repository: ${error.message}`);
process.exit(1);
}
}
const rootDir = process.cwd();
const triggerDir = path.join(rootDir, 'trigger');
try {
// Stop trigger services first
await executeDockerCommandInteractive('docker compose down', {
cwd: triggerDir,
message: 'Stopping Trigger.dev services...',
showOutput: true
});
// Stop main services
await executeDockerCommandInteractive('docker compose down', {
cwd: rootDir,
message: 'Stopping Core services...',
showOutput: true
});
// Final success message
outro('🎉 Core Development Environment Stopped!');
log.success('All services have been stopped.');
log.info('Run "core start" to start services again.');
} catch (error: any) {
outro(`❌ Failed to stop services: ${error.message}`);
process.exit(1);
}
}

View File

@ -0,0 +1,3 @@
#!/usr/bin/env node
import './cli/index.js';

View File

@ -0,0 +1,24 @@
import chalk from "chalk";
export function printCoreBrainLogo(): void {
const brain = `
o o o
o o---o---o o
o---o o o---o---o
o o---o---o---o o
o---o o o---o---o
o o---o---o o
o o o
`;
console.log(chalk.cyan(brain));
console.log(chalk.bold.white(" 🧠 CORE - Contextual Observation & Recall Engine \n"));
}

View File

@ -0,0 +1,108 @@
import { spawn, ChildProcess } from 'child_process';
import { spinner } from '@clack/prompts';
export interface DockerCommandOptions {
cwd: string;
message: string;
showOutput?: boolean;
}
export function executeDockerCommandInteractive(
command: string,
options: DockerCommandOptions
): Promise<void> {
return new Promise((resolve, reject) => {
const s = spinner();
s.start(options.message);
// Split command into parts
const parts = command.split(' ');
const cmd = parts[0];
const args = parts.slice(1);
if (!cmd) {
reject(new Error('Invalid command'));
return;
}
const child: ChildProcess = spawn(cmd, args, {
cwd: options.cwd,
stdio: options.showOutput ? ['ignore', 'pipe', 'pipe'] : 'ignore',
detached: false
});
let output = '';
// Handle stdout
if (child.stdout && options.showOutput) {
child.stdout.on('data', (data: Buffer) => {
const text = data.toString();
output += text;
// Update spinner with latest output line
const lines = text.trim().split('\n');
const lastLine = lines[lines.length - 1];
if (lastLine && lastLine.trim()) {
s.message(`${options.message}\n${lastLine.trim()}`);
}
});
}
// Handle stderr
if (child.stderr && options.showOutput) {
child.stderr.on('data', (data: Buffer) => {
const text = data.toString();
output += text;
// Update spinner with error output
const lines = text.trim().split('\n');
const lastLine = lines[lines.length - 1];
if (lastLine && lastLine.trim()) {
s.message(`${options.message}\n❌ ${lastLine.trim()}`);
}
});
}
// Handle process exit
child.on('exit', (code: number | null) => {
if (code === 0) {
s.stop(`${options.message.replace(/\.\.\.$/, '')} completed`);
resolve();
} else {
s.stop(`${options.message.replace(/\.\.\.$/, '')} failed (exit code: ${code})`);
if (options.showOutput && output) {
console.log('\nOutput:');
console.log(output);
}
reject(new Error(`Command failed with exit code ${code}`));
}
});
// Handle errors
child.on('error', (error: Error) => {
s.stop(`${options.message.replace(/\.\.\.$/, '')} failed`);
reject(error);
});
// Handle Ctrl+C
const handleSigint = () => {
s.stop(`⏹️ ${options.message.replace(/\.\.\.$/, '')} interrupted`);
child.kill('SIGTERM');
// Give the process time to clean up
setTimeout(() => {
if (child.killed === false) {
child.kill('SIGKILL');
}
process.exit(130); // Standard exit code for SIGINT
}, 5000);
};
process.on('SIGINT', handleSigint);
// Clean up event listener when done
child.on('exit', () => {
process.off('SIGINT', handleSigint);
});
});
}

View File

@ -0,0 +1,30 @@
import { execSync } from 'child_process';
export function checkPostgresHealth(): Promise<boolean> {
return new Promise((resolve) => {
try {
const result = execSync('curl -f http://localhost:5432 || nc -z localhost 5432', {
encoding: 'utf8',
timeout: 5000
});
resolve(true);
} catch {
resolve(false);
}
});
}
export function executeDockerCommand(command: string, cwd: string): Promise<string> {
return new Promise((resolve, reject) => {
try {
const result = execSync(command, {
cwd,
encoding: 'utf8',
stdio: 'pipe'
});
resolve(result);
} catch (error: any) {
reject(new Error(`Docker command failed: ${error.message}`));
}
});
}

View File

@ -0,0 +1,23 @@
import fs from 'fs/promises';
export async function checkEnvValue(filePath: string, key: string): Promise<string | null> {
try {
const content = await fs.readFile(filePath, 'utf8');
const lines = content.split('\n');
const line = lines.find(l => l.startsWith(`${key}=`));
if (line) {
const value = line.split('=')[1]?.trim();
return value && value.length > 0 ? value : null;
}
return null;
} catch {
return null;
}
}
export async function hasTriggerConfig(envPath: string): Promise<boolean> {
const projectId = await checkEnvValue(envPath, 'TRIGGER_PROJECT_ID');
const secretKey = await checkEnvValue(envPath, 'TRIGGER_SECRET_KEY');
return !!(projectId && secretKey);
}

View File

@ -0,0 +1,17 @@
import path from 'path';
import { fileExists, copyFile } from './file.js';
export async function setupEnvFile(directory: string, name: string = 'root'): Promise<void> {
const envExamplePath = path.join(directory, '.env.example');
const envPath = path.join(directory, '.env');
if (!(await fileExists(envExamplePath))) {
throw new Error(`❌ .env.example not found in ${name} directory`);
}
if (await fileExists(envPath)) {
return; // .env already exists, skip copying
}
await copyFile(envExamplePath, envPath);
}

View File

@ -0,0 +1,41 @@
import fs from 'fs/promises';
import path from 'path';
export async function copyFile(source: string, destination: string): Promise<void> {
try {
await fs.copyFile(source, destination);
} catch (error: any) {
throw new Error(`Failed to copy file: ${error.message}`);
}
}
export async function fileExists(filePath: string): Promise<boolean> {
try {
await fs.access(filePath);
return true;
} catch {
return false;
}
}
export async function updateEnvFile(filePath: string, key: string, value: string): Promise<void> {
try {
let content = '';
if (await fileExists(filePath)) {
content = await fs.readFile(filePath, 'utf8');
}
const lines = content.split('\n');
const keyIndex = lines.findIndex(line => line.startsWith(`${key}=`));
if (keyIndex !== -1) {
lines[keyIndex] = `${key}=${value}`;
} else {
lines.push(`${key}=${value}`);
}
await fs.writeFile(filePath, lines.join('\n'));
} catch (error: any) {
throw new Error(`Failed to update .env file: ${error.message}`);
}
}

View File

@ -0,0 +1,22 @@
import { execSync } from "child_process";
export function getGitRemoteUrl(): string | null {
try {
const url = execSync("git config --get remote.origin.url", { encoding: "utf8" }).trim();
return url;
} catch {
return null;
}
}
export function isValidCoreRepo(): boolean {
const remoteUrl = getGitRemoteUrl();
if (!remoteUrl) return false;
return (
remoteUrl.includes("github.com/redplanethq/core") ||
remoteUrl.includes("github.com:redplanethq/core") ||
remoteUrl.includes("github.com/tegonhq/echo") ||
remoteUrl.includes("github.com:tegonhq/echo")
);
}

View File

@ -0,0 +1,22 @@
import { spinner } from '@clack/prompts';
export function createSpinner(message: string) {
return spinner();
}
export async function withSpinner<T>(
message: string,
task: () => Promise<T>
): Promise<T> {
const s = spinner();
s.start(message);
try {
const result = await task();
s.stop(message);
return result;
} catch (error) {
s.stop(`${message} - Failed`);
throw error;
}
}

View File

@ -79,6 +79,7 @@ RUN_REPLICATION_CLICKHOUSE_URL=http://default:password@clickhouse:8123
# - When deploying to production, you will have to change these, especially the password and URL
# - See the docs for more information: https://trigger.dev/docs/self-hosting/docker#registry-setup
DOCKER_REGISTRY_URL=localhost:5000
DOCKER_REGISTRY_NAMESPACE=
DOCKER_REGISTRY_USERNAME=registry-user
DOCKER_REGISTRY_PASSWORD=very-secure-indeed

View File

@ -224,6 +224,7 @@ services:
DOCKER_HOST: tcp://docker-proxy:2375
DOCKER_RUNNER_NETWORKS: webapp,supervisor
DOCKER_REGISTRY_URL: ${DOCKER_REGISTRY_URL:-localhost:5000}
DEPLOY_REGISTRY_NAMESPACE: ${DOCKER_REGISTRY_NAMESPACE:-redplanethq}
DOCKER_REGISTRY_USERNAME: ${DOCKER_REGISTRY_USERNAME:-}
DOCKER_REGISTRY_PASSWORD: ${DOCKER_REGISTRY_PASSWORD:-}
DOCKER_AUTOREMOVE_EXITED_CONTAINERS: 0

View File

@ -47,12 +47,13 @@ services:
MAGIC_LINK_SECRET: ${MAGIC_LINK_SECRET}
ENCRYPTION_KEY: ${ENCRYPTION_KEY}
MANAGED_WORKER_SECRET: ${MANAGED_WORKER_SECRET}
REDIS_HOST: trigger-redis
REDIS_HOST: host.docker.internal
REDIS_PORT: 6379
REDIS_TLS_DISABLED: true
APP_LOG_LEVEL: info
DEV_OTEL_EXPORTER_OTLP_ENDPOINT: ${DEV_OTEL_EXPORTER_OTLP_ENDPOINT:-http://localhost:8030/otel}
DEPLOY_REGISTRY_HOST: ${DOCKER_REGISTRY_URL:-localhost:5000}
DEPLOY_REGISTRY_NAMESPACE: ${DOCKER_REGISTRY_NAMESPACE:-trigger}
OBJECT_STORE_BASE_URL: ${OBJECT_STORE_BASE_URL:-http://minio:9000}
OBJECT_STORE_ACCESS_KEY_ID: ${OBJECT_STORE_ACCESS_KEY_ID}
OBJECT_STORE_SECRET_ACCESS_KEY: ${OBJECT_STORE_SECRET_ACCESS_KEY}