cli4ai 1.1.5 → 1.2.1

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 (113) hide show
  1. package/README.md +39 -0
  2. package/dist/bin.d.ts +6 -0
  3. package/dist/bin.js +105 -0
  4. package/dist/cli.d.ts +5 -0
  5. package/dist/cli.js +335 -0
  6. package/dist/commands/add.d.ts +11 -0
  7. package/dist/commands/add.js +459 -0
  8. package/dist/commands/browse.d.ts +4 -0
  9. package/dist/commands/browse.js +379 -0
  10. package/dist/commands/config.d.ts +10 -0
  11. package/dist/commands/config.js +121 -0
  12. package/dist/commands/info.d.ts +9 -0
  13. package/dist/commands/info.js +122 -0
  14. package/dist/commands/init.d.ts +10 -0
  15. package/dist/commands/init.js +458 -0
  16. package/dist/commands/list.d.ts +10 -0
  17. package/dist/commands/list.js +76 -0
  18. package/dist/commands/mcp-config.d.ts +10 -0
  19. package/dist/commands/mcp-config.js +49 -0
  20. package/dist/commands/remotes.d.ts +22 -0
  21. package/dist/commands/remotes.js +196 -0
  22. package/dist/commands/remove.d.ts +8 -0
  23. package/dist/commands/remove.js +61 -0
  24. package/dist/commands/routines.d.ts +29 -0
  25. package/dist/commands/routines.js +363 -0
  26. package/dist/commands/run.d.ts +12 -0
  27. package/dist/commands/run.js +104 -0
  28. package/dist/commands/scheduler.d.ts +27 -0
  29. package/dist/commands/scheduler.js +350 -0
  30. package/dist/commands/search.d.ts +9 -0
  31. package/dist/commands/search.js +159 -0
  32. package/dist/commands/secrets.d.ts +28 -0
  33. package/dist/commands/secrets.js +236 -0
  34. package/dist/commands/serve.d.ts +13 -0
  35. package/dist/commands/serve.js +49 -0
  36. package/dist/commands/start.d.ts +8 -0
  37. package/dist/commands/start.js +27 -0
  38. package/dist/commands/update.d.ts +17 -0
  39. package/dist/commands/update.js +210 -0
  40. package/dist/core/config.d.ts +91 -0
  41. package/dist/core/config.js +738 -0
  42. package/dist/core/execute.d.ts +51 -0
  43. package/dist/core/execute.js +475 -0
  44. package/dist/core/link.d.ts +39 -0
  45. package/dist/core/link.js +214 -0
  46. package/dist/core/lockfile.d.ts +63 -0
  47. package/dist/core/lockfile.js +140 -0
  48. package/dist/core/manifest.d.ts +96 -0
  49. package/dist/core/manifest.js +224 -0
  50. package/dist/core/registry.d.ts +74 -0
  51. package/dist/core/registry.js +116 -0
  52. package/dist/core/remote-client.d.ts +98 -0
  53. package/dist/core/remote-client.js +252 -0
  54. package/dist/core/remotes.d.ts +88 -0
  55. package/dist/core/remotes.js +206 -0
  56. package/dist/core/routine-engine.d.ts +124 -0
  57. package/dist/core/routine-engine.js +699 -0
  58. package/dist/core/routines.d.ts +36 -0
  59. package/dist/core/routines.js +132 -0
  60. package/dist/core/scheduler-daemon.d.ts +10 -0
  61. package/dist/core/scheduler-daemon.js +77 -0
  62. package/dist/core/scheduler.d.ts +131 -0
  63. package/dist/core/scheduler.js +492 -0
  64. package/dist/core/secrets.d.ts +48 -0
  65. package/dist/core/secrets.js +384 -0
  66. package/dist/lib/cli.d.ts +84 -0
  67. package/dist/lib/cli.js +216 -0
  68. package/dist/mcp/adapter.d.ts +35 -0
  69. package/dist/mcp/adapter.js +94 -0
  70. package/dist/mcp/config-gen.d.ts +31 -0
  71. package/dist/mcp/config-gen.js +75 -0
  72. package/dist/mcp/server.d.ts +41 -0
  73. package/dist/mcp/server.js +296 -0
  74. package/dist/server/service.d.ts +85 -0
  75. package/dist/server/service.js +304 -0
  76. package/package.json +6 -3
  77. package/src/bin.ts +0 -118
  78. package/src/cli.ts +0 -409
  79. package/src/commands/add.ts +0 -562
  80. package/src/commands/browse.ts +0 -449
  81. package/src/commands/config.ts +0 -154
  82. package/src/commands/info.ts +0 -102
  83. package/src/commands/init.ts +0 -514
  84. package/src/commands/list.ts +0 -72
  85. package/src/commands/mcp-config.ts +0 -69
  86. package/src/commands/remotes.ts +0 -253
  87. package/src/commands/remove.ts +0 -78
  88. package/src/commands/routines.ts +0 -427
  89. package/src/commands/run.ts +0 -127
  90. package/src/commands/scheduler.ts +0 -438
  91. package/src/commands/search.ts +0 -148
  92. package/src/commands/secrets.ts +0 -292
  93. package/src/commands/serve.ts +0 -66
  94. package/src/commands/start.ts +0 -40
  95. package/src/commands/update.ts +0 -252
  96. package/src/core/config.ts +0 -845
  97. package/src/core/execute.ts +0 -569
  98. package/src/core/link.ts +0 -246
  99. package/src/core/lockfile.ts +0 -187
  100. package/src/core/manifest.ts +0 -327
  101. package/src/core/registry.ts +0 -165
  102. package/src/core/remote-client.ts +0 -419
  103. package/src/core/remotes.ts +0 -268
  104. package/src/core/routine-engine.ts +0 -895
  105. package/src/core/routines.ts +0 -171
  106. package/src/core/scheduler-daemon.ts +0 -94
  107. package/src/core/scheduler.ts +0 -606
  108. package/src/core/secrets.ts +0 -430
  109. package/src/lib/cli.ts +0 -261
  110. package/src/mcp/adapter.ts +0 -131
  111. package/src/mcp/config-gen.ts +0 -106
  112. package/src/mcp/server.ts +0 -365
  113. package/src/server/service.ts +0 -434
