veryfront 0.1.21 → 0.1.23

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (68) hide show
  1. package/bin/veryfront.js +24 -11
  2. package/esm/cli/commands/task/command-help.d.ts +3 -0
  3. package/esm/cli/commands/task/command-help.d.ts.map +1 -0
  4. package/esm/cli/commands/task/command-help.js +20 -0
  5. package/esm/cli/commands/task/command.d.ts +5 -0
  6. package/esm/cli/commands/task/command.d.ts.map +1 -0
  7. package/esm/cli/commands/task/command.js +79 -0
  8. package/esm/cli/commands/task/handler.d.ts +24 -0
  9. package/esm/cli/commands/task/handler.d.ts.map +1 -0
  10. package/esm/cli/commands/task/handler.js +17 -0
  11. package/esm/cli/help/command-definitions.d.ts.map +1 -1
  12. package/esm/cli/help/command-definitions.js +2 -0
  13. package/esm/cli/router.d.ts.map +1 -1
  14. package/esm/cli/router.js +2 -0
  15. package/esm/deno.d.ts +1 -0
  16. package/esm/deno.js +2 -1
  17. package/esm/src/discovery/discovery-engine.d.ts.map +1 -1
  18. package/esm/src/discovery/discovery-engine.js +6 -1
  19. package/esm/src/discovery/handlers/index.d.ts +1 -0
  20. package/esm/src/discovery/handlers/index.d.ts.map +1 -1
  21. package/esm/src/discovery/handlers/index.js +1 -0
  22. package/esm/src/discovery/handlers/task-handler.d.ts +7 -0
  23. package/esm/src/discovery/handlers/task-handler.d.ts.map +1 -0
  24. package/esm/src/discovery/handlers/task-handler.js +19 -0
  25. package/esm/src/discovery/types.d.ts +3 -0
  26. package/esm/src/discovery/types.d.ts.map +1 -1
  27. package/esm/src/platform/adapters/fs/veryfront/adapter.d.ts +0 -2
  28. package/esm/src/platform/adapters/fs/veryfront/adapter.d.ts.map +1 -1
  29. package/esm/src/platform/adapters/fs/veryfront/adapter.js +3 -7
  30. package/esm/src/studio/bridge-template.d.ts.map +1 -1
  31. package/esm/src/studio/bridge-template.js +3 -2
  32. package/esm/src/task/discovery.d.ts +83 -0
  33. package/esm/src/task/discovery.d.ts.map +1 -0
  34. package/esm/src/task/discovery.js +149 -0
  35. package/esm/src/task/runner.d.ts +34 -0
  36. package/esm/src/task/runner.d.ts.map +1 -0
  37. package/esm/src/task/runner.js +45 -0
  38. package/esm/src/task/types.d.ts +34 -0
  39. package/esm/src/task/types.d.ts.map +1 -0
  40. package/esm/src/task/types.js +16 -0
  41. package/esm/src/transforms/mdx/esm-module-loader/jsx/runtime-loader.js +1 -1
  42. package/esm/src/workflow/claude-code/tool.d.ts +5 -5
  43. package/package.json +2 -2
  44. package/src/cli/commands/task/command-help.ts +22 -0
  45. package/src/cli/commands/task/command.ts +98 -0
  46. package/src/cli/commands/task/handler.ts +23 -0
  47. package/src/cli/help/command-definitions.ts +2 -0
  48. package/src/cli/router.ts +2 -0
  49. package/src/deno.js +2 -1
  50. package/src/src/discovery/discovery-engine.ts +7 -0
  51. package/src/src/discovery/handlers/index.ts +1 -0
  52. package/src/src/discovery/handlers/task-handler.ts +23 -0
  53. package/src/src/discovery/types.ts +3 -0
  54. package/src/src/platform/adapters/fs/veryfront/adapter.ts +3 -7
  55. package/src/src/studio/bridge-template.ts +3 -2
  56. package/src/src/task/discovery.ts +228 -0
  57. package/src/src/task/runner.ts +94 -0
  58. package/src/src/task/types.ts +40 -0
  59. package/src/src/transforms/mdx/esm-module-loader/jsx/runtime-loader.ts +1 -1
  60. package/esm/deps/esm.sh/react@19.1.1/jsx-dev-runtime.d.ts +0 -2
  61. package/esm/deps/esm.sh/react@19.1.1/jsx-dev-runtime.d.ts.map +0 -1
  62. package/esm/deps/esm.sh/react@19.1.1/jsx-dev-runtime.js +0 -3
  63. package/src/deps/esm.sh/@types/react@19.1.17/global.d.ts +0 -165
  64. package/src/deps/esm.sh/@types/react@19.1.17/index.d.ts +0 -4267
  65. package/src/deps/esm.sh/@types/react@19.1.17/jsx-dev-runtime.d.ts +0 -45
  66. package/src/deps/esm.sh/csstype@3.1.3/index.d.ts +0 -21297
  67. package/src/deps/esm.sh/react@19.1.1/jsx-dev-runtime.d.ts +0 -45
  68. package/src/deps/esm.sh/react@19.1.1/jsx-dev-runtime.js +0 -3
