@hazeljs/cli 1.0.6 → 2.0.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.
@@ -35,13 +35,94 @@ var __importStar = (this && this.__importStar) || (function () {
35
35
  Object.defineProperty(exports, "__esModule", { value: true });
36
36
  exports.registerAgentCommand = registerAgentCommand;
37
37
  const fs = __importStar(require("fs"));
38
+ const os = __importStar(require("os"));
38
39
  const path = __importStar(require("path"));
40
+ const agent_templates_1 = require("./agent-templates");
41
+ const DEFAULT_RUN_STORE = path.join('.hazel', 'agent-runs.json');
42
+ const DEFAULT_DURABLE_DIR = path.join('.hazel', 'runs');
43
+ const DEFAULT_TIMELINE = path.join('.hazel', 'runs', 'timeline.jsonl');
44
+ const DEFAULT_PLATFORM_STORE = path.join('.hazel', 'platform', 'resources.json');
39
45
  /**
46
+ * `hazel agent new` — scaffold Agent OS / DNA templates (G2 template unification).
40
47
  * `hazel agent install <file.dna.json>` — validate / print marketplace install plan.
41
- * Live hot-reload happens in-process via AgentRuntime.installAgentPackage().
48
+ * `hazel agent run` live execute from DNA (AOS-011).
49
+ * `hazel agent apply|get|describe|delete|reconcile|events` — declarative platform resources (local control plane).
50
+ * `hazel agent logs` / `doctor` — timeline + environment checks.
51
+ * `hazel agent runs list|inspect|cancel|resume|approve` — durable store ops.
42
52
  */
43
53
  function registerAgentCommand(program) {
44
- const agent = program.command('agent').description('Agent OS DNA / marketplace helpers');
54
+ const agent = program
55
+ .command('agent')
56
+ .description('Agent OS DNA / runtime / marketplace / platform helpers');
57
+ agent
58
+ .command('templates')
59
+ .description('List Agent OS / DNA project templates')
60
+ .option('--json', 'Print JSON')
61
+ .action((opts) => {
62
+ const templates = (0, agent_templates_1.listAgentTemplates)();
63
+ if (opts.json) {
64
+ // eslint-disable-next-line no-console
65
+ console.log(JSON.stringify({ ok: true, templates }, null, 2));
66
+ return;
67
+ }
68
+ // eslint-disable-next-line no-console
69
+ console.log('\nAgent OS templates (`hazel agent new <name> --template <id>`):\n');
70
+ for (const t of templates) {
71
+ // eslint-disable-next-line no-console
72
+ console.log(` ${t.id.padEnd(12)} ${t.label}`);
73
+ // eslint-disable-next-line no-console
74
+ console.log(` ${t.description}\n`);
75
+ }
76
+ });
77
+ agent
78
+ .command('new')
79
+ .description('Scaffold an Agent OS / DNA project (bare | agent-os | skillgate). DNA = contract; app tools = implementation.')
80
+ .argument('<name>', 'Project directory / package name')
81
+ .option('-t, --template <id>', 'Template: bare | agent-os | skillgate', 'agent-os')
82
+ .option('-d, --dest <dir>', 'Parent directory', '.')
83
+ .option('-f, --force', 'Allow non-empty destination')
84
+ .option('--json', 'Print machine-readable result')
85
+ .action((name, opts) => {
86
+ try {
87
+ const destDir = path.resolve(process.cwd(), opts.dest, name);
88
+ const result = (0, agent_templates_1.scaffoldAgentProject)({
89
+ name,
90
+ destDir,
91
+ template: opts.template,
92
+ force: opts.force,
93
+ });
94
+ const payload = {
95
+ ok: true,
96
+ action: 'agent-new',
97
+ ...result,
98
+ next: [
99
+ `cd ${path.relative(process.cwd(), result.path) || '.'}`,
100
+ result.template === 'bare'
101
+ ? 'npx hazel agent run dna/agent.marketplace.json "hello"'
102
+ : 'npm install && npm run dev',
103
+ 'npx hazel store publish dna/agent.marketplace.json',
104
+ ],
105
+ };
106
+ // eslint-disable-next-line no-console
107
+ console.log(opts.json
108
+ ? JSON.stringify(payload, null, 2)
109
+ : [
110
+ `✓ Created Agent OS project (${result.template})`,
111
+ ` ${result.path}`,
112
+ ` files: ${result.files.length}`,
113
+ '',
114
+ 'Next:',
115
+ ...payload.next.map((l) => ` ${l}`),
116
+ '',
117
+ 'Note: `hazel agent run` on DNA uses stub tools. Use the app (`npm run dev`) for real @Tool / Skillgate handlers.',
118
+ ].join('\n'));
119
+ }
120
+ catch (e) {
121
+ // eslint-disable-next-line no-console
122
+ console.error(e instanceof Error ? e.message : e);
123
+ process.exitCode = 1;
124
+ }
125
+ });
45
126
  agent
46
127
  .command('install')
47
128
  .description('Validate a .dna / marketplace JSON package and print install plan')
@@ -58,7 +139,7 @@ function registerAgentCommand(program) {
58
139
  agent: pkg.dna.name,
59
140
  tools: pkg.dna.tools.map((t) => t.name),
60
141
  hasPolicies: Boolean(pkg.dna.policies?.length),
61
- note: 'Call runtime.installAgentPackage(path) in your app to hot-reload',
142
+ note: 'Validate only. Use hazel store install to materialize into .hazel/agents; call runtime.installAgentPackage(path) to hot-reload',
62
143
  }, null, 2));
63
144
  if (opts.out) {
64
145
  const outPath = path.join(opts.out, `${pkg.dna.name}.marketplace.json`);
@@ -91,4 +172,643 @@ function registerAgentCommand(program) {
91
172
  process.exitCode = 1;
92
173
  }
93
174
  });