@@ -0,0 +1,36 @@
1
+ /**
2
+ * Routine discovery and resolution.
3
+ *
4
+ * Routines live in:
5
+ * - local: <project>/.cli4ai/routines
6
+ * - global: ~/.cli4ai/routines
7
+ *
8
+ * Resolution order:
9
+ * - local before global (unless globalOnly)
10
+ * - within a scope: .routine.yaml > .routine.yml > .routine.json > .routine.sh
11
+ */
12
+ import { type RoutineSchedule } from './routine-engine.js';
13
+ export type RoutineKind = 'yaml' | 'json' | 'bash';
14
+ export type RoutineScope = 'local' | 'global';
15
+ export interface RoutineInfo {
16
+ name: string;
17
+ kind: RoutineKind;
18
+ scope: RoutineScope;
19
+ path: string;
20
+ }
21
+ export interface ResolveRoutineOptions {
22
+ globalOnly?: boolean;
23
+ }
24
+ export declare function validateRoutineName(name: string): void;
25
+ export declare function getLocalRoutines(projectDir: string): RoutineInfo[];
26
+ export declare function getGlobalRoutines(): RoutineInfo[];
27
+ export declare function resolveRoutine(name: string, projectDir: string, options?: ResolveRoutineOptions): RoutineInfo | null;
28
+ export interface ScheduledRoutineInfo extends RoutineInfo {
29
+ schedule: RoutineSchedule;
30
+ }
31
+ /**
32
+ * Get all routines that have a schedule defined.
33
+ * Only JSON routines can have schedules (bash scripts cannot).
34
+ * Searches both local (if projectDir provided) and global routines.
35
+ */
36
+ export declare function getScheduledRoutines(projectDir?: string): ScheduledRoutineInfo[];
@@ -0,0 +1,132 @@
1
+ /**
2
+ * Routine discovery and resolution.
3
+ *
4
+ * Routines live in:
5
+ * - local: <project>/.cli4ai/routines
6
+ * - global: ~/.cli4ai/routines
7
+ *
8
+ * Resolution order:
9
+ * - local before global (unless globalOnly)
10
+ * - within a scope: .routine.yaml > .routine.yml > .routine.json > .routine.sh
11
+ */
12
+ import { existsSync, readdirSync, statSync, readFileSync } from 'fs';
13
+ import { resolve } from 'path';
14
+ import { parse as parseYaml } from 'yaml';
15
+ import { ensureCli4aiHome, ensureLocalDir, ROUTINES_DIR, LOCAL_ROUTINES_DIR } from './config.js';
16
+ import { validateScheduleConfig } from './routine-engine.js';
17
+ const ROUTINE_FILES = [
18
+ { kind: 'yaml', suffix: '.routine.yaml' },
19
+ { kind: 'yaml', suffix: '.routine.yml' },
20
+ { kind: 'json', suffix: '.routine.json' },
21
+ { kind: 'bash', suffix: '.routine.sh' }
22
+ ];
23
+ const NAME_PATTERN = /^[a-z][a-z0-9-]*$/;
24
+ export function validateRoutineName(name) {
25
+ if (!NAME_PATTERN.test(name)) {
26
+ throw new Error(`Invalid routine name: ${name}`);
27
+ }
28
+ }
29
+ function listRoutinesInDir(dir, scope) {
30
+ if (!existsSync(dir))
31
+ return [];
32
+ const results = [];
33
+ for (const entry of readdirSync(dir, { withFileTypes: true })) {
34
+ if (!entry.isFile())
35
+ continue;
36
+ for (const def of ROUTINE_FILES) {
37
+ if (!entry.name.endsWith(def.suffix))
38
+ continue;
39
+ const name = entry.name.slice(0, -def.suffix.length);
40
+ if (!NAME_PATTERN.test(name))
41
+ continue;
42
+ const fullPath = resolve(dir, entry.name);
43
+ // Best-effort: ensure it's a regular file
44
+ try {
45
+ if (!statSync(fullPath).isFile())
46
+ continue;
47
+ }
48
+ catch {
49
+ continue;
50
+ }
51
+ results.push({
52
+ name,
53
+ kind: def.kind,
54
+ scope,
55
+ path: fullPath
56
+ });
57
+ }
58
+ }
59
+ return results;
60
+ }
61
+ export function getLocalRoutines(projectDir) {
62
+ ensureLocalDir(projectDir);
63
+ const dir = resolve(projectDir, LOCAL_ROUTINES_DIR);
64
+ return listRoutinesInDir(dir, 'local');
65
+ }
66
+ export function getGlobalRoutines() {
67
+ ensureCli4aiHome();
68
+ return listRoutinesInDir(ROUTINES_DIR, 'global');
69
+ }
70
+ export function resolveRoutine(name, projectDir, options = {}) {
71
+ validateRoutineName(name);
72
+ const candidates = options.globalOnly
73
+ ? [{ scope: 'global', baseDir: ROUTINES_DIR }]
74
+ : [
75
+ { scope: 'local', baseDir: resolve(projectDir, LOCAL_ROUTINES_DIR) },
76
+ { scope: 'global', baseDir: ROUTINES_DIR }
77
+ ];
78
+ for (const { scope, baseDir } of candidates) {
79
+ for (const def of ROUTINE_FILES) {
80
+ const routinePath = resolve(baseDir, `${name}${def.suffix}`);
81
+ if (existsSync(routinePath)) {
82
+ return { name, kind: def.kind, scope, path: routinePath };
83
+ }
84
+ }
85
+ }
86
+ return null;
87
+ }
88
+ /**
89
+ * Get all routines that have a schedule defined.
90
+ * Only JSON routines can have schedules (bash scripts cannot).
91
+ * Searches both local (if projectDir provided) and global routines.
92
+ */
93
+ export function getScheduledRoutines(projectDir) {
94
+ const results = [];
95
+ const seen = new Set();
96
+ // Collect all structured routines (YAML and JSON - bash scripts cannot have schedules)
97
+ const allRoutines = [];
98
+ if (projectDir) {
99
+ allRoutines.push(...getLocalRoutines(projectDir).filter(r => r.kind === 'yaml' || r.kind === 'json'));
100
+ }
101
+ allRoutines.push(...getGlobalRoutines().filter(r => r.kind === 'yaml' || r.kind === 'json'));
102
+ for (const routine of allRoutines) {
103
+ // Skip if we've already processed a routine with this name (local takes precedence)
104
+ if (seen.has(routine.name))
105
+ continue;
106
+ seen.add(routine.name);
107
+ try {
108
+ const content = readFileSync(routine.path, 'utf-8');
109
+ const data = routine.kind === 'yaml' ? parseYaml(content) : JSON.parse(content);
110
+ if (data.schedule && typeof data.schedule === 'object') {
111
+ // Check if schedule is enabled (defaults to true)
112
+ const enabled = data.schedule.enabled !== false;
113
+ if (!enabled)
114
+ continue;
115
+ try {
116
+ const schedule = validateScheduleConfig(data.schedule, routine.path);
117
+ results.push({
118
+ ...routine,
119
+ schedule
120
+ });
121
+ }
122
+ catch {
123
+ // Skip routines with invalid schedule configs (don't crash the scheduler)
124
+ }
125
+ }
126
+ }
127
+ catch {
128
+ // Skip invalid JSON files
129
+ }
130
+ }
131
+ return results;
132
+ }
@@ -0,0 +1,10 @@
1
+ #!/usr/bin/env npx tsx
2
+ /**
3
+ * Scheduler daemon entry point.
4
+ *
5
+ * This script runs as a background process and manages scheduled routine execution.
6
+ * It's spawned by `cli4ai scheduler start` with detached mode.
7
+ *
8
+ * Usage: npx tsx scheduler-daemon.ts [--project-dir <dir>]
9
+ */
10
+ export {};
@@ -0,0 +1,77 @@
1
+ #!/usr/bin/env npx tsx
2
+ /**
3
+ * Scheduler daemon entry point.
4
+ *
5
+ * This script runs as a background process and manages scheduled routine execution.
6
+ * It's spawned by `cli4ai scheduler start` with detached mode.
7
+ *
8
+ * Usage: npx tsx scheduler-daemon.ts [--project-dir <dir>]
9
+ */
10
+ import { Scheduler, writeDaemonPid, removeDaemonPid, appendSchedulerLog, SCHEDULER_LOG_FILE, ensureSchedulerDirs } from './scheduler.js';
11
+ import { createWriteStream } from 'fs';
12
+ import { ensureCli4aiHome } from './config.js';
13
+ // Parse arguments
14
+ const args = process.argv.slice(2);
15
+ let projectDir;
16
+ for (let i = 0; i < args.length; i++) {
17
+ if (args[i] === '--project-dir' && args[i + 1]) {
18
+ projectDir = args[i + 1];
19
+ i++;
20
+ }
21
+ }
22
+ // Ensure directories exist
23
+ ensureCli4aiHome();
24
+ ensureSchedulerDirs();
25
+ // Redirect stdout/stderr to log file (when running detached)
26
+ if (process.env.CLI4AI_DAEMON === 'true') {
27
+ const logStream = createWriteStream(SCHEDULER_LOG_FILE, { flags: 'a' });
28
+ // Redirect console output
29
+ const originalLog = console.log;
30
+ const originalError = console.error;
31
+ console.log = (...args) => {
32
+ const message = args.map(a => typeof a === 'string' ? a : JSON.stringify(a)).join(' ');
33
+ logStream.write(`[${new Date().toISOString()}] [STDOUT] ${message}\n`);
34
+ };
35
+ console.error = (...args) => {
36
+ const message = args.map(a => typeof a === 'string' ? a : JSON.stringify(a)).join(' ');
37
+ logStream.write(`[${new Date().toISOString()}] [STDERR] ${message}\n`);
38
+ };
39
+ // Handle uncaught errors
40
+ process.on('uncaughtException', (err) => {
41
+ appendSchedulerLog('error', `Uncaught exception: ${err.message}\n${err.stack}`);
42
+ process.exit(1);
43
+ });
44
+ process.on('unhandledRejection', (reason) => {
45
+ appendSchedulerLog('error', `Unhandled rejection: ${reason}`);
46
+ });
47
+ }
48
+ // Write PID file
49
+ writeDaemonPid(process.pid);
50
+ appendSchedulerLog('info', `Daemon started with PID ${process.pid}`);
51
+ // Create and start scheduler
52
+ const scheduler = new Scheduler({ projectDir });
53
+ // Handle shutdown signals
54
+ let shuttingDown = false;
55
+ async function shutdown(signal) {
56
+ if (shuttingDown)
57
+ return;
58
+ shuttingDown = true;
59
+ appendSchedulerLog('info', `Received ${signal}, shutting down...`);
60
+ try {
61
+ await scheduler.stop();
62
+ removeDaemonPid();
63
+ appendSchedulerLog('info', 'Daemon stopped gracefully');
64
+ }
65
+ catch (err) {
66
+ appendSchedulerLog('error', `Error during shutdown: ${err instanceof Error ? err.message : String(err)}`);
67
+ }
68
+ process.exit(0);
69
+ }
70
+ process.on('SIGTERM', () => shutdown('SIGTERM'));
71
+ process.on('SIGINT', () => shutdown('SIGINT'));
72
+ // Start the scheduler
73
+ scheduler.start().catch(err => {
74
+ appendSchedulerLog('error', `Failed to start scheduler: ${err instanceof Error ? err.message : String(err)}`);
75
+ removeDaemonPid();
76
+ process.exit(1);
77
+ });
@@ -0,0 +1,131 @@
1
+ /**
2
+ * Scheduler core - manages scheduled routine execution.
3
+ *
4
+ * Storage structure:
5
+ * ~/.cli4ai/scheduler/
6
+ * ├── scheduler.pid # Daemon PID
7
+ * ├── state.json # Next runs, last results
8
+ * ├── runs/ # Execution records
9
+ * │ └── <routine>-<ts>.json
10
+ * └── logs/
11
+ * └── scheduler.log # Daemon logs
12
+ */
13
+ import { type RoutineSchedule } from './routine-engine.js';
14
+ export declare const SCHEDULER_PID_FILE: string;
15
+ export declare const SCHEDULER_STATE_FILE: string;
16
+ export declare const SCHEDULER_RUNS_DIR: string;
17
+ export declare const SCHEDULER_LOGS_DIR: string;
18
+ export declare const SCHEDULER_LOG_FILE: string;
19
+ export interface ScheduledRoutineState {
20
+ name: string;
21
+ path: string;
22
+ schedule: RoutineSchedule;
23
+ nextRunAt: string | null;
24
+ lastRunAt: string | null;
25
+ lastStatus: 'success' | 'failed' | null;
26
+ running: boolean;
27
+ retryCount: number;
28
+ }
29
+ export interface SchedulerState {
30
+ version: 1;
31
+ startedAt: string;
32
+ routines: Record<string, ScheduledRoutineState>;
33
+ }
34
+ export interface SchedulerRunRecord {
35
+ routine: string;
36
+ startedAt: string;
37
+ finishedAt: string;
38
+ status: 'success' | 'failed';
39
+ exitCode: number;
40
+ durationMs: number;
41
+ retryAttempt: number;
42
+ error?: string;
43
+ }
44
+ /**
45
+ * Parse an interval string like "30s", "5m", "1h", "1d" into milliseconds.
46
+ */
47
+ export declare function parseInterval(interval: string): number;
48
+ /**
49
+ * Calculate the next run time for a schedule.
50
+ * If both cron and interval are specified, returns the earlier of the two.
51
+ */
52
+ export declare function getNextRunTime(schedule: RoutineSchedule, after?: Date): Date | null;
53
+ /**
54
+ * Get the PID of the running daemon, or null if not running.
55
+ */
56
+ export declare function getDaemonPid(): number | null;
57
+ /**
58
+ * Check if the scheduler daemon is running.
59
+ */
60
+ export declare function isDaemonRunning(): boolean;
61
+ /**
62
+ * Write the daemon PID file.
63
+ */
64
+ export declare function writeDaemonPid(pid: number): void;
65
+ /**
66
+ * Remove the daemon PID file.
67
+ */
68
+ export declare function removeDaemonPid(): void;
69
+ export declare function ensureSchedulerDirs(): void;
70
+ /**
71
+ * Load scheduler state from disk.
72
+ */
73
+ export declare function loadSchedulerState(): SchedulerState | null;
74
+ /**
75
+ * Save scheduler state to disk.
76
+ */
77
+ export declare function saveSchedulerState(state: SchedulerState): void;
78
+ /**
79
+ * Save a run record to disk.
80
+ */
81
+ export declare function saveRunRecord(record: SchedulerRunRecord): void;
82
+ /**
83
+ * Get run history for a routine (or all routines).
84
+ */
85
+ export declare function getRunHistory(routineName?: string, limit?: number): SchedulerRunRecord[];
86
+ export type LogLevel = 'info' | 'warn' | 'error' | 'debug';
87
+ export declare function appendSchedulerLog(level: LogLevel, message: string): void;
88
+ export interface SchedulerOptions {
89
+ tickIntervalMs?: number;
90
+ projectDir?: string;
91
+ }
92
+ export declare class Scheduler {
93
+ private state;
94
+ private tickIntervalMs;
95
+ private projectDir;
96
+ private running;
97
+ private tickTimer;
98
+ private routineChainByName;
99
+ constructor(options?: SchedulerOptions);
100
+ /**
101
+ * Refresh the list of scheduled routines from disk.
102
+ */
103
+ refreshRoutines(): void;
104
+ /**
105
+ * Start the scheduler loop.
106
+ */
107
+ start(): Promise<void>;
108
+ /**
109
+ * Stop the scheduler loop.
110
+ */
111
+ stop(): Promise<void>;
112
+ /**
113
+ * Get current scheduler state (for status display).
114
+ */
115
+ getState(): SchedulerState;
116
+ private scheduleTick;
117
+ private isRoutineActive;
118
+ private enqueueRoutineExecution;
119
+ /**
120
+ * Execute one tick - check for routines that need to run.
121
+ */
122
+ private tick;
123
+ /**
124
+ * Execute a routine (async, doesn't block the tick loop).
125
+ */
126
+ private executeRoutine;
127
+ /**
128
+ * Manually trigger a routine to run now.
129
+ */
130
+ runNow(routineName: string): Promise<SchedulerRunRecord>;
131
+ }