@cef-ai/cli 0.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 (55) hide show
  1. package/README.md +1273 -0
  2. package/dist/cli.d.ts +3 -0
  3. package/dist/cli.js +66 -0
  4. package/dist/commands/deploy/clone.d.ts +13 -0
  5. package/dist/commands/deploy/clone.js +143 -0
  6. package/dist/commands/deploy/delete.d.ts +14 -0
  7. package/dist/commands/deploy/delete.js +134 -0
  8. package/dist/commands/deploy/deploy.d.ts +24 -0
  9. package/dist/commands/deploy/deploy.js +654 -0
  10. package/dist/commands/deploy/index.d.ts +2 -0
  11. package/dist/commands/deploy/index.js +216 -0
  12. package/dist/commands/deploy/status.d.ts +14 -0
  13. package/dist/commands/deploy/status.js +61 -0
  14. package/dist/commands/dev/index.d.ts +2 -0
  15. package/dist/commands/dev/index.js +134 -0
  16. package/dist/commands/dev/server.d.ts +14 -0
  17. package/dist/commands/dev/server.js +167 -0
  18. package/dist/commands/dev/ui.d.ts +1 -0
  19. package/dist/commands/dev/ui.js +984 -0
  20. package/dist/commands/playground/index.d.ts +2 -0
  21. package/dist/commands/playground/index.js +75 -0
  22. package/dist/commands/playground/test.d.ts +15 -0
  23. package/dist/commands/playground/test.js +166 -0
  24. package/dist/commands/service/index.d.ts +2 -0
  25. package/dist/commands/service/index.js +75 -0
  26. package/dist/lib/api.d.ts +146 -0
  27. package/dist/lib/api.js +330 -0
  28. package/dist/lib/config.d.ts +12 -0
  29. package/dist/lib/config.js +120 -0
  30. package/dist/lib/runtime/agent-router.d.ts +12 -0
  31. package/dist/lib/runtime/agent-router.js +78 -0
  32. package/dist/lib/runtime/context-builder.d.ts +27 -0
  33. package/dist/lib/runtime/context-builder.js +166 -0
  34. package/dist/lib/runtime/cubby-manager.d.ts +20 -0
  35. package/dist/lib/runtime/cubby-manager.js +161 -0
  36. package/dist/lib/runtime/index.d.ts +51 -0
  37. package/dist/lib/runtime/index.js +183 -0
  38. package/dist/lib/runtime/log-collector.d.ts +17 -0
  39. package/dist/lib/runtime/log-collector.js +61 -0
  40. package/dist/lib/runtime/stream-manager.d.ts +10 -0
  41. package/dist/lib/runtime/stream-manager.js +77 -0
  42. package/dist/lib/runtime/types.d.ts +76 -0
  43. package/dist/lib/runtime/types.js +2 -0
  44. package/dist/lib/typecheck.d.ts +31 -0
  45. package/dist/lib/typecheck.js +175 -0
  46. package/dist/lib/types.d.ts +246 -0
  47. package/dist/lib/types.js +2 -0
  48. package/dist/lib/utils.d.ts +4 -0
  49. package/dist/lib/utils.js +33 -0
  50. package/example/.env.example +6 -0
  51. package/example/agents/object-detection/tasks/yolo.ts +28 -0
  52. package/example/agents/parking-violation-detector/tasks/detect.ts +326 -0
  53. package/example/cef.config.yaml +91 -0
  54. package/example/engagements/engagement.ts +902 -0
  55. package/package.json +69 -0