175
+ agent
176
+ .command('run')
177
+ .description('Execute an agent from DNA / marketplace package (live CLI run, AOS-011)')
178
+ .argument('<file>', 'Path to .dna.json or marketplace package JSON')
179
+ .argument('[input...]', 'User input (default: hello)')
180
+ .option('--dir <path>', 'Durable store directory', DEFAULT_DURABLE_DIR)
181
+ .option('--mock', 'Use offline mock LLM (no API key)')
182
+ .option('--model <model>', 'Model id for HTTP LLM', process.env.HAZEL_AGENT_MODEL ?? 'gpt-4o-mini')
183
+ .option('--base-url <url>', 'OpenAI-compatible base URL', process.env.OPENAI_BASE_URL)
184
+ .option('--api-key <key>', 'API key (default: OPENAI_API_KEY)')
185
+ .option('--worker-id <id>', 'Worker id for run leases', `cli-${os.hostname()}`)
186
+ .option('--max-steps <n>', 'Max agent steps', '8')
187
+ .option('--json', 'Print full execution result JSON')
188
+ .action(async (file, inputParts, opts) => {
189
+ try {
190
+ const { bootstrapRuntimeFromDna, createHttpLlmProvider, createMockLlmProvider } = await Promise.resolve().then(() => __importStar(require('@hazeljs/agent')));
191
+ const dnaPath = path.resolve(process.cwd(), file);
192
+ if (!fs.existsSync(dnaPath)) {
193
+ throw new Error(`DNA file not found: ${dnaPath}`);
194
+ }
195
+ const input = inputParts.length ? inputParts.join(' ') : 'hello';
196
+ const storeDir = path.resolve(process.cwd(), opts.dir);
197
+ const apiKey = opts.apiKey ?? process.env.OPENAI_API_KEY;
198
+ const llm = opts.mock || !apiKey
199
+ ? createMockLlmProvider(opts.mock
200
+ ? 'Mock reply from hazel agent run.'
201
+ : 'No OPENAI_API_KEY — mock reply. Pass --mock to silence this, or set a key.')
202
+ : createHttpLlmProvider({
203
+ apiKey,
204
+ baseUrl: opts.baseUrl,
205
+ model: opts.model,
206
+ });
207
+ const { runtime, dna, store, timelinePath } = bootstrapRuntimeFromDna(dnaPath, {
208
+ llmProvider: llm,
209
+ storeDir,
210
+ durableSuspend: true,
211
+ workerId: opts.workerId,
212
+ stubTools: true,
213
+ });
214
+ const result = await runtime.execute(dna.name, input, {
215
+ maxSteps: Number(opts.maxSteps) || 8,
216
+ });
217
+ const run = store ? await store.runRepository.get(result.executionId) : undefined;
218
+ const summary = {
219
+ agent: dna.name,
220
+ executionId: result.executionId,
221
+ state: result.state,
222
+ response: result.response,
223
+ runStatus: run?.status,
224
+ storeDir,
225
+ timelinePath,
226
+ llm: opts.mock || !apiKey ? 'mock' : 'http',
227
+ };
228
+ // eslint-disable-next-line no-console
229
+ console.log(JSON.stringify(opts.json ? { ...summary, result, run } : summary, null, 2));
230
+ }
231
+ catch (e) {
232
+ // eslint-disable-next-line no-console
233
+ console.error(e);
234
+ process.exitCode = 1;
235
+ }
236
+ });
237
+ agent
238
+ .command('logs')
239
+ .description('Show AgentRun timeline JSONL (optionally filter by run id)')
240
+ .option('--timeline <path>', 'Timeline JSONL path', DEFAULT_TIMELINE)
241
+ .option('--run <runId>', 'Filter by execution / run id')
242
+ .option('--agent <name>', 'Filter by agent name')
243
+ .option('--follow', 'Tail new lines (poll)')
244
+ .option('--interval <ms>', 'Follow poll interval', '1000')
245
+ .action(async (opts) => {
246
+ try {
247
+ const { FileTimelineStore } = await Promise.resolve().then(() => __importStar(require('@hazeljs/agent')));
248
+ const timelinePath = path.resolve(process.cwd(), opts.timeline);
249
+ const store = new FileTimelineStore(timelinePath);
250
+ const print = () => {
251
+ const steps = store.load({
252
+ executionId: opts.run,
253
+ agentName: opts.agent,
254
+ });
255
+ // eslint-disable-next-line no-console
256
+ console.log(JSON.stringify(steps, null, 2));
257
+ };
258
+ if (!opts.follow) {
259
+ print();
260
+ return;
261
+ }
262
+ let lastSize = fs.existsSync(timelinePath) ? fs.statSync(timelinePath).size : 0;
263
+ print();
264
+ const ms = Math.max(200, Number(opts.interval) || 1000);
265
+ // eslint-disable-next-line no-console
266
+ console.error(`Following ${timelinePath} every ${ms}ms (Ctrl+C to stop)…`);
267
+ setInterval(() => {
268
+ if (!fs.existsSync(timelinePath))
269
+ return;
270
+ const size = fs.statSync(timelinePath).size;
271
+ if (size !== lastSize) {
272
+ lastSize = size;
273
+ print();
274
+ }
275
+ }, ms);
276
+ }
277
+ catch (e) {
278
+ // eslint-disable-next-line no-console
279
+ console.error(e);
280
+ process.exitCode = 1;
281
+ }
282
+ });
283
+ agent
284
+ .command('doctor')
285
+ .description('Check Agent OS CLI environment (peers, store paths, LLM key)')
286
+ .option('--dir <path>', 'Expected durable store directory', DEFAULT_DURABLE_DIR)
287
+ .action(async (opts) => {
288
+ try {
289
+ const cwd = process.cwd();
290
+ const storeDir = path.resolve(cwd, opts.dir);
291
+ const checks = [];
292
+ checks.push({
293
+ ok: true,
294
+ name: 'node',
295
+ detail: process.version,
296
+ });
297
+ try {
298
+ await Promise.resolve().then(() => __importStar(require('@hazeljs/agent')));
299
+ checks.push({ ok: true, name: '@hazeljs/agent', detail: 'resolvable' });
300
+ }
301
+ catch (e) {
302
+ checks.push({
303
+ ok: false,
304
+ name: '@hazeljs/agent',
305
+ detail: e instanceof Error ? e.message : String(e),
306
+ });
307
+ }
308
+ const pkgPath = path.join(cwd, 'package.json');
309
+ checks.push({
310
+ ok: fs.existsSync(pkgPath),
311
+ name: 'package.json',
312
+ detail: fs.existsSync(pkgPath) ? pkgPath : 'missing in cwd',
313
+ });
314
+ checks.push({
315
+ ok: true,
316
+ name: 'durableStore',
317
+ detail: fs.existsSync(storeDir)
318
+ ? `exists: ${storeDir}`
319
+ : `will be created on run: ${storeDir}`,
320
+ });
321
+ const key = process.env.OPENAI_API_KEY;
322
+ checks.push({
323
+ ok: Boolean(key),
324
+ name: 'OPENAI_API_KEY',
325
+ detail: key ? 'set (http LLM available)' : 'unset — use --mock for offline run',
326
+ });
327
+ const failed = checks.filter((c) => !c.ok);
328
+ // eslint-disable-next-line no-console
329
+ console.log(JSON.stringify({
330
+ ok: failed.length === 0 || (failed.length === 1 && failed[0].name === 'OPENAI_API_KEY'),
331
+ checks,
332
+ hints: [
333
+ 'hazel agent run ./agent.dna.json "hello" --mock',
334
+ 'hazel agent runs list --dir .hazel/runs',
335
+ 'hazel agent logs --timeline .hazel/runs/timeline.jsonl',
336
+ ],
337
+ }, null, 2));
338
+ if (failed.some((c) => c.name === '@hazeljs/agent'))
339
+ process.exitCode = 1;
340
+ }
341
+ catch (e) {
342
+ // eslint-disable-next-line no-console
343
+ console.error(e);
344
+ process.exitCode = 1;
345
+ }
346
+ });
347
+ const runs = agent.command('runs').description('Inspect durable AgentRun records (file store)');
348
+ runs
349
+ .command('list')
350
+ .description('List AgentRun records from a FileAgentRunRepository JSON store')
351
+ .option('--store <path>', 'Path to runs JSON', DEFAULT_RUN_STORE)
352
+ .option('--dir <path>', 'Durable store directory (uses runs.json inside)')
353
+ .option('--agent <name>', 'Filter by agent name')
354
+ .option('--status <status>', 'Filter by status')
355
+ .action(async (opts) => {
356
+ try {
357
+ const { FileAgentRunRepository, AgentRunStatus, createDurableRunStore } = await Promise.resolve().then(() => __importStar(require('@hazeljs/agent')));
358
+ const repo = opts.dir
359
+ ? createDurableRunStore(path.resolve(process.cwd(), opts.dir))
360
+ .runRepository
361
+ : new FileAgentRunRepository(path.resolve(process.cwd(), opts.store));
362
+ const filter = {};
363
+ if (opts.agent)
364
+ filter.agentName = opts.agent;
365
+ if (opts.status) {
366
+ const values = Object.values(AgentRunStatus);
367
+ if (!values.includes(opts.status)) {
368
+ throw new Error(`Unknown status "${opts.status}". Expected one of: ${values.join(', ')}`);
369
+ }
370
+ filter.status = opts.status;
371
+ }
372
+ const list = await repo.list(filter);
373
+ // eslint-disable-next-line no-console
374
+ console.log(JSON.stringify(list.map((r) => ({
375
+ id: r.id,
376
+ agentName: r.agentName,
377
+ status: r.status,
378
+ leaseOwner: r.leaseOwner,
379
+ updatedAt: r.updatedAt,
380
+ })), null, 2));
381
+ }
382
+ catch (e) {
383
+ // eslint-disable-next-line no-console
384
+ console.error(e);
385
+ process.exitCode = 1;
386
+ }
387
+ });
388
+ runs
389
+ .command('inspect')
390
+ .description('Show one AgentRun by id')
391
+ .argument('<runId>', 'AgentRun / execution id')
392
+ .option('--store <path>', 'Path to runs JSON', DEFAULT_RUN_STORE)
393
+ .option('--dir <path>', 'Durable store directory')
394
+ .action(async (runId, opts) => {
395
+ try {
396
+ const { FileAgentRunRepository, createDurableRunStore } = await Promise.resolve().then(() => __importStar(require('@hazeljs/agent')));
397
+ const repo = opts.dir
398
+ ? createDurableRunStore(path.resolve(process.cwd(), opts.dir)).runRepository
399
+ : new FileAgentRunRepository(path.resolve(process.cwd(), opts.store));
400
+ const run = await repo.get(runId);
401
+ if (!run) {
402
+ // eslint-disable-next-line no-console
403
+ console.error(`Run not found: ${runId}`);
404
+ process.exitCode = 1;
405
+ return;
406
+ }
407
+ // eslint-disable-next-line no-console
408
+ console.log(JSON.stringify(run, null, 2));
409
+ }
410
+ catch (e) {
411
+ // eslint-disable-next-line no-console
412
+ console.error(e);
413
+ process.exitCode = 1;
414
+ }
415
+ });
416
+ runs
417
+ .command('cancel')
418
+ .description('Mark an AgentRun CANCELLED in the file store (does not abort a live worker)')
419
+ .argument('<runId>', 'AgentRun / execution id')
420
+ .option('--store <path>', 'Path to runs JSON', DEFAULT_RUN_STORE)
421
+ .option('--dir <path>', 'Durable store directory')
422
+ .action(async (runId, opts) => {
423
+ try {
424
+ const { FileAgentRunRepository, AgentRunStatus, createDurableRunStore } = await Promise.resolve().then(() => __importStar(require('@hazeljs/agent')));
425
+ const repo = opts.dir
426
+ ? createDurableRunStore(path.resolve(process.cwd(), opts.dir)).runRepository
427
+ : new FileAgentRunRepository(path.resolve(process.cwd(), opts.store));
428
+ const run = await repo.updateStatus(runId, AgentRunStatus.CANCELLED, {
429
+ error: { message: 'Cancelled via hazel agent runs cancel' },
430
+ });
431
+ // eslint-disable-next-line no-console
432
+ console.log(JSON.stringify({ id: run.id, status: run.status }, null, 2));
433
+ }
434
+ catch (e) {
435
+ // eslint-disable-next-line no-console
436
+ console.error(e);
437
+ process.exitCode = 1;
438
+ }
439
+ });
440
+ const resumeAction = async (runId, opts) => {
441
+ try {
442
+ const approved = opts.approve !== false && !opts.reject;
443
+ const { createDurableRunStore, FileAgentRunRepository, FileHumanTaskService, FileCheckpointService, } = await Promise.resolve().then(() => __importStar(require('@hazeljs/agent')));
444
+ const storePath = path.resolve(process.cwd(), opts.store);
445
+ const isDir = opts.dir || storePath.endsWith('.hazel') || !storePath.endsWith('.json');
446
+ let humanTasks;
447
+ let runsRepo;
448
+ if (opts.dir) {
449
+ const store = createDurableRunStore(path.resolve(process.cwd(), opts.dir));
450
+ humanTasks = store.humanTaskService;
451
+ runsRepo = store.runRepository;
452
+ }
453
+ else if (isDir && !storePath.endsWith('.json')) {
454
+ const store = createDurableRunStore(storePath);
455
+ humanTasks = store.humanTaskService;
456
+ runsRepo = store.runRepository;
457
+ }
458
+ else {
459
+ const dir = path.dirname(storePath);
460
+ runsRepo = new FileAgentRunRepository(storePath);
461
+ humanTasks = new FileHumanTaskService(path.join(dir, 'human-tasks.json'));
462
+ void new FileCheckpointService(path.join(dir, 'checkpoints.json'));
463
+ }
464
+ const run = await runsRepo.get(runId);
465
+ if (!run) {
466
+ // eslint-disable-next-line no-console
467
+ console.error(`Run not found: ${runId}`);
468
+ process.exitCode = 1;
469
+ return;
470
+ }
471
+ const tasks = await humanTasks.listByRun(runId);
472
+ const pending = tasks.find((t) => t.status === 'pending');
473
+ if (pending) {
474
+ await humanTasks.resolve(pending.id, approved ? 'approved' : 'rejected', opts.by);
475
+ }
476
+ await runsRepo.updateStatus(runId, run.status, {
477
+ metadata: {
478
+ ...run.metadata,
479
+ cliDecision: {
480
+ approved,
481
+ by: opts.by,
482
+ at: new Date().toISOString(),
483
+ },
484
+ },
485
+ });
486
+ // eslint-disable-next-line no-console
487
+ console.log(JSON.stringify({
488
+ runId,
489
+ decision: approved ? 'approved' : 'rejected',
490
+ by: opts.by,
491
+ humanTaskId: pending?.id,
492
+ note: 'Human task updated. Call runtime.approveAndResume(runId, { approved, approvedBy }) in your app to continue the agent.',
493
+ }, null, 2));
494
+ }
495
+ catch (e) {
496
+ // eslint-disable-next-line no-console
497
+ console.error(e);
498
+ process.exitCode = 1;
499
+ }
500
+ };
501
+ runs
502
+ .command('resume')
503
+ .description('Record HITL approve/reject on file store (in-app approveAndResume still required to continue)')
504
+ .argument('<runId>', 'AgentRun / execution id')
505
+ .option('--store <path>', 'Path to runs.json or durable store directory', DEFAULT_RUN_STORE)
506
+ .option('--dir <path>', 'Durable store directory (runs + human-tasks + checkpoints)')
507
+ .option('--approve', 'Approve pending human task (default)')
508
+ .option('--reject', 'Reject pending human task')
509
+ .option('--by <who>', 'Approver identity', 'cli')
510
+ .action(resumeAction);
511
+ runs
512
+ .command('approve')
513
+ .description('Alias for runs resume --approve')
514
+ .argument('<runId>', 'AgentRun / execution id')
515
+ .option('--store <path>', 'Path to runs.json or durable store directory', DEFAULT_RUN_STORE)
516
+ .option('--dir <path>', 'Durable store directory')
517
+ .option('--by <who>', 'Approver identity', 'cli')
518
+ .action(async (runId, opts) => {
519
+ await resumeAction(runId, { ...opts, approve: true });
520
+ });
521
+ agent
522
+ .command('apply')
523
+ .description('Apply declarative Agent OS platform resources (Definition / Deployment / Run)')
524
+ .requiredOption('-f, --file <path>', 'Manifest file (JSON or YAML)')
525
+ .option('--store <path>', 'Platform resource store path', DEFAULT_PLATFORM_STORE)
526
+ .option('--registry <path>', 'Local package registry root for packageRef resolution')
527
+ .option('--project <path>', 'Project root for .hazel/agents packageRef resolution', '.')
528
+ .action(async (opts) => {
529
+ try {
530
+ const { createLocalPlatform, defaultRegistryRoot, parsePlatformDocuments } = await Promise.resolve().then(() => __importStar(require('@hazeljs/agent')));
531
+ const text = fs.readFileSync(path.resolve(opts.file), 'utf8');
532
+ const docs = parsePlatformDocuments(text);
533
+ const projectRoot = path.resolve(opts.project);
534
+ const platform = createLocalPlatform({
535
+ storePath: path.resolve(opts.store),
536
+ projectRoot,
537
+ registryRoot: opts.registry ? path.resolve(opts.registry) : defaultRegistryRoot(),
538
+ });
539
+ const results = [];
540
+ for (const doc of docs) {
541
+ const result = await platform.reconciler.applyResource(doc);
542
+ results.push({
543
+ kind: result.resource.kind,
544
+ name: result.resource.metadata.name,
545
+ namespace: result.resource.metadata.namespace ?? 'default',
546
+ generation: result.resource.metadata.generation,
547
+ ready: result.ready,
548
+ message: result.message,
549
+ conditions: result.resource.status?.conditions,
550
+ resolved: result.resource.status?.backend,
551
+ });
552
+ }
553
+ // eslint-disable-next-line no-console
554
+ console.log(JSON.stringify({ applied: results.length, results }, null, 2));
555
+ if (results.some((r) => !r.ready))
556
+ process.exitCode = 1;
557
+ }
558
+ catch (e) {
559
+ // eslint-disable-next-line no-console
560
+ console.error(e instanceof Error ? e.message : e);
561
+ process.exitCode = 1;
562
+ }
563
+ });
564
+ agent
565
+ .command('get')
566
+ .description('List or get platform resources from the local store')
567
+ .argument('[type]', 'Resource type (e.g. agentdefinitions, agentdeployments)')
568
+ .argument('[name]', 'Resource name')
569
+ .option('--store <path>', 'Platform resource store path', DEFAULT_PLATFORM_STORE)
570
+ .option('-n, --namespace <ns>', 'Namespace filter', 'default')
571
+ .option('--all-namespaces', 'List across namespaces')
572
+ .option('--project <path>', 'Project root (durable run correlation)', '.')
573
+ .option('--summary', 'Print secret-safe summaries instead of full resources')
574
+ .action(async (type, name, opts) => {
575
+ try {
576
+ const { createLocalPlatform, parseResourceTypeArg, summarizeResource } = await Promise.resolve().then(() => __importStar(require('@hazeljs/agent')));
577
+ const platform = createLocalPlatform({
578
+ storePath: path.resolve(opts.store),
579
+ projectRoot: path.resolve(opts.project),
580
+ actor: 'cli',
581
+ });
582
+ let kind;
583
+ let resourceName = name;
584
+ if (type) {
585
+ const parsed = parseResourceTypeArg(name ? `${type}/${name}` : type);
586
+ kind = parsed.kind;
587
+ resourceName = parsed.name ?? name;
588
+ if (parsed.namespace && !opts.allNamespaces) {
589
+ opts.namespace = parsed.namespace;
590
+ }
591
+ }
592
+ if (kind && resourceName) {
593
+ const found = platform.repo.get(kind, resourceName, opts.namespace);
594
+ if (!found) {
595
+ // eslint-disable-next-line no-console
596
+ console.error(`Not found: ${opts.namespace}/${kind}/${resourceName}`);
597
+ process.exitCode = 1;
598
+ return;
599
+ }
600
+ // eslint-disable-next-line no-console
601
+ console.log(JSON.stringify(opts.summary ? summarizeResource(found) : found, null, 2));
602
+ return;
603
+ }
604
+ const items = platform.repo.list({
605
+ kind,
606
+ namespace: opts.allNamespaces ? undefined : opts.namespace,
607
+ });
608
+ // eslint-disable-next-line no-console
609
+ console.log(JSON.stringify({
610
+ items: opts.summary
611
+ ? items.map(summarizeResource)
612
+ : items.map((r) => ({
613
+ kind: r.kind,
614
+ name: r.metadata.name,
615
+ namespace: r.metadata.namespace ?? 'default',
616
+ generation: r.metadata.generation,
617
+ ready: r.status?.conditions?.find((c) => c.type === 'Ready')?.status,
618
+ })),
619
+ }, null, 2));
620
+ }
621
+ catch (e) {
622
+ // eslint-disable-next-line no-console
623
+ console.error(e instanceof Error ? e.message : e);
624
+ process.exitCode = 1;
625
+ }
626
+ });
627
+ agent
628
+ .command('describe')
629
+ .description('Describe a platform resource (spec, status, conditions)')
630
+ .argument('<resource>', 'kind/name or namespace/kind/name')
631
+ .option('--store <path>', 'Platform resource store path', DEFAULT_PLATFORM_STORE)
632
+ .option('--project <path>', 'Project root (re-correlate durable runs on describe)', '.')
633
+ .option('--refresh', 'Re-reconcile before describe (refresh durable correlation)')
634
+ .action(async (resource, opts) => {
635
+ try {
636
+ const { createLocalPlatform, parseResourceTypeArg } = await Promise.resolve().then(() => __importStar(require('@hazeljs/agent')));
637
+ const parsed = parseResourceTypeArg(resource);
638
+ if (!parsed.name) {
639
+ throw new Error('describe requires kind/name (e.g. agentdeployment/support)');
640
+ }
641
+ const platform = createLocalPlatform({
642
+ storePath: path.resolve(opts.store),
643
+ projectRoot: path.resolve(opts.project),
644
+ });
645
+ const ns = parsed.namespace ?? 'default';
646
+ if (opts.refresh) {
647
+ if (parsed.kind === 'AgentDeployment') {
648
+ await platform.reconciler.reconcileDeployment(parsed.name, ns);
649
+ }
650
+ else if (parsed.kind === 'AgentRun') {
651
+ await platform.reconciler.reconcileRun(parsed.name, ns);
652
+ }
653
+ }
654
+ const found = platform.repo.get(parsed.kind, parsed.name, ns);
655
+ if (!found) {
656
+ // eslint-disable-next-line no-console
657
+ console.error(`Not found: ${ns}/${parsed.kind}/${parsed.name}`);
658
+ process.exitCode = 1;
659
+ return;
660
+ }
661
+ const { summarizeResource } = await Promise.resolve().then(() => __importStar(require('@hazeljs/agent')));
662
+ // eslint-disable-next-line no-console
663
+ console.log(JSON.stringify({
664
+ resource: found,
665
+ summary: summarizeResource(found),
666
+ }, null, 2));
667
+ }
668
+ catch (e) {
669
+ // eslint-disable-next-line no-console
670
+ console.error(e instanceof Error ? e.message : e);
671
+ process.exitCode = 1;
672
+ }
673
+ });
674
+ agent
675
+ .command('delete')
676
+ .description('Delete a platform resource (deployments clean up the local backend)')
677
+ .argument('<resource>', 'kind/name or namespace/kind/name')
678
+ .option('--store <path>', 'Platform resource store path', DEFAULT_PLATFORM_STORE)
679
+ .action(async (resource, opts) => {
680
+ try {
681
+ const { createLocalPlatform, parseResourceTypeArg } = await Promise.resolve().then(() => __importStar(require('@hazeljs/agent')));
682
+ const parsed = parseResourceTypeArg(resource);
683
+ if (!parsed.name) {
684
+ throw new Error('delete requires kind/name (e.g. agentdeployment/support)');
685
+ }
686
+ const platform = createLocalPlatform({
687
+ storePath: path.resolve(opts.store),
688
+ projectRoot: process.cwd(),
689
+ });
690
+ const result = await platform.reconciler.deleteResource({
691
+ kind: parsed.kind,
692
+ name: parsed.name,
693
+ namespace: parsed.namespace ?? 'default',
694
+ });
695
+ // FileResourceRepository auto-persists; keep save() for compatibility
696
+ platform.save();
697
+ // eslint-disable-next-line no-console
698
+ console.log(JSON.stringify({
699
+ deleted: result.deleted,
700
+ kind: parsed.kind,
701
+ name: parsed.name,
702
+ namespace: parsed.namespace ?? 'default',
703
+ backendMessage: result.backendMessage,
704
+ }, null, 2));
705
+ if (!result.deleted)
706
+ process.exitCode = 1;
707
+ }
708
+ catch (e) {
709
+ // eslint-disable-next-line no-console
710
+ console.error(e instanceof Error ? e.message : e);
711
+ process.exitCode = 1;
712
+ }
713
+ });
714
+ agent
715
+ .command('reconcile')
716
+ .description('Reconcile all AgentDeployments / AgentRuns in the local platform store (control-plane loop)')
717
+ .option('--store <path>', 'Platform resource store path', DEFAULT_PLATFORM_STORE)
718
+ .option('--project <path>', 'Project root for packageRef / durable run correlation', '.')
719
+ .option('--registry <path>', 'Local package registry root for packageRef resolution')
720
+ .option('-n, --namespace <ns>', 'Limit to one namespace')
721
+ .option('--watch', 'Keep reconciling on an interval until interrupted')
722
+ .option('--interval <seconds>', 'Watch interval in seconds (default 5)', '5')
723
+ .action(async (opts) => {
724
+ try {
725
+ const { createLocalPlatform, defaultRegistryRoot, watchLocalPlatform } = await Promise.resolve().then(() => __importStar(require('@hazeljs/agent')));
726
+ const projectRoot = path.resolve(opts.project);
727
+ const platform = createLocalPlatform({
728
+ storePath: path.resolve(opts.store),
729
+ projectRoot,
730
+ registryRoot: opts.registry ? path.resolve(opts.registry) : defaultRegistryRoot(),
731
+ actor: 'cli',
732
+ });
733
+ const namespace = opts.namespace;
734
+ const printTick = (result, tick) => {
735
+ // eslint-disable-next-line no-console
736
+ console.log(JSON.stringify({
737
+ tick: tick ?? 1,
738
+ ready: result.ready,
739
+ notReady: result.notReady,
740
+ errors: result.errors,
741
+ items: result.results.map((r) => ({
742
+ kind: r.resource.kind,
743
+ name: r.resource.metadata.name,
744
+ namespace: r.resource.metadata.namespace ?? 'default',
745
+ ready: r.ready,
746
+ message: r.message,
747
+ })),
748
+ }, null, 2));
749
+ };
750
+ if (!opts.watch) {
751
+ const result = await platform.reconcileAll({ namespace });
752
+ printTick(result);
753
+ if (result.notReady > 0 || result.errors.length > 0)
754
+ process.exitCode = 1;
755
+ return;
756
+ }
757
+ const seconds = Math.max(1, Number(opts.interval) || 5);
758
+ const ac = new AbortController();
759
+ const onSig = () => ac.abort();
760
+ process.on('SIGINT', onSig);
761
+ process.on('SIGTERM', onSig);
762
+ // eslint-disable-next-line no-console
763
+ console.error(`Watching every ${seconds}s (Ctrl+C to stop)…`);
764
+ await watchLocalPlatform(platform, {
765
+ namespace,
766
+ intervalMs: seconds * 1000,
767
+ signal: ac.signal,
768
+ onTick: async (result, tick) => {
769
+ printTick(result, tick);
770
+ },
771
+ });
772
+ process.off('SIGINT', onSig);
773
+ process.off('SIGTERM', onSig);
774
+ }
775
+ catch (e) {
776
+ // eslint-disable-next-line no-console
777
+ console.error(e instanceof Error ? e.message : e);
778
+ process.exitCode = 1;
779
+ }
780
+ });
781
+ agent
782
+ .command('events')
783
+ .description('List platform control-plane events (audit log; no secrets)')
784
+ .option('--store <path>', 'Platform resource store path', DEFAULT_PLATFORM_STORE)
785
+ .option('--events <path>', 'Events JSONL path (default: beside store)')
786
+ .option('--type <type>', 'Filter by event type')
787
+ .option('--kind <kind>', 'Filter by resource kind')
788
+ .option('--limit <n>', 'Max events (most recent)', '50')
789
+ .action(async (opts) => {
790
+ try {
791
+ const { createLocalPlatform } = await Promise.resolve().then(() => __importStar(require('@hazeljs/agent')));
792
+ const storePath = path.resolve(opts.store);
793
+ const platform = createLocalPlatform({
794
+ storePath,
795
+ eventsPath: opts.events
796
+ ? path.resolve(opts.events)
797
+ : path.join(path.dirname(storePath), 'events.jsonl'),
798
+ actor: 'cli',
799
+ });
800
+ const items = platform.events.list({
801
+ type: opts.type,
802
+ kind: opts.kind,
803
+ limit: Number(opts.limit) || 50,
804
+ });
805
+ // eslint-disable-next-line no-console
806
+ console.log(JSON.stringify({ items }, null, 2));
807
+ }
808
+ catch (e) {
809
+ // eslint-disable-next-line no-console
810
+ console.error(e instanceof Error ? e.message : e);
811
+ process.exitCode = 1;
812
+ }
813
+ });
94
814
  }