@@ -0,0 +1,149 @@
1
+ /**
2
+ * Task Discovery
3
+ *
4
+ * Discovers task definitions from user's project `tasks/` directory.
5
+ * Follows the same patterns as workflow-discovery.ts.
6
+ *
7
+ * Scans:
8
+ * - tasks/*.ts - task definition files
9
+ * - tasks/**\/*.ts - nested task files
10
+ *
11
+ * Task files should export a task definition:
12
+ * ```typescript
13
+ * export default {
14
+ * name: "Sync external data",
15
+ * run: async (ctx) => {
16
+ * console.log("Syncing data...")
17
+ * return { synced: 42 }
18
+ * }
19
+ * }
20
+ * ```
21
+ */
22
+ import { join } from "../../deps/jsr.io/@std/path/1.1.4/mod.js";
23
+ import { logger as baseLogger } from "../utils/index.js";
24
+ import { collectFiles } from "../utils/file-discovery.js";
25
+ import { loadHandlerModule } from "../routing/api/module-loader/loader.js";
26
+ import { isTaskDefinition } from "./types.js";
27
+ const logger = baseLogger.component("task-discovery");
28
+ /**
29
+ * Derive task ID from file path (e.g., "tasks/sync-data.ts" → "sync-data")
30
+ */
31
+ export function deriveTaskId(filePath, tasksDir) {
32
+ // Remove the tasks dir prefix and extension
33
+ let relative = filePath;
34
+ const dirPrefix = tasksDir.endsWith("/") ? tasksDir : `${tasksDir}/`;
35
+ if (relative.startsWith(dirPrefix)) {
36
+ relative = relative.slice(dirPrefix.length);
37
+ }
38
+ // Remove extension
39
+ return relative.replace(/\.(ts|tsx|js|jsx)$/, "");
40
+ }
41
+ /**
42
+ * Discover all tasks in a project
43
+ */
44
+ export async function discoverTasks(options) {
45
+ const { projectDir, adapter, config, tasksDir = "tasks", debug = false, } = options;
46
+ const tasks = [];
47
+ const errors = [];
48
+ const fsType = config?.fs?.type ?? "local";
49
+ const useRelativePaths = fsType === "github" || fsType === "veryfront-api";
50
+ const baseDir = useRelativePaths ? tasksDir : join(projectDir, tasksDir);
51
+ if (debug) {
52
+ logger.info(`Scanning ${baseDir} for tasks`);
53
+ }
54
+ try {
55
+ const dirExists = await adapter.fs.exists(baseDir);
56
+ if (!dirExists) {
57
+ if (debug) {
58
+ logger.info(`No tasks directory found at ${baseDir}`);
59
+ }
60
+ return { tasks, errors };
61
+ }
62
+ const files = await collectFiles({
63
+ baseDir,
64
+ extensions: [".ts", ".tsx", ".js", ".jsx"],
65
+ recursive: true,
66
+ ignorePatterns: ["node_modules", ".git", "__tests__", "*.test.*", "*.spec.*"],
67
+ adapter,
68
+ });
69
+ if (debug) {
70
+ logger.info(`Found ${files.length} potential task files`);
71
+ }
72
+ for (const file of files) {
73
+ try {
74
+ const module = await loadHandlerModule({
75
+ projectDir,
76
+ modulePath: file.path,
77
+ adapter,
78
+ config,
79
+ });
80
+ if (!module)
81
+ continue;
82
+ // Prefer default export (aligned with discovery-engine behaviour)
83
+ const defaultExport = module.default;
84
+ if (isTaskDefinition(defaultExport)) {
85
+ const id = deriveTaskId(file.path, baseDir);
86
+ tasks.push({
87
+ id,
88
+ name: defaultExport.name || id,
89
+ filePath: file.path,
90
+ exportName: "default",
91
+ definition: defaultExport,
92
+ });
93
+ if (debug) {
94
+ logger.info(`Found task "${id}" in ${file.path} (export: default)`);
95
+ }
96
+ }
97
+ else {
98
+ // Fallback: check named exports
99
+ for (const [exportName, value] of Object.entries(module)) {
100
+ if (exportName === "default")
101
+ continue;
102
+ if (isTaskDefinition(value)) {
103
+ const id = deriveTaskId(file.path, baseDir);
104
+ tasks.push({
105
+ id,
106
+ name: value.name || id,
107
+ filePath: file.path,
108
+ exportName,
109
+ definition: value,
110
+ });
111
+ if (debug) {
112
+ logger.info(`Found task "${id}" in ${file.path} (export: ${exportName})`);
113
+ }
114
+ break; // Only take the first valid named export per file
115
+ }
116
+ }
117
+ }
118
+ }
119
+ catch (error) {
120
+ const errorMsg = error instanceof Error ? error.message : String(error);
121
+ errors.push({ filePath: file.path, error: errorMsg });
122
+ if (debug) {
123
+ logger.warn(`Failed to load ${file.path}: ${errorMsg}`);
124
+ }
125
+ }
126
+ }
127
+ if (debug) {
128
+ logger.info(`Discovered ${tasks.length} tasks`);
129
+ }
130
+ return { tasks, errors };
131
+ }
132
+ catch (error) {
133
+ const errorMsg = error instanceof Error ? error.message : String(error);
134
+ logger.error(`Task discovery failed: ${errorMsg}`);
135
+ errors.push({ filePath: baseDir, error: errorMsg });
136
+ return { tasks, errors };
137
+ }
138
+ }
139
+ /**
140
+ * Find a specific task by ID
141
+ *
142
+ * TODO: Optimise by short-circuiting discovery once the target task is found
143
+ * instead of discovering all tasks first. This is consistent with the workflow
144
+ * pattern but could be improved for large projects with many task files.
145
+ */
146
+ export async function findTaskById(taskId, options) {
147
+ const { tasks } = await discoverTasks(options);
148
+ return tasks.find((t) => t.id === taskId) ?? null;
149
+ }
@@ -0,0 +1,34 @@
1
+ import type { DiscoveredTask } from "./discovery.js";
2
+ /**
3
+ * Options for running a task
4
+ */
5
+ export interface RunTaskOptions {
6
+ /** The discovered task to run */
7
+ task: DiscoveredTask;
8
+ /** Additional config to pass to the task */
9
+ config?: Record<string, unknown>;
10
+ /** Project ID (for cloud context) */
11
+ projectId?: string;
12
+ /** If set, only these env var names are passed to the task. */
13
+ envAllowlist?: string[];
14
+ /** Enable debug logging */
15
+ debug?: boolean;
16
+ }
17
+ /**
18
+ * Result of running a task
19
+ */
20
+ export interface TaskRunResult {
21
+ /** Whether the task completed successfully */
22
+ success: boolean;
23
+ /** Return value from the task's run() */
24
+ result?: unknown;
25
+ /** Error if the task failed */
26
+ error?: string;
27
+ /** Execution duration in milliseconds */
28
+ durationMs: number;
29
+ }
30
+ /**
31
+ * Run a task with the given options
32
+ */
33
+ export declare function runTask(options: RunTaskOptions): Promise<TaskRunResult>;
34
+ //# sourceMappingURL=runner.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"runner.d.ts","sourceRoot":"","sources":["../../../src/src/task/runner.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAKrD;;GAEG;AACH,MAAM,WAAW,cAAc;IAC7B,iCAAiC;IACjC,IAAI,EAAE,cAAc,CAAC;IAErB,4CAA4C;IAC5C,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAEjC,qCAAqC;IACrC,SAAS,CAAC,EAAE,MAAM,CAAC;IAEnB,+DAA+D;IAC/D,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC;IAExB,2BAA2B;IAC3B,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AAED;;GAEG;AACH,MAAM,WAAW,aAAa;IAC5B,8CAA8C;IAC9C,OAAO,EAAE,OAAO,CAAC;IAEjB,yCAAyC;IACzC,MAAM,CAAC,EAAE,OAAO,CAAC;IAEjB,+BAA+B;IAC/B,KAAK,CAAC,EAAE,MAAM,CAAC;IAEf,yCAAyC;IACzC,UAAU,EAAE,MAAM,CAAC;CACpB;AAED;;GAEG;AACH,wBAAsB,OAAO,CAAC,OAAO,EAAE,cAAc,GAAG,OAAO,CAAC,aAAa,CAAC,CAsC7E"}
@@ -0,0 +1,45 @@
1
+ /**
2
+ * Task Runner
3
+ *
4
+ * Executes a discovered task by calling its run() function
5
+ * with the appropriate context.
6
+ */
7
+ import * as dntShim from "../../_dnt.shims.js";
8
+ import { logger as baseLogger } from "../utils/index.js";
9
+ const logger = baseLogger.component("task-runner");
10
+ /**
11
+ * Run a task with the given options
12
+ */
13
+ export async function runTask(options) {
14
+ const { task, config = {}, projectId, envAllowlist, debug = false } = options;
15
+ const start = Date.now();
16
+ if (debug) {
17
+ logger.info(`Running task "${task.id}" (${task.name})`);
18
+ }
19
+ const env = { ...dntShim.Deno.env.toObject() };
20
+ if (envAllowlist) {
21
+ for (const k of Object.keys(env)) {
22
+ if (!envAllowlist.includes(k))
23
+ delete env[k];
24
+ }
25
+ }
26
+ const ctx = {
27
+ env,
28
+ config,
29
+ projectId,
30
+ };
31
+ try {
32
+ const result = await task.definition.run(ctx);
33
+ const durationMs = Date.now() - start;
34
+ if (debug) {
35
+ logger.info(`Task "${task.id}" completed in ${durationMs}ms`);
36
+ }
37
+ return { success: true, result, durationMs };
38
+ }
39
+ catch (error) {
40
+ const durationMs = Date.now() - start;
41
+ const errorMsg = error instanceof Error ? error.message : String(error);
42
+ logger.error(`Task "${task.id}" failed: ${errorMsg}`);
43
+ return { success: false, error: errorMsg, durationMs };
44
+ }
45
+ }
@@ -0,0 +1,34 @@
1
+ /**
2
+ * Task Types
3
+ *
4
+ * Type definitions for the task execution system.
5
+ * Tasks are user-defined functions in `tasks/` that can run
6
+ * locally via `veryfront task <name>` or in the cloud as Jobs/CronJobs.
7
+ */
8
+ /**
9
+ * Context passed to task run() function
10
+ */
11
+ export interface TaskContext {
12
+ /** Environment variables */
13
+ env: Record<string, string>;
14
+ /** Job config (when run as a cloud job) */
15
+ config: Record<string, unknown>;
16
+ /** Project ID (when run as a cloud job) */
17
+ projectId?: string;
18
+ }
19
+ /**
20
+ * Task definition exported from a tasks/ file
21
+ */
22
+ export interface TaskDefinition {
23
+ /** Human-readable name */
24
+ name?: string;
25
+ /** Task description */
26
+ description?: string;
27
+ /** The function to execute */
28
+ run: (ctx: TaskContext) => Promise<unknown> | unknown;
29
+ }
30
+ /**
31
+ * Type guard: checks if a value looks like a TaskDefinition
32
+ */
33
+ export declare function isTaskDefinition(value: unknown): value is TaskDefinition;
34
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/src/task/types.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH;;GAEG;AACH,MAAM,WAAW,WAAW;IAC1B,4BAA4B;IAC5B,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC5B,2CAA2C;IAC3C,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAChC,2CAA2C;IAC3C,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED;;GAEG;AACH,MAAM,WAAW,cAAc;IAC7B,0BAA0B;IAC1B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,uBAAuB;IACvB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,8BAA8B;IAC9B,GAAG,EAAE,CAAC,GAAG,EAAE,WAAW,KAAK,OAAO,CAAC,OAAO,CAAC,GAAG,OAAO,CAAC;CACvD;AAED;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,cAAc,CAIxE"}
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Task Types
3
+ *
4
+ * Type definitions for the task execution system.
5
+ * Tasks are user-defined functions in `tasks/` that can run
6
+ * locally via `veryfront task <name>` or in the cloud as Jobs/CronJobs.
7
+ */
8
+ /**
9
+ * Type guard: checks if a value looks like a TaskDefinition
10
+ */
11
+ export function isTaskDefinition(value) {
12
+ if (!value || typeof value !== "object")
13
+ return false;
14
+ const obj = value;
15
+ return typeof obj.run === "function";
16
+ }
@@ -1,6 +1,6 @@
1
1
  export async function loadJSXRuntime() {
2
2
  // deno-lint-ignore no-explicit-any
3
- const runtime = (await import("../../../../../deps/esm.sh/react@19.1.1/jsx-dev-runtime.js"));
3
+ const runtime = (await import("react/jsx-dev-runtime"));
4
4
  return {
5
5
  Fragment: runtime.Fragment,
6
6
  jsx: runtime.jsx ?? runtime.jsxDEV,
@@ -24,8 +24,8 @@ declare const claudeCodeInputSchema: z.ZodObject<{
24
24
  system: z.ZodOptional<z.ZodString>;
25
25
  }, "strip", z.ZodTypeAny, {
26
26
  mode: "code" | "custom" | "full" | "analysis";
27
- maxTurns: number;
28
27
  task: string;
28
+ maxTurns: number;
29
29
  context?: Record<string, unknown> | undefined;
30
30
  files?: string[] | undefined;
31
31
  system?: string | undefined;
@@ -77,8 +77,8 @@ export declare function createClaudeCodeTool(options?: {
77
77
  /** Code review tool (analysis mode, read-only) */
78
78
  export declare const codeReviewTool: Tool<{
79
79
  mode: "code" | "custom" | "full" | "analysis";
80
- maxTurns: number;
81
80
  task: string;
81
+ maxTurns: number;
82
82
  context?: Record<string, unknown> | undefined;
83
83
  files?: string[] | undefined;
84
84
  system?: string | undefined;
@@ -86,8 +86,8 @@ export declare const codeReviewTool: Tool<{
86
86
  /** Bug fix tool (code mode) */
87
87
  export declare const bugFixTool: Tool<{
88
88
  mode: "code" | "custom" | "full" | "analysis";
89
- maxTurns: number;
90
89
  task: string;
90
+ maxTurns: number;
91
91
  context?: Record<string, unknown> | undefined;
92
92
  files?: string[] | undefined;
93
93
  system?: string | undefined;
@@ -95,8 +95,8 @@ export declare const bugFixTool: Tool<{
95
95
  /** Refactoring tool (code mode) */
96
96
  export declare const refactorTool: Tool<{
97
97
  mode: "code" | "custom" | "full" | "analysis";
98
- maxTurns: number;
99
98
  task: string;
99
+ maxTurns: number;
100
100
  context?: Record<string, unknown> | undefined;
101
101
  files?: string[] | undefined;
102
102
  system?: string | undefined;
@@ -104,8 +104,8 @@ export declare const refactorTool: Tool<{
104
104
  /** Documentation tool (code mode) */
105
105
  export declare const docsTool: Tool<{
106
106
  mode: "code" | "custom" | "full" | "analysis";
107
- maxTurns: number;
108
107
  task: string;
108
+ maxTurns: number;
109
109
  context?: Record<string, unknown> | undefined;
110
110
  files?: string[] | undefined;
111
111
  system?: string | undefined;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "veryfront",
3
- "version": "0.1.21",
3
+ "version": "0.1.23",
4
4
  "description": "The simplest way to build AI-powered apps",
5
5
  "keywords": [
6
6
  "react",
@@ -19,7 +19,7 @@
19
19
  "type": "git",
20
20
  "url": "git+https://github.com/veryfront/veryfront.git"
21
21
  },
22
- "license": "MIT",
22
+ "license": "Apache-2.0",
23
23
  "bugs": {
24
24
  "url": "https://github.com/veryfront/veryfront/issues"
25
25
  },
@@ -0,0 +1,22 @@
1
+ import type { CommandHelp } from "../../help/types.js";
2
+
3
+ export const taskHelp: CommandHelp = {
4
+ name: "task",
5
+ description: "Run a task from the tasks/ directory",
6
+ usage: "veryfront task <name> [options]",
7
+ options: [
8
+ {
9
+ flag: "--config <json>",
10
+ description: "JSON config to pass to the task's ctx.config",
11
+ },
12
+ {
13
+ flag: "--debug",
14
+ description: "Enable debug logging",
15
+ },
16
+ ],
17
+ examples: [
18
+ "veryfront task sync-data",
19
+ 'veryfront task send-report --config \'{"to": "team@example.com"}\'',
20
+ "veryfront task cleanup --debug",
21
+ ],
22
+ };
@@ -0,0 +1,98 @@
1
+ /**
2
+ * Task command - Discover and run a task from the tasks/ directory
3
+ *
4
+ * Finds the specified task file, imports it, and calls its run() function
5
+ * with a local execution context.
6
+ */
7
+ import * as dntShim from "../../../_dnt.shims.js";
8
+
9
+
10
+ import { cliLogger } from "../../utils/index.js";
11
+ import { exitProcess } from "../../utils/index.js";
12
+ import type { TaskArgs } from "./handler.js";
13
+
14
+ export interface TaskOptions extends TaskArgs {}
15
+
16
+ export async function taskCommand(options: TaskOptions): Promise<void> {
17
+ const { getAdapter } = await import(
18
+ "../../../src/platform/adapters/detect.js"
19
+ );
20
+ const { discoverTasks } = await import(
21
+ "../../../src/task/discovery.js"
22
+ );
23
+ const { runTask } = await import(
24
+ "../../../src/task/runner.js"
25
+ );
26
+
27
+ const taskName = options.name;
28
+ if (!taskName) {
29
+ cliLogger.error("Task name is required. Usage: veryfront task <name>");
30
+ exitProcess(1);
31
+ return;
32
+ }
33
+
34
+ const projectDir = dntShim.Deno.cwd();
35
+ const adapter = await getAdapter();
36
+
37
+ cliLogger.info(`Discovering tasks in ${projectDir}/tasks/...`);
38
+
39
+ const { tasks, errors } = await discoverTasks({
40
+ projectDir,
41
+ adapter,
42
+ debug: options.debug,
43
+ });
44
+
45
+ if (errors.length > 0 && options.debug) {
46
+ for (const err of errors) {
47
+ cliLogger.warn(` Warning: ${err.filePath}: ${err.error}`);
48
+ }
49
+ }
50
+
51
+ const task = tasks.find((t) => t.id === taskName);
52
+ if (!task) {
53
+ cliLogger.error(`Task "${taskName}" not found.`);
54
+ if (tasks.length > 0) {
55
+ cliLogger.info("Available tasks:");
56
+ for (const t of tasks) {
57
+ cliLogger.info(` - ${t.id}${t.name !== t.id ? ` (${t.name})` : ""}`);
58
+ }
59
+ } else {
60
+ cliLogger.info("No tasks found. Create a task file in tasks/ directory:");
61
+ cliLogger.info(" tasks/my-task.ts");
62
+ }
63
+ exitProcess(1);
64
+ return;
65
+ }
66
+
67
+ // Parse config JSON if provided
68
+ let config: Record<string, unknown> = {};
69
+ if (options.config) {
70
+ try {
71
+ config = JSON.parse(options.config);
72
+ } catch {
73
+ cliLogger.error("Invalid --config JSON");
74
+ exitProcess(1);
75
+ return;
76
+ }
77
+ }
78
+
79
+ cliLogger.info(`Running task: ${task.name} (${task.id})`);
80
+ cliLogger.info("");
81
+
82
+ const result = await runTask({
83
+ task,
84
+ config,
85
+ debug: options.debug,
86
+ });
87
+
88
+ cliLogger.info("");
89
+ if (result.success) {
90
+ cliLogger.info(`Task completed in ${result.durationMs}ms`);
91
+ if (result.result !== undefined) {
92
+ cliLogger.info(`Result: ${JSON.stringify(result.result, null, 2)}`);
93
+ }
94
+ } else {
95
+ cliLogger.error(`Task failed after ${result.durationMs}ms: ${result.error}`);
96
+ exitProcess(1);
97
+ }
98
+ }
@@ -0,0 +1,23 @@
1
+ import { z } from "zod";
2
+ import { createArgParser, parseArgsOrThrow } from "../../shared/args.js";
3
+ import type { ParsedArgs } from "../../shared/types.js";
4
+
5
+ const TaskArgsSchema = z.object({
6
+ name: z.string(),
7
+ config: z.string().optional(),
8
+ debug: z.boolean().default(false),
9
+ });
10
+
11
+ export type TaskArgs = z.infer<typeof TaskArgsSchema>;
12
+
13
+ export const parseTaskArgs = createArgParser(TaskArgsSchema, {
14
+ name: { keys: ["name"], type: "string", positional: 0 },
15
+ config: { keys: ["config"], type: "string" },
16
+ debug: { keys: ["debug"], type: "boolean" },
17
+ });
18
+
19
+ export async function handleTaskCommand(args: ParsedArgs): Promise<void> {
20
+ const opts = parseArgsOrThrow(parseTaskArgs, "task", args);
21
+ const { taskCommand } = await import("./command.js");
22
+ await taskCommand(opts);
23
+ }
@@ -32,6 +32,7 @@ import { demoHelp } from "../commands/demo/command-help.js";
32
32
  import { mcpHelp } from "../commands/mcp/command-help.js";
33
33
  import { issuesHelp } from "../commands/issues/command-help.js";
34
34
  import { startHelp } from "../commands/start/command-help.js";
35
+ import { taskHelp } from "../commands/task/command-help.js";
35
36
 
36
37
  /**
37
38
  * Central registry of all command help definitions.
@@ -63,4 +64,5 @@ export const COMMANDS: CommandRegistry = {
63
64
  mcp: mcpHelp,
64
65
  issues: issuesHelp,
65
66
  start: startHelp,
67
+ task: taskHelp,
66
68
  };
package/src/cli/router.ts CHANGED
@@ -27,6 +27,7 @@ import { handleServeCommand } from "./commands/serve/handler.js";
27
27
  import { handleStartCommand } from "./commands/start/handler.js";
28
28
  import { handleStudioCommand } from "./commands/studio/handler.js";
29
29
  import { handleUpCommand } from "./commands/up/index.js";
30
+ import { handleTaskCommand } from "./commands/task/handler.js";
30
31
  import { handleWorkerCommand } from "./commands/worker/handler.js";
31
32
  import { login, logout, whoami } from "./auth/index.js";
32
33
  import { parseLoginMethod } from "./auth/utils.js";
@@ -73,6 +74,7 @@ const commands: Record<string, (args: ParsedArgs) => Promise<void>> = {
73
74
  "mcp": handleMCPCommand,
74
75
  "issues": handleIssuesCommand,
75
76
  "start": handleStartCommand,
77
+ "task": handleTaskCommand,
76
78
  "worker": handleWorkerCommand,
77
79
  };
78
80
 
package/src/deno.js CHANGED
@@ -1,6 +1,7 @@
1
1
  export default {
2
2
  "name": "veryfront",
3
- "version": "0.1.21",
3
+ "version": "0.1.23",
4
+ "license": "Apache-2.0",
4
5
  "nodeModulesDir": "auto",
5
6
  "exclude": [
6
7
  "npm/",
@@ -20,6 +20,7 @@ import {
20
20
  agentHandler,
21
21
  promptHandler,
22
22
  resourceHandler,
23
+ taskHandler,
23
24
  toolHandler,
24
25
  workflowHandler,
25
26
  } from "./handlers/index.js";
@@ -89,6 +90,7 @@ export async function discoverAll(config: DiscoveryConfig): Promise<DiscoveryRes
89
90
  resources: new Map(),
90
91
  prompts: new Map(),
91
92
  workflows: new Map(),
93
+ tasks: new Map(),
92
94
  errors: [],
93
95
  };
94
96
 
@@ -117,5 +119,10 @@ export async function discoverAll(config: DiscoveryConfig): Promise<DiscoveryRes
117
119
  await discoverItems(`${baseDir}/${dir}`, result, context, workflowHandler, config.verbose);
118
120
  }
119
121
 
122
+ // Discover tasks
123
+ for (const dir of config.taskDirs ?? ["tasks"]) {
124
+ await discoverItems(`${baseDir}/${dir}`, result, context, taskHandler, config.verbose);
125
+ }
126
+
120
127
  return result;
121
128
  }
@@ -9,3 +9,4 @@ export { agentHandler } from "./agent-handler.js";
9
9
  export { resourceHandler } from "./resource-handler.js";
10
10
  export { promptHandler } from "./prompt-handler.js";
11
11
  export { workflowHandler } from "./workflow-handler.js";
12
+ export { taskHandler } from "./task-handler.js";
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Task Discovery Handler
3
+ */
4
+
5
+ import type { TaskDefinition } from "../../task/types.js";
6
+ import { isTaskDefinition } from "../../task/types.js";
7
+ import type { DiscoveryHandler, DiscoveryResult } from "../types.js";
8
+
9
+ export const taskHandler: DiscoveryHandler<TaskDefinition> = {
10
+ typeName: "task",
11
+ validate: (item): item is TaskDefinition => isTaskDefinition(item),
12
+ getId: (_task, file, dir) => {
13
+ // Derive ID from file path relative to tasks dir
14
+ let relative = file;
15
+ const prefix = dir.endsWith("/") ? dir : `${dir}/`;
16
+ if (relative.startsWith(prefix)) {
17
+ relative = relative.slice(prefix.length);
18
+ }
19
+ return relative.replace(/\.(ts|tsx|js|jsx)$/, "");
20
+ },
21
+ register: (_id, task) => task,
22
+ getResultMap: (result: DiscoveryResult) => result.tasks,
23
+ };
@@ -9,6 +9,7 @@ import type { Agent } from "../agent/index.js";
9
9
  import type { Resource } from "../resource/index.js";
10
10
  import type { Prompt } from "../prompt/index.js";
11
11
  import type { Workflow } from "../workflow/index.js";
12
+ import type { TaskDefinition } from "../task/types.js";
12
13
  import type { Platform } from "../platform/core-platform.js";
13
14
  import type { FileSystemAdapter } from "../platform/adapters/base.js";
14
15
 
@@ -35,6 +36,7 @@ export interface DiscoveryConfig {
35
36
  resourceDirs?: string[];
36
37
  promptDirs?: string[];
37
38
  workflowDirs?: string[];
39
+ taskDirs?: string[];
38
40
  verbose?: boolean;
39
41
  fsAdapter?: FileSystemAdapter;
40
42
  }
@@ -48,6 +50,7 @@ export interface DiscoveryResult {
48
50
  resources: Map<string, Resource>;
49
51
  prompts: Map<string, Prompt>;
50
52
  workflows: Map<string, Workflow>;
53
+ tasks: Map<string, TaskDefinition>;
51
54
  errors: Array<{ file: string; error: Error }>;
52
55
  }
53
56
 
@@ -42,8 +42,6 @@ export class VeryfrontFSAdapter implements FSAdapter {
42
42
 
43
43
  /** Resolves when file list initialization is complete (for coordinating reads) */
44
44
  private fileListReadyResolve: (() => void) | null = null;
45
- /** Rejects when file list initialization fails */
46
- private fileListReadyReject: ((error: Error) => void) | null = null;
47
45
 
48
46
  private projectData?: Project;
49
47
  private apiBaseUrl: string;
@@ -233,9 +231,8 @@ export class VeryfrontFSAdapter implements FSAdapter {
233
231
  return;
234
232
  }
235
233
 
236
- const fileListReadyPromise = new Promise<void>((resolve, reject) => {
234
+ const fileListReadyPromise = new Promise<void>((resolve) => {
237
235
  this.fileListReadyResolve = resolve;
238
- this.fileListReadyReject = reject;
239
236
  });
240
237
  this.readOps.setFileListReadyPromise(fileListReadyPromise);
241
238
 
@@ -303,7 +300,6 @@ export class VeryfrontFSAdapter implements FSAdapter {
303
300
 
304
301
  this.fileListReadyResolve?.();
305
302
  this.fileListReadyResolve = null;
306
- this.fileListReadyReject = null;
307
303
 
308
304
  logger.debug("Fetched files during initialization", {
309
305
  cacheKey,
@@ -357,9 +353,9 @@ export class VeryfrontFSAdapter implements FSAdapter {
357
353
  this.wsManager.connect(projectId);
358
354
  }
359
355
  } catch (error) {
360
- this.fileListReadyReject?.(error instanceof Error ? error : new Error(String(error)));
356
+ // Resolve (not reject) to avoid an unhandled-rejection crash in Deno when no lookup() is awaiting.
357
+ this.fileListReadyResolve?.();
361
358
  this.fileListReadyResolve = null;
362
- this.fileListReadyReject = null;
363
359
  throw error;
364
360
  }
365
361
  }