package/dist/cli.d.ts ADDED
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ import { Command } from 'commander';
3
+ export declare function createProgram(): Command;
package/dist/cli.js ADDED
@@ -0,0 +1,66 @@
1
+ #!/usr/bin/env node
2
+ import fs from 'fs';
3
+ import path from 'path';
4
+ import { fileURLToPath } from 'url';
5
+ import { Command } from 'commander';
6
+ import { registerDeployCommands } from './commands/deploy/index.js';
7
+ import { registerServiceCommands } from './commands/service/index.js';
8
+ import { registerPlaygroundCommands } from './commands/playground/index.js';
9
+ import { registerDevCommands } from './commands/dev/index.js';
10
+ function getVersion() {
11
+ try {
12
+ const dir = path.dirname(fileURLToPath(import.meta.url));
13
+ const pkgPath = path.join(dir, '..', 'package.json');
14
+ const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
15
+ return pkg.version ?? '0.0.0';
16
+ }
17
+ catch {
18
+ return '0.0.0';
19
+ }
20
+ }
21
+ function showWelcome(version) {
22
+ console.log(`
23
+ \x1b[1mCEF AI CLI\x1b[0m \x1b[2mv${version}\x1b[0m
24
+
25
+ \x1b[36mQuick start:\x1b[0m
26
+
27
+ \x1b[33m$\x1b[0m cef dev Start local dev server with UI
28
+ \x1b[33m$\x1b[0m cef deploy Deploy topology to orchestrator
29
+ \x1b[33m$\x1b[0m cef status Compare local vs deployed state
30
+
31
+ \x1b[36mOther commands:\x1b[0m
32
+
33
+ \x1b[33m$\x1b[0m cef service list List agent services
34
+ \x1b[33m$\x1b[0m cef service create Create a new agent service
35
+ \x1b[33m$\x1b[0m cef clone --pubkey 0x... Download existing service
36
+ \x1b[33m$\x1b[0m cef delete Delete deployed entities
37
+ \x1b[33m$\x1b[0m cef --help Show all options
38
+
39
+ \x1b[2mDocs: https://github.com/CEF-AI/deploy-sdk#readme\x1b[0m
40
+ `);
41
+ }
42
+ export function createProgram() {
43
+ const program = new Command();
44
+ program
45
+ .name('cef')
46
+ .description('CEF AI command-line interface')
47
+ .version(getVersion(), '-V, --version');
48
+ registerDeployCommands(program);
49
+ registerServiceCommands(program);
50
+ registerPlaygroundCommands(program);
51
+ registerDevCommands(program);
52
+ return program;
53
+ }
54
+ const isDirectRun = process.argv[1] &&
55
+ (fileURLToPath(import.meta.url) === process.argv[1] ||
56
+ process.argv[1].endsWith('/cli.js'));
57
+ if (isDirectRun) {
58
+ const program = createProgram();
59
+ // No args (or only node + script): show welcome instead of crashing into deploy
60
+ const userArgs = process.argv.slice(2);
61
+ if (userArgs.length === 0) {
62
+ showWelcome(getVersion());
63
+ process.exit(0);
64
+ }
65
+ program.parse();
66
+ }
@@ -0,0 +1,13 @@
1
+ import type { EnvConfig } from '../../lib/types.js';
2
+ export interface CloneOptions {
3
+ pubkey: string;
4
+ outputDir: string;
5
+ force: boolean;
6
+ }
7
+ export interface CloneResult {
8
+ entity: string;
9
+ name: string;
10
+ action: 'written' | 'skipped' | 'fetched';
11
+ file?: string;
12
+ }
13
+ export declare function runClone(env: EnvConfig, options: CloneOptions): Promise<CloneResult[]>;
@@ -0,0 +1,143 @@
1
+ import fs from 'fs';
2
+ import path from 'path';
3
+ import { stringify } from 'yaml';
4
+ import { createApiClient } from '../../lib/api.js';
5
+ import { toFileName, errorMessage } from '../../lib/utils.js';
6
+ function writeFileIfAllowed(filePath, content, force, allowedBase) {
7
+ if (allowedBase) {
8
+ const resolved = path.resolve(filePath);
9
+ const base = path.resolve(allowedBase);
10
+ if (!resolved.startsWith(base + path.sep) && resolved !== base) {
11
+ throw new Error(`Path traversal detected: ${filePath} escapes ${allowedBase}`);
12
+ }
13
+ }
14
+ if (fs.existsSync(filePath) && !force)
15
+ return 'skipped';
16
+ fs.mkdirSync(path.dirname(filePath), { recursive: true });
17
+ fs.writeFileSync(filePath, content, 'utf8');
18
+ return 'written';
19
+ }
20
+ async function safeFetch(label, fn) {
21
+ try {
22
+ return await fn();
23
+ }
24
+ catch (err) {
25
+ console.warn(`Warning: Could not fetch ${label}: ${errorMessage(err)}`);
26
+ return null;
27
+ }
28
+ }
29
+ export async function runClone(env, options) {
30
+ const { pubkey, outputDir, force } = options;
31
+ const api = createApiClient(env, pubkey);
32
+ const results = [];
33
+ // Fetch in parallel
34
+ const [engagementsRes, agentsRes, ontologyRes, cubbiesRes] = await Promise.all([
35
+ safeFetch('engagements', () => api.getEngagements()),
36
+ safeFetch('agents', () => api.getAgents()),
37
+ safeFetch('ontology', () => api.getOntology()),
38
+ safeFetch('cubbies', () => api.getCubbies()),
39
+ ]);
40
+ const allAgents = agentsRes?.data ?? [];
41
+ const engagements = engagementsRes?.data ?? [];
42
+ // Separate concierge agents from user agents
43
+ const conciergeMap = new Map(allAgents.filter(a => a.isConcierge).map(a => [a.agentId, a]));
44
+ const userAgents = allAgents.filter(a => !a.isConcierge);
45
+ // ─── Write engagement files ────────────────────────────────────────────
46
+ const engagementConfigs = engagements.map(eng => {
47
+ const concierge = conciergeMap.get(eng.conciergeAgentId);
48
+ const tsCode = concierge?.tasks[0]?.tsCode ?? '';
49
+ let fileName = `${toFileName(eng.name)}.ts`;
50
+ if (fileName === 'unknown.ts')
51
+ fileName = `unknown-${eng.engagementId}.ts`;
52
+ const relPath = `./engagements/${fileName}`;
53
+ const absPath = path.join(outputDir, 'engagements', fileName);
54
+ if (tsCode) {
55
+ const action = writeFileIfAllowed(absPath, tsCode, force, outputDir);
56
+ results.push({ entity: 'engagement', name: eng.name, action, file: relPath });
57
+ }
58
+ return {
59
+ name: eng.name,
60
+ description: eng.description || undefined,
61
+ engagementId: eng.engagementId,
62
+ file: relPath,
63
+ version: eng.version || '1.0.0',
64
+ };
65
+ });
66
+ // ─── Write agent task files ────────────────────────────────────────────
67
+ const agentConfigs = userAgents.map(agent => {
68
+ const tasks = agent.tasks.map(task => {
69
+ let taskFileName = `${toFileName(task.alias)}.ts`;
70
+ if (taskFileName === 'unknown.ts')
71
+ taskFileName = `unknown-${task.id}.ts`;
72
+ const relPath = `./agents/${agent.alias}/tasks/${taskFileName}`;
73
+ const absPath = path.join(outputDir, 'agents', agent.alias, 'tasks', taskFileName);
74
+ if (task.tsCode) {
75
+ const action = writeFileIfAllowed(absPath, task.tsCode, force, outputDir);
76
+ results.push({ entity: 'agent-task', name: `${agent.name}/${task.name}`, action, file: relPath });
77
+ }
78
+ return {
79
+ name: task.name,
80
+ alias: task.alias,
81
+ file: relPath,
82
+ parameters: task.parameters,
83
+ returns: task.returns,
84
+ };
85
+ });
86
+ return {
87
+ name: agent.name,
88
+ alias: agent.alias,
89
+ description: agent.description || undefined,
90
+ agentId: agent.agentId,
91
+ version: agent.version || '1.0.0',
92
+ tasks,
93
+ };
94
+ });
95
+ // ─── Map ontology to workspace config ──────────────────────────────────
96
+ const workspaceConfigs = ontologyRes?.workspaces?.map(ws => ({
97
+ name: ws.name,
98
+ description: ws.description || undefined,
99
+ workspaceId: ws.workspaceId,
100
+ streams: ws.streams?.map(s => ({
101
+ name: s.name,
102
+ description: s.description || undefined,
103
+ streamId: s.streamId,
104
+ selectors: s.selectors?.length ? s.selectors : undefined,
105
+ deployments: s.deployments?.map(d => ({
106
+ name: d.name,
107
+ description: d.description || undefined,
108
+ deploymentId: d.deploymentId,
109
+ engagement: d.engagements?.[0]?.name ?? '',
110
+ isActive: d.isActive,
111
+ triggers: d.triggers?.length ? d.triggers : undefined,
112
+ })),
113
+ })),
114
+ }));
115
+ // ─── Map remote cubbies to config ───────────────────────────────────────
116
+ const remoteCubbies = cubbiesRes ?? [];
117
+ const cubbyConfigs = remoteCubbies.map(cubby => ({
118
+ alias: cubby.alias,
119
+ name: cubby.name,
120
+ description: cubby.description || undefined,
121
+ cubbyId: cubby.cubbyId,
122
+ migrations: cubby.migrations,
123
+ maxSizeBytes: cubby.maxSizeBytes,
124
+ idleTimeout: cubby.idleTimeout,
125
+ }));
126
+ // ─── Build config ──────────────────────────────────────────────────────
127
+ const config = {
128
+ agentServicePubKey: pubkey,
129
+ };
130
+ if (engagementConfigs.length)
131
+ config.engagements = engagementConfigs;
132
+ if (agentConfigs.length)
133
+ config.agents = agentConfigs;
134
+ if (cubbyConfigs.length)
135
+ config.cubbies = cubbyConfigs;
136
+ if (workspaceConfigs && workspaceConfigs.length)
137
+ config.workspaces = workspaceConfigs;
138
+ // Write config file (respects --force)
139
+ const configPath = path.join(outputDir, 'cef.config.yaml');
140
+ const configAction = writeFileIfAllowed(configPath, stringify(config), force);
141
+ results.push({ entity: 'config', name: 'cef.config.yaml', action: configAction, file: configPath });
142
+ return results;
143
+ }
@@ -0,0 +1,14 @@
1
+ import type { ResolvedConfig } from '../../lib/types.js';
2
+ export interface DeleteResult {
3
+ entity: string;
4
+ name: string;
5
+ id: string;
6
+ action: 'deleted' | 'skipped' | 'failed';
7
+ error?: string;
8
+ }
9
+ interface DeleteOptions {
10
+ only?: string;
11
+ force?: boolean;
12
+ }
13
+ export declare function runDelete(resolved: ResolvedConfig, options: DeleteOptions): Promise<DeleteResult[]>;
14
+ export {};
@@ -0,0 +1,134 @@
1
+ import readline from 'readline/promises';
2
+ import { createApiClient } from '../../lib/api.js';
3
+ import { writeIdsBack } from './deploy.js';
4
+ import { errorMessage } from '../../lib/utils.js';
5
+ function collectEntitiesToDelete(config, api) {
6
+ const entities = [];
7
+ // Reverse dependency order: deployments -> rafts -> streams -> workspaces -> agents -> engagements
8
+ for (const ws of config.workspaces ?? []) {
9
+ for (const stream of ws.streams ?? []) {
10
+ for (const dep of stream.deployments ?? []) {
11
+ if (dep.deploymentId) {
12
+ entities.push({
13
+ entity: 'deployment',
14
+ name: dep.name,
15
+ id: dep.deploymentId,
16
+ deleteFn: () => api.deleteDeployment(dep.deploymentId),
17
+ clearId: () => { dep.deploymentId = undefined; },
18
+ });
19
+ }
20
+ }
21
+ for (const raft of stream.rafts ?? []) {
22
+ if (raft.raftId) {
23
+ entities.push({
24
+ entity: 'raft',
25
+ name: raft.name,
26
+ id: raft.raftId,
27
+ deleteFn: () => api.deleteRaft(raft.raftId),
28
+ clearId: () => { raft.raftId = undefined; },
29
+ });
30
+ }
31
+ }
32
+ if (stream.streamId) {
33
+ entities.push({
34
+ entity: 'stream',
35
+ name: stream.name,
36
+ id: stream.streamId,
37
+ deleteFn: () => api.deleteStream(stream.streamId),
38
+ clearId: () => { stream.streamId = undefined; },
39
+ });
40
+ }
41
+ }
42
+ if (ws.workspaceId) {
43
+ entities.push({
44
+ entity: 'workspace',
45
+ name: ws.name,
46
+ id: ws.workspaceId,
47
+ deleteFn: () => api.deleteWorkspace(ws.workspaceId),
48
+ clearId: () => { ws.workspaceId = undefined; },
49
+ });
50
+ }
51
+ }
52
+ for (const agent of config.agents ?? []) {
53
+ if (agent.agentId) {
54
+ entities.push({
55
+ entity: 'agent',
56
+ name: agent.name,
57
+ id: agent.agentId,
58
+ deleteFn: () => api.deleteAgent(agent.agentId),
59
+ clearId: () => { agent.agentId = undefined; },
60
+ });
61
+ }
62
+ }
63
+ for (const eng of config.engagements ?? []) {
64
+ if (eng.engagementId) {
65
+ entities.push({
66
+ entity: 'engagement',
67
+ name: eng.name,
68
+ id: eng.engagementId,
69
+ deleteFn: () => api.deleteEngagement(eng.engagementId),
70
+ clearId: () => { eng.engagementId = undefined; },
71
+ });
72
+ }
73
+ }
74
+ return entities;
75
+ }
76
+ async function confirmDelete(count, pubkey) {
77
+ if (!process.stdin.isTTY || !process.stdout.isTTY) {
78
+ throw new Error(`Cannot confirm deletion of ${count} entities in non-interactive mode. Use --force to skip confirmation.`);
79
+ }
80
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
81
+ try {
82
+ const answer = await rl.question(`Delete ${count} entity/entities from agent service ${pubkey}? This cannot be undone. (y/n) `);
83
+ return /^y(es)?$/i.test((answer ?? '').trim());
84
+ }
85
+ finally {
86
+ rl.close();
87
+ }
88
+ }
89
+ export async function runDelete(resolved, options) {
90
+ const { config, env, configPath } = resolved;
91
+ const api = createApiClient(env, config.agentServicePubKey, config.agentServiceId);
92
+ let entities = collectEntitiesToDelete(config, api);
93
+ if (options.only) {
94
+ entities = entities.filter(e => e.entity === options.only);
95
+ }
96
+ // Warn about cubbies that cannot be deleted via CLI
97
+ for (const cubby of config.cubbies ?? []) {
98
+ if (cubby.cubbyId && (!options.only || options.only === 'cubby')) {
99
+ console.warn(`Warning: cubby "${cubby.name}" cannot be deleted via CLI. Remove manually if needed.`);
100
+ }
101
+ }
102
+ if (entities.length === 0) {
103
+ console.log('No entities with IDs to delete.');
104
+ return [];
105
+ }
106
+ console.log(`\nEntities to delete (${entities.length}):`);
107
+ for (const e of entities) {
108
+ console.log(` ${e.entity}: ${e.name} (${e.id})`);
109
+ }
110
+ if (!options.force) {
111
+ const confirmed = await confirmDelete(entities.length, config.agentServicePubKey);
112
+ if (!confirmed) {
113
+ console.log('Cancelled.');
114
+ return entities.map(e => ({ entity: e.entity, name: e.name, id: e.id, action: 'skipped' }));
115
+ }
116
+ }
117
+ const results = [];
118
+ for (const e of entities) {
119
+ try {
120
+ console.log(`Deleting ${e.entity}: ${e.name} (${e.id})`);
121
+ await e.deleteFn();
122
+ e.clearId();
123
+ results.push({ entity: e.entity, name: e.name, id: e.id, action: 'deleted' });
124
+ }
125
+ catch (err) {
126
+ const msg = errorMessage(err);
127
+ console.error(` Failed to delete ${e.entity} "${e.name}": ${msg}`);
128
+ results.push({ entity: e.entity, name: e.name, id: e.id, action: 'failed', error: msg });
129
+ }
130
+ }
131
+ // Write cleaned config (IDs removed for successfully deleted entities)
132
+ writeIdsBack(configPath, config);
133
+ return results;
134
+ }
@@ -0,0 +1,24 @@
1
+ import type { ResolvedConfig, DeployResult, CefConfig } from '../../lib/types.js';
2
+ export type PromptForCopyEntity = 'engagement' | 'agent' | 'workspace' | 'stream' | 'deployment' | 'raft';
3
+ export type PromptForCopy = (entity: PromptForCopyEntity, name: string) => Promise<boolean>;
4
+ /** Return workspace ID for this agent service, or undefined to create new. */
5
+ export type PromptForWorkspaceId = (workspaceName: string) => Promise<string | undefined>;
6
+ /** Called when config.agentServiceId doesn't match the resolved ID from ROB, indicating the config was likely copied from another service. Return true to clear all entity IDs and deploy fresh. */
7
+ export type PromptForClearIds = (oldServiceId: string, newServiceId: string) => Promise<boolean>;
8
+ interface DeployOptions {
9
+ dryRun?: boolean;
10
+ only?: string;
11
+ /** When update returns 404 (e.g. ID from another agent service), call this to ask if we should create a copy. Default: interactive y/n when TTY, else throw. */
12
+ promptForCopy?: PromptForCopy;
13
+ /** When workspace has no workspaceId and we can't resolve it from the orchestrator, call this. Return ID or undefined to create new. Default: interactive prompt when TTY. */
14
+ promptForWorkspaceId?: PromptForWorkspaceId;
15
+ /** When agentServiceId mismatch is detected (config copied from another service), call this to ask if all entity IDs should be cleared. Default: interactive y/n when TTY, else throw. */
16
+ promptForClearIds?: PromptForClearIds;
17
+ }
18
+ /** Strip all entity IDs from config so everything deploys as new. */
19
+ export declare function clearAllEntityIds(config: CefConfig): void;
20
+ /** Detect if config was copied from a different agent service and offer to clear all entity IDs. */
21
+ export declare function detectAndClearStaleIds(config: CefConfig, oldServiceId: string | undefined, newServiceId: string, promptFn: PromptForClearIds): Promise<boolean>;
22
+ export declare function writeIdsBack(configPath: string, config: CefConfig): void;
23
+ export declare function runDeploy(resolved: ResolvedConfig, options: DeployOptions): Promise<DeployResult[]>;
24
+ export {};