@hazeljs/cli 2.0.5 → 2.0.7
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.
- package/README.md +46 -15
- package/cli-manifest.json +7 -1
- package/dist/commands/agent-templates.js +255 -1
- package/dist/commands/agent.test.js +4 -0
- package/dist/commands/organism.d.ts +6 -0
- package/dist/commands/organism.js +214 -0
- package/dist/index.js +2 -0
- package/dist/utils/packages-registry.js +67 -0
- package/package.json +11 -11
package/README.md
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
# @hazeljs/cli
|
|
2
2
|
|
|
3
|
-
**
|
|
3
|
+
**Scaffold Agent OS apps and HazelJS backends.**
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
DNA / Store / Skillgate / HITL live in [Meridian](https://github.com/hazel-js/hazeljs-meridian-ops) and `hazel agent new`. HTTP + HCEL templates (`--template=ai-native`) are a secondary framework path — not the Agent OS flagship.
|
|
6
6
|
|
|
7
7
|
[](https://www.npmjs.com/package/@hazeljs/cli)
|
|
8
8
|
[](https://www.npmjs.com/package/@hazeljs/cli)
|
|
@@ -10,13 +10,13 @@ Generate controllers, services, modules, AI agents, RAG systems — or create a
|
|
|
10
10
|
|
|
11
11
|
## Features
|
|
12
12
|
|
|
13
|
-
-
|
|
14
|
-
-
|
|
15
|
-
-
|
|
16
|
-
-
|
|
17
|
-
-
|
|
18
|
-
-
|
|
19
|
-
-
|
|
13
|
+
- 🧬 **Agent OS** — `hazel agent new` (bare / agent-os / skillgate), Store, DNA install, runs, apply
|
|
14
|
+
- 🚪 **Skillgate / Gatekeeper** — `hazel skillgate from-openapi`, `hazel gatekeeper validate|simulate|explain`
|
|
15
|
+
- 🧪 **Eval / benchmark** — `hazel eval`, `hazel benchmark`
|
|
16
|
+
- 🚀 **App scaffolding** — `hazel new`, `hazel g app` (HTTP skeleton or `--template=ai-native`)
|
|
17
|
+
- 🎨 **Code generation** — controllers, services, agents, RAG, 20+ component types
|
|
18
|
+
- 📦 **Package management** — `hazel add` with optional setup files
|
|
19
|
+
- Generated apps do **not** list or import `reflect-metadata` — `@hazeljs/core` loads it
|
|
20
20
|
|
|
21
21
|
## Installation
|
|
22
22
|
|
|
@@ -34,25 +34,45 @@ npm install --save-dev @hazeljs/cli
|
|
|
34
34
|
|
|
35
35
|
## Commands
|
|
36
36
|
|
|
37
|
-
###
|
|
37
|
+
### Agent OS (start here)
|
|
38
38
|
|
|
39
|
-
**
|
|
39
|
+
**Flagship teaching path** — clone [Meridian](https://github.com/hazel-js/hazeljs-meridian-ops) (`store:sync` → `platform:sync` → `dev`). The CLI is not a substitute for that app.
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
# Smaller Agent OS / DNA scaffold (not Meridian)
|
|
43
|
+
hazel agent new my-desk --template=agent-os
|
|
44
|
+
# templates: bare | agent-os | skillgate
|
|
45
|
+
# agent-os includes: DNA + real @Tool app + ops stack (self-healing + predictive-scaling) + K8s manifests
|
|
46
|
+
cd my-desk && npm install && npm run dev
|
|
47
|
+
|
|
48
|
+
hazel agent install ./packages/support.dna.json
|
|
49
|
+
hazel agent runs list
|
|
50
|
+
hazel store publish | install | list
|
|
51
|
+
hazel skillgate from-openapi ./openapi.yaml
|
|
52
|
+
hazel gatekeeper validate
|
|
53
|
+
hazel benchmark
|
|
54
|
+
hazel eval
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
`hazel agent run` is a DNA smoke with stubs. Real tools and HITL = your app’s `runtime.execute` / Meridian `/api/chat`. `hazel agent apply` declares desired state — it does not restart Node.
|
|
58
|
+
|
|
59
|
+
### Create a framework application (secondary)
|
|
60
|
+
|
|
61
|
+
HTTP + HCEL / RAG scaffold — **not** the Agent OS flagship:
|
|
40
62
|
|
|
41
63
|
```bash
|
|
42
64
|
hazel g app my-ai-app --template=ai-native
|
|
43
65
|
cd my-ai-app && npm install && cp .env.example .env && docker-compose up -d && npm run dev
|
|
44
66
|
```
|
|
45
67
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
**Skeleton app (quick start):**
|
|
68
|
+
Skeleton API:
|
|
49
69
|
|
|
50
70
|
```bash
|
|
51
71
|
hazel g app my-app
|
|
52
72
|
cd my-app && npm install && npm run dev
|
|
53
73
|
```
|
|
54
74
|
|
|
55
|
-
|
|
75
|
+
Interactive package selection:
|
|
56
76
|
|
|
57
77
|
```bash
|
|
58
78
|
hazel new my-app -i
|
|
@@ -218,6 +238,17 @@ hazel g serverless handler --platform lambda
|
|
|
218
238
|
## Quick Reference
|
|
219
239
|
|
|
220
240
|
```bash
|
|
241
|
+
# Agent OS
|
|
242
|
+
hazel agent new <name> [--template agent-os|bare|skillgate]
|
|
243
|
+
hazel agent install <dna>
|
|
244
|
+
hazel agent run | doctor | logs
|
|
245
|
+
hazel agent runs list | inspect | cancel | resume | approve
|
|
246
|
+
hazel store publish | install | list
|
|
247
|
+
hazel skillgate from-openapi <spec>
|
|
248
|
+
hazel gatekeeper validate | simulate | explain
|
|
249
|
+
hazel benchmark
|
|
250
|
+
hazel eval
|
|
251
|
+
|
|
221
252
|
# Project Management
|
|
222
253
|
hazel new <name> [-i] # Create new project
|
|
223
254
|
hazel info # Show project info
|
package/cli-manifest.json
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
"description": "Machine-readable manifest of all CLI commands and options for LLM agent tool-use",
|
|
5
5
|
"cli": {
|
|
6
6
|
"name": "hazel",
|
|
7
|
-
"version": "2.0.
|
|
7
|
+
"version": "2.0.7",
|
|
8
8
|
"description": "CLI for generating HazelJS components and applications"
|
|
9
9
|
},
|
|
10
10
|
"commands": [
|
|
@@ -746,6 +746,12 @@
|
|
|
746
746
|
"label": "Configuration (@hazeljs/config)",
|
|
747
747
|
"description": "Environment-based configuration management"
|
|
748
748
|
},
|
|
749
|
+
{
|
|
750
|
+
"shortName": "config-server",
|
|
751
|
+
"npm": "@hazeljs/config-server",
|
|
752
|
+
"label": "Git config server (@hazeljs/config-server)",
|
|
753
|
+
"description": "Git-backed distributed configuration (Spring Cloud Config style)"
|
|
754
|
+
},
|
|
749
755
|
{
|
|
750
756
|
"shortName": "cron",
|
|
751
757
|
"npm": "@hazeljs/cron",
|
|
@@ -54,7 +54,7 @@ exports.AGENT_TEMPLATES = [
|
|
|
54
54
|
{
|
|
55
55
|
id: 'agent-os',
|
|
56
56
|
label: 'Agent OS mini-app',
|
|
57
|
-
description: 'DNA + real @Agent/@Tool
|
|
57
|
+
description: 'DNA + real @Agent/@Tool app + ops stack (self-healing + predictive scaling) + K8s manifests',
|
|
58
58
|
},
|
|
59
59
|
{
|
|
60
60
|
id: 'skillgate',
|
|
@@ -156,6 +156,235 @@ Wire real \`@Tool\` / Skillgate handlers in your Hazel app for production behavi
|
|
|
156
156
|
`,
|
|
157
157
|
};
|
|
158
158
|
}
|
|
159
|
+
function agentOsOpsFiles(npm, _projectName) {
|
|
160
|
+
const deployment = npm;
|
|
161
|
+
const hpaName = `${npm}-hpa`;
|
|
162
|
+
return {
|
|
163
|
+
'.env.example': `# Agent runtime
|
|
164
|
+
HITL=0
|
|
165
|
+
|
|
166
|
+
# Ops stack (proactive scaling + reactive self-healing)
|
|
167
|
+
ENABLE_OPS_STACK=0
|
|
168
|
+
K8S_DEPLOYMENT=${deployment}
|
|
169
|
+
K8S_NAMESPACE=default
|
|
170
|
+
K8S_HPA_NAME=${hpaName}
|
|
171
|
+
PROMETHEUS_URL=http://prometheus.monitoring.svc:9090
|
|
172
|
+
SLACK_BOT_TOKEN=
|
|
173
|
+
PAGERDUTY_ROUTING_KEY=
|
|
174
|
+
`,
|
|
175
|
+
'src/ops/stack.ts': `import { createOperationsStack } from '@hazeljs/predictive-scaling';
|
|
176
|
+
import {
|
|
177
|
+
FetchKubernetesRestartClient,
|
|
178
|
+
FetchKubernetesScalingClient,
|
|
179
|
+
createSlackHealingNotifier,
|
|
180
|
+
createHealingNotifierChain,
|
|
181
|
+
InMemoryKubernetesRestartClient,
|
|
182
|
+
InMemoryKubernetesScalingClient,
|
|
183
|
+
} from '@hazeljs/self-healing';
|
|
184
|
+
|
|
185
|
+
export function createAgentOpsStack(appName: string) {
|
|
186
|
+
const deployment = process.env.K8S_DEPLOYMENT ?? appName;
|
|
187
|
+
const namespace = process.env.K8S_NAMESPACE ?? 'default';
|
|
188
|
+
const hpaName = process.env.K8S_HPA_NAME ?? \`\${deployment}-hpa\`;
|
|
189
|
+
const inCluster = Boolean(process.env.KUBERNETES_SERVICE_HOST);
|
|
190
|
+
|
|
191
|
+
const scalingClient = inCluster
|
|
192
|
+
? new FetchKubernetesScalingClient()
|
|
193
|
+
: new InMemoryKubernetesScalingClient();
|
|
194
|
+
const restartClient = inCluster
|
|
195
|
+
? new FetchKubernetesRestartClient()
|
|
196
|
+
: new InMemoryKubernetesRestartClient();
|
|
197
|
+
|
|
198
|
+
const notifications = [];
|
|
199
|
+
if (process.env.SLACK_BOT_TOKEN) {
|
|
200
|
+
notifications.push(
|
|
201
|
+
createSlackHealingNotifier({
|
|
202
|
+
token: process.env.SLACK_BOT_TOKEN,
|
|
203
|
+
channel: process.env.SLACK_ALERT_CHANNEL ?? '#ops-alerts',
|
|
204
|
+
})
|
|
205
|
+
);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
return createOperationsStack({
|
|
209
|
+
healing: {
|
|
210
|
+
strategies: ['config-rollback', 'hpa-boost', 'pod-restart', 'safe-mode'],
|
|
211
|
+
aiDiagnostics: true,
|
|
212
|
+
drain: { timeoutMs: 30_000 },
|
|
213
|
+
notifyOn: ['critical-healing', 'healing-failed', 'hpa-boost', 'pod-restart'],
|
|
214
|
+
notifications: notifications.length ? createHealingNotifierChain(notifications) : undefined,
|
|
215
|
+
kubernetes: {
|
|
216
|
+
deployment,
|
|
217
|
+
namespace,
|
|
218
|
+
client: restartClient,
|
|
219
|
+
drainBeforeRestart: true,
|
|
220
|
+
hpa: {
|
|
221
|
+
name: hpaName,
|
|
222
|
+
namespace,
|
|
223
|
+
client: scalingClient,
|
|
224
|
+
boostMinReplicas: Number(process.env.HPA_BOOST_MIN ?? 4),
|
|
225
|
+
restoreAfterMs: Number(process.env.HPA_RESTORE_MS ?? 300_000),
|
|
226
|
+
},
|
|
227
|
+
},
|
|
228
|
+
performance: {
|
|
229
|
+
enabled: true,
|
|
230
|
+
autoScaleOnDegradation: true,
|
|
231
|
+
thresholds: { criticalLatencyMs: 2000, sampleSize: 20 },
|
|
232
|
+
},
|
|
233
|
+
},
|
|
234
|
+
scaling: {
|
|
235
|
+
model: 'time-series-forecast',
|
|
236
|
+
metrics: ['requests', 'latency', 'cpu'],
|
|
237
|
+
horizon: '30m',
|
|
238
|
+
confidence: 0.85,
|
|
239
|
+
capacityPerReplica: Number(process.env.CAPACITY_PER_REPLICA ?? 120),
|
|
240
|
+
hpa: {
|
|
241
|
+
name: hpaName,
|
|
242
|
+
namespace,
|
|
243
|
+
client: scalingClient,
|
|
244
|
+
maxReplicas: Number(process.env.HPA_MAX_REPLICAS ?? 50),
|
|
245
|
+
},
|
|
246
|
+
},
|
|
247
|
+
prometheus: process.env.PROMETHEUS_URL
|
|
248
|
+
? {
|
|
249
|
+
baseUrl: process.env.PROMETHEUS_URL,
|
|
250
|
+
pollIntervalMs: 60_000,
|
|
251
|
+
queries: {
|
|
252
|
+
requests: \`sum(rate(http_requests_total{service="${deployment}"}[5m]))\`,
|
|
253
|
+
latency:
|
|
254
|
+
\`histogram_quantile(0.95, sum(rate(http_request_duration_seconds_bucket{service="${deployment}"}[5m])) by (le))\`,
|
|
255
|
+
cpu: \`avg(rate(container_cpu_usage_seconds_total{pod=~"${deployment}-.*"}[5m]))\`,
|
|
256
|
+
},
|
|
257
|
+
}
|
|
258
|
+
: undefined,
|
|
259
|
+
});
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
let opsStack: ReturnType<typeof createAgentOpsStack> | null = null;
|
|
263
|
+
|
|
264
|
+
export function startAgentOpsStack(appName: string) {
|
|
265
|
+
if (opsStack) return opsStack;
|
|
266
|
+
opsStack = createAgentOpsStack(appName);
|
|
267
|
+
opsStack.start();
|
|
268
|
+
return opsStack;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
export function stopAgentOpsStack() {
|
|
272
|
+
opsStack?.stop();
|
|
273
|
+
opsStack = null;
|
|
274
|
+
}
|
|
275
|
+
`,
|
|
276
|
+
'deploy/kubernetes/deployment.yaml': `apiVersion: apps/v1
|
|
277
|
+
kind: Deployment
|
|
278
|
+
metadata:
|
|
279
|
+
name: ${deployment}
|
|
280
|
+
labels:
|
|
281
|
+
app: ${deployment}
|
|
282
|
+
spec:
|
|
283
|
+
replicas: 2
|
|
284
|
+
selector:
|
|
285
|
+
matchLabels:
|
|
286
|
+
app: ${deployment}
|
|
287
|
+
template:
|
|
288
|
+
metadata:
|
|
289
|
+
labels:
|
|
290
|
+
app: ${deployment}
|
|
291
|
+
spec:
|
|
292
|
+
serviceAccountName: ${deployment}
|
|
293
|
+
containers:
|
|
294
|
+
- name: agent
|
|
295
|
+
image: ${deployment}:latest
|
|
296
|
+
env:
|
|
297
|
+
- name: ENABLE_OPS_STACK
|
|
298
|
+
value: "1"
|
|
299
|
+
- name: K8S_DEPLOYMENT
|
|
300
|
+
value: ${deployment}
|
|
301
|
+
- name: K8S_HPA_NAME
|
|
302
|
+
value: ${hpaName}
|
|
303
|
+
- name: PROMETHEUS_URL
|
|
304
|
+
value: http://prometheus.monitoring.svc:9090
|
|
305
|
+
resources:
|
|
306
|
+
requests:
|
|
307
|
+
cpu: 250m
|
|
308
|
+
memory: 512Mi
|
|
309
|
+
limits:
|
|
310
|
+
cpu: "1"
|
|
311
|
+
memory: 1Gi
|
|
312
|
+
`,
|
|
313
|
+
'deploy/kubernetes/hpa.yaml': `apiVersion: autoscaling/v2
|
|
314
|
+
kind: HorizontalPodAutoscaler
|
|
315
|
+
metadata:
|
|
316
|
+
name: ${hpaName}
|
|
317
|
+
spec:
|
|
318
|
+
scaleTargetRef:
|
|
319
|
+
apiVersion: apps/v1
|
|
320
|
+
kind: Deployment
|
|
321
|
+
name: ${deployment}
|
|
322
|
+
minReplicas: 2
|
|
323
|
+
maxReplicas: 50
|
|
324
|
+
metrics:
|
|
325
|
+
- type: Resource
|
|
326
|
+
resource:
|
|
327
|
+
name: cpu
|
|
328
|
+
target:
|
|
329
|
+
type: Utilization
|
|
330
|
+
averageUtilization: 70
|
|
331
|
+
- type: Resource
|
|
332
|
+
resource:
|
|
333
|
+
name: memory
|
|
334
|
+
target:
|
|
335
|
+
type: Utilization
|
|
336
|
+
averageUtilization: 80
|
|
337
|
+
`,
|
|
338
|
+
'deploy/kubernetes/service.yaml': `apiVersion: v1
|
|
339
|
+
kind: Service
|
|
340
|
+
metadata:
|
|
341
|
+
name: ${deployment}
|
|
342
|
+
spec:
|
|
343
|
+
selector:
|
|
344
|
+
app: ${deployment}
|
|
345
|
+
ports:
|
|
346
|
+
- port: 80
|
|
347
|
+
targetPort: 3000
|
|
348
|
+
`,
|
|
349
|
+
'deploy/kubernetes/serviceaccount.yaml': `apiVersion: v1
|
|
350
|
+
kind: ServiceAccount
|
|
351
|
+
metadata:
|
|
352
|
+
name: ${deployment}
|
|
353
|
+
---
|
|
354
|
+
apiVersion: rbac.authorization.k8s.io/v1
|
|
355
|
+
kind: Role
|
|
356
|
+
metadata:
|
|
357
|
+
name: ${deployment}-ops
|
|
358
|
+
rules:
|
|
359
|
+
- apiGroups: ["autoscaling"]
|
|
360
|
+
resources: ["horizontalpodautoscalers"]
|
|
361
|
+
verbs: ["get", "patch", "update"]
|
|
362
|
+
- apiGroups: ["apps"]
|
|
363
|
+
resources: ["deployments"]
|
|
364
|
+
verbs: ["get", "patch", "update"]
|
|
365
|
+
---
|
|
366
|
+
apiVersion: rbac.authorization.k8s.io/v1
|
|
367
|
+
kind: RoleBinding
|
|
368
|
+
metadata:
|
|
369
|
+
name: ${deployment}-ops
|
|
370
|
+
subjects:
|
|
371
|
+
- kind: ServiceAccount
|
|
372
|
+
name: ${deployment}
|
|
373
|
+
roleRef:
|
|
374
|
+
apiGroup: rbac.authorization.k8s.io
|
|
375
|
+
kind: Role
|
|
376
|
+
name: ${deployment}-ops
|
|
377
|
+
`,
|
|
378
|
+
'deploy/kubernetes/kustomization.yaml': `apiVersion: kustomize.config.k8s.io/v1beta1
|
|
379
|
+
kind: Kustomization
|
|
380
|
+
resources:
|
|
381
|
+
- serviceaccount.yaml
|
|
382
|
+
- deployment.yaml
|
|
383
|
+
- service.yaml
|
|
384
|
+
- hpa.yaml
|
|
385
|
+
`,
|
|
386
|
+
};
|
|
387
|
+
}
|
|
159
388
|
function agentOsFiles(projectName) {
|
|
160
389
|
const dnaName = agentDnaName(projectName);
|
|
161
390
|
const npm = sanitizeNpmName(projectName);
|
|
@@ -197,6 +426,8 @@ Use lookupOrder for facts. Call processRefund only after lookup. Be concise.`,
|
|
|
197
426
|
},
|
|
198
427
|
dependencies: {
|
|
199
428
|
'@hazeljs/agent': '^1.0.6',
|
|
429
|
+
'@hazeljs/self-healing': '^2.0.5',
|
|
430
|
+
'@hazeljs/predictive-scaling': '^2.0.5',
|
|
200
431
|
},
|
|
201
432
|
devDependencies: {
|
|
202
433
|
'@types/node': '^20.19.39',
|
|
@@ -279,9 +510,16 @@ import {
|
|
|
279
510
|
type AgentEvent,
|
|
280
511
|
} from '@hazeljs/agent';
|
|
281
512
|
import { SupportAgent } from './support.agent';
|
|
513
|
+
import { startAgentOpsStack, stopAgentOpsStack } from './ops/stack';
|
|
282
514
|
|
|
283
515
|
async function main() {
|
|
284
516
|
const input = process.argv.slice(2).join(' ') || 'Where is ORD-1001?';
|
|
517
|
+
|
|
518
|
+
if (process.env.ENABLE_OPS_STACK === '1') {
|
|
519
|
+
startAgentOpsStack('${npm}');
|
|
520
|
+
process.on('SIGINT', () => stopAgentOpsStack());
|
|
521
|
+
process.on('SIGTERM', () => stopAgentOpsStack());
|
|
522
|
+
}
|
|
285
523
|
// Demo uses mock LLM. Wire OpenAI (or another provider) in production apps.
|
|
286
524
|
const llm = createMockLlmProvider(
|
|
287
525
|
'Used real @Tool handlers on AgentRuntime. (Replace createMockLlmProvider with your LLM.)'
|
|
@@ -326,6 +564,8 @@ main().catch((e) => {
|
|
|
326
564
|
| \`dna/agent.marketplace.json\` | DNA / marketplace package (OpenAPI-for-agents) |
|
|
327
565
|
| \`src/support.agent.ts\` | Real tool handlers |
|
|
328
566
|
| \`src/main.ts\` | AgentRuntime bootstrap + optional DNA overlay |
|
|
567
|
+
| \`src/ops/stack.ts\` | Predictive scaling + self-healing ops stack |
|
|
568
|
+
| \`deploy/kubernetes/\` | K8s Deployment, HPA, RBAC for in-cluster ops |
|
|
329
569
|
|
|
330
570
|
## Quick start
|
|
331
571
|
|
|
@@ -343,7 +583,21 @@ npm run store:install
|
|
|
343
583
|
\`\`\`
|
|
344
584
|
|
|
345
585
|
Set \`HITL=1\` to pause on \`processRefund\` approvals.
|
|
586
|
+
|
|
587
|
+
## Production ops (predictive + self-healing)
|
|
588
|
+
|
|
589
|
+
\`\`\`bash
|
|
590
|
+
cp .env.example .env
|
|
591
|
+
ENABLE_OPS_STACK=1 npm run dev
|
|
592
|
+
|
|
593
|
+
# Deploy to Kubernetes (requires kubectl + kustomize)
|
|
594
|
+
kubectl apply -k deploy/kubernetes
|
|
595
|
+
\`\`\`
|
|
596
|
+
|
|
597
|
+
- **Proactive:** \`@hazeljs/predictive-scaling\` forecasts traffic and adjusts HPA min replicas
|
|
598
|
+
- **Reactive:** \`@hazeljs/self-healing\` heals failures, drains pods, opens Slack/Jira alerts
|
|
346
599
|
`,
|
|
600
|
+
...agentOsOpsFiles(npm, projectName),
|
|
347
601
|
};
|
|
348
602
|
}
|
|
349
603
|
function skillgateFiles(projectName) {
|
|
@@ -88,8 +88,12 @@ describe('agent templates scaffold', () => {
|
|
|
88
88
|
(0, agent_templates_1.scaffoldAgentProject)({ name: 'os-demo', destDir: dest, template: 'agent-os' });
|
|
89
89
|
expect(fs.existsSync(path.join(dest, 'src/support.agent.ts'))).toBe(true);
|
|
90
90
|
expect(fs.existsSync(path.join(dest, 'src/main.ts'))).toBe(true);
|
|
91
|
+
expect(fs.existsSync(path.join(dest, 'src/ops/stack.ts'))).toBe(true);
|
|
92
|
+
expect(fs.existsSync(path.join(dest, 'deploy/kubernetes/hpa.yaml'))).toBe(true);
|
|
91
93
|
expect(fs.existsSync(path.join(dest, 'dna/agent.marketplace.json'))).toBe(true);
|
|
92
94
|
const pkg = JSON.parse(fs.readFileSync(path.join(dest, 'package.json'), 'utf8'));
|
|
95
|
+
expect(pkg.dependencies['@hazeljs/self-healing']).toBe('^2.0.5');
|
|
96
|
+
expect(pkg.dependencies['@hazeljs/predictive-scaling']).toBe('^2.0.5');
|
|
93
97
|
expect(pkg.dependencies['reflect-metadata']).toBeUndefined();
|
|
94
98
|
const main = fs.readFileSync(path.join(dest, 'src/main.ts'), 'utf8');
|
|
95
99
|
const agent = fs.readFileSync(path.join(dest, 'src/support.agent.ts'), 'utf8');
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
/**
|
|
3
|
+
* `hazel organism` — inspect / control organism snapshots (Phase 1).
|
|
4
|
+
* Live runtime control uses the programmatic API; CLI reads/writes local snapshots.
|
|
5
|
+
*/
|
|
6
|
+
export declare function registerOrganismCommand(program: Command): void;
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.registerOrganismCommand = registerOrganismCommand;
|
|
37
|
+
const fs = __importStar(require("fs"));
|
|
38
|
+
const path = __importStar(require("path"));
|
|
39
|
+
const DEFAULT_STORE = path.join('.hazel', 'organisms.json');
|
|
40
|
+
function loadStore(storePath) {
|
|
41
|
+
const resolved = path.resolve(storePath);
|
|
42
|
+
if (!fs.existsSync(resolved))
|
|
43
|
+
return {};
|
|
44
|
+
try {
|
|
45
|
+
return JSON.parse(fs.readFileSync(resolved, 'utf8'));
|
|
46
|
+
}
|
|
47
|
+
catch {
|
|
48
|
+
return {};
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
function saveStore(storePath, data) {
|
|
52
|
+
const resolved = path.resolve(storePath);
|
|
53
|
+
fs.mkdirSync(path.dirname(resolved), { recursive: true });
|
|
54
|
+
fs.writeFileSync(resolved, JSON.stringify(data, null, 2));
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* `hazel organism` — inspect / control organism snapshots (Phase 1).
|
|
58
|
+
* Live runtime control uses the programmatic API; CLI reads/writes local snapshots.
|
|
59
|
+
*/
|
|
60
|
+
function registerOrganismCommand(program) {
|
|
61
|
+
const org = program.command('organism').description('Agentic Organism Runtime commands');
|
|
62
|
+
org
|
|
63
|
+
.command('list')
|
|
64
|
+
.description('List known organisms from the local snapshot store')
|
|
65
|
+
.option('--store <path>', 'Snapshot store path', DEFAULT_STORE)
|
|
66
|
+
.option('--json', 'Print JSON')
|
|
67
|
+
.action((opts) => {
|
|
68
|
+
const data = loadStore(opts.store);
|
|
69
|
+
const rows = Object.values(data);
|
|
70
|
+
if (opts.json) {
|
|
71
|
+
console.log(JSON.stringify({ organisms: rows }, null, 2));
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
if (!rows.length) {
|
|
75
|
+
console.log('No organisms found. Use createOrganism() then persist a snapshot.');
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
for (const r of rows) {
|
|
79
|
+
console.log(`${r.id}\t${r.status}\t${r.mission.objective}`);
|
|
80
|
+
}
|
|
81
|
+
});
|
|
82
|
+
org
|
|
83
|
+
.command('inspect <id>')
|
|
84
|
+
.description('Inspect an organism snapshot')
|
|
85
|
+
.option('--store <path>', 'Snapshot store path', DEFAULT_STORE)
|
|
86
|
+
.option('--json', 'Print JSON')
|
|
87
|
+
.action((id, opts) => {
|
|
88
|
+
const data = loadStore(opts.store);
|
|
89
|
+
const snap = data[id];
|
|
90
|
+
if (!snap) {
|
|
91
|
+
console.error(`Organism not found: ${id}`);
|
|
92
|
+
process.exitCode = 1;
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
console.log(opts.json ? JSON.stringify(snap, null, 2) : formatInspect(snap));
|
|
96
|
+
});
|
|
97
|
+
org
|
|
98
|
+
.command('agents <id>')
|
|
99
|
+
.description('List agents for an organism')
|
|
100
|
+
.option('--store <path>', 'Snapshot store path', DEFAULT_STORE)
|
|
101
|
+
.option('--json', 'Print JSON')
|
|
102
|
+
.action((id, opts) => {
|
|
103
|
+
const snap = loadStore(opts.store)[id];
|
|
104
|
+
if (!snap) {
|
|
105
|
+
console.error(`Organism not found: ${id}`);
|
|
106
|
+
process.exitCode = 1;
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
if (opts.json) {
|
|
110
|
+
console.log(JSON.stringify({ agents: snap.agents }, null, 2));
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
for (const a of snap.agents) {
|
|
114
|
+
console.log(`${a.id}\t${a.status}\tgen=${a.generation}\t${a.name ?? ''}`);
|
|
115
|
+
}
|
|
116
|
+
});
|
|
117
|
+
org
|
|
118
|
+
.command('genealogy <id>')
|
|
119
|
+
.description('Show agent genealogy')
|
|
120
|
+
.option('--store <path>', 'Snapshot store path', DEFAULT_STORE)
|
|
121
|
+
.option('--json', 'Print JSON')
|
|
122
|
+
.action((id, opts) => {
|
|
123
|
+
const snap = loadStore(opts.store)[id];
|
|
124
|
+
if (!snap) {
|
|
125
|
+
console.error(`Organism not found: ${id}`);
|
|
126
|
+
process.exitCode = 1;
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
console.log(opts.json
|
|
130
|
+
? JSON.stringify(snap.genealogy ?? [], null, 2)
|
|
131
|
+
: JSON.stringify(snap.genealogy ?? [], null, 2));
|
|
132
|
+
});
|
|
133
|
+
org
|
|
134
|
+
.command('resources <id>')
|
|
135
|
+
.description('Show organism resource pool')
|
|
136
|
+
.option('--store <path>', 'Snapshot store path', DEFAULT_STORE)
|
|
137
|
+
.option('--json', 'Print JSON')
|
|
138
|
+
.action((id, opts) => {
|
|
139
|
+
const snap = loadStore(opts.store)[id];
|
|
140
|
+
if (!snap) {
|
|
141
|
+
console.error(`Organism not found: ${id}`);
|
|
142
|
+
process.exitCode = 1;
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
console.log(JSON.stringify(snap.resources ?? {}, null, 2));
|
|
146
|
+
});
|
|
147
|
+
org
|
|
148
|
+
.command('events <id>')
|
|
149
|
+
.description('Show recent organism events')
|
|
150
|
+
.option('--store <path>', 'Snapshot store path', DEFAULT_STORE)
|
|
151
|
+
.option('--json', 'Print JSON')
|
|
152
|
+
.action((id, opts) => {
|
|
153
|
+
const snap = loadStore(opts.store)[id];
|
|
154
|
+
if (!snap) {
|
|
155
|
+
console.error(`Organism not found: ${id}`);
|
|
156
|
+
process.exitCode = 1;
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
console.log(JSON.stringify(snap.events ?? [], null, 2));
|
|
160
|
+
});
|
|
161
|
+
for (const action of ['pause', 'resume', 'stop']) {
|
|
162
|
+
org
|
|
163
|
+
.command(`${action} <id>`)
|
|
164
|
+
.description(`${action} an organism snapshot (status flag only in Phase 1 CLI)`)
|
|
165
|
+
.option('--store <path>', 'Snapshot store path', DEFAULT_STORE)
|
|
166
|
+
.action((id, opts) => {
|
|
167
|
+
const data = loadStore(opts.store);
|
|
168
|
+
const snap = data[id];
|
|
169
|
+
if (!snap) {
|
|
170
|
+
console.error(`Organism not found: ${id}`);
|
|
171
|
+
process.exitCode = 1;
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
174
|
+
snap.status =
|
|
175
|
+
action === 'pause' ? 'paused' : action === 'resume' ? 'operating' : 'terminated';
|
|
176
|
+
snap.updatedAt = new Date().toISOString();
|
|
177
|
+
data[id] = snap;
|
|
178
|
+
saveStore(opts.store, data);
|
|
179
|
+
console.log(`Organism ${id} marked ${snap.status}`);
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
org
|
|
183
|
+
.command('save-snapshot')
|
|
184
|
+
.description('Helper: write a minimal demo snapshot for CLI testing')
|
|
185
|
+
.option('--store <path>', 'Snapshot store path', DEFAULT_STORE)
|
|
186
|
+
.option('--id <id>', 'Organism id', 'demo-org')
|
|
187
|
+
.action((opts) => {
|
|
188
|
+
const data = loadStore(opts.store);
|
|
189
|
+
data[opts.id] = {
|
|
190
|
+
id: opts.id,
|
|
191
|
+
status: 'operating',
|
|
192
|
+
mission: {
|
|
193
|
+
id: 'demo',
|
|
194
|
+
objective: 'Operate customer support while maintaining 90% CSAT',
|
|
195
|
+
},
|
|
196
|
+
agents: [],
|
|
197
|
+
genealogy: [],
|
|
198
|
+
resources: { tokensRemaining: 5000000 },
|
|
199
|
+
events: [],
|
|
200
|
+
updatedAt: new Date().toISOString(),
|
|
201
|
+
};
|
|
202
|
+
saveStore(opts.store, data);
|
|
203
|
+
console.log(`Wrote snapshot ${opts.id} to ${opts.store}`);
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
function formatInspect(snap) {
|
|
207
|
+
return [
|
|
208
|
+
`id: ${snap.id}`,
|
|
209
|
+
`status: ${snap.status}`,
|
|
210
|
+
`mission: ${snap.mission.objective}`,
|
|
211
|
+
`agents: ${snap.agents.length}`,
|
|
212
|
+
`updatedAt: ${snap.updatedAt}`,
|
|
213
|
+
].join('\n');
|
|
214
|
+
}
|
package/dist/index.js
CHANGED
|
@@ -59,6 +59,7 @@ const agent_1 = require("./commands/agent");
|
|
|
59
59
|
const skillgate_1 = require("./commands/skillgate");
|
|
60
60
|
const gatekeeper_1 = require("./commands/gatekeeper");
|
|
61
61
|
const store_1 = require("./commands/store");
|
|
62
|
+
const organism_1 = require("./commands/organism");
|
|
62
63
|
// Read version from package.json to ensure consistency
|
|
63
64
|
const packageJson = JSON.parse((0, fs_1.readFileSync)((0, path_1.join)(__dirname, '../package.json'), 'utf8'));
|
|
64
65
|
const program = new commander_1.Command();
|
|
@@ -77,6 +78,7 @@ program
|
|
|
77
78
|
(0, skillgate_1.registerSkillgateCommand)(program);
|
|
78
79
|
(0, gatekeeper_1.registerGatekeeperCommand)(program);
|
|
79
80
|
(0, store_1.registerStoreCommand)(program);
|
|
81
|
+
(0, organism_1.registerOrganismCommand)(program);
|
|
80
82
|
// Generate command group (unified: hazel g <type> <name> [--path] [--dry-run] [--json], or hazel g --list)
|
|
81
83
|
const generateCommand = program
|
|
82
84
|
.command('generate')
|
|
@@ -91,6 +91,22 @@ export const oauthImports = [
|
|
|
91
91
|
moduleExpression: "ConfigModule.forRoot({ envFilePath: '.env' })",
|
|
92
92
|
setupTemplate: null,
|
|
93
93
|
},
|
|
94
|
+
{
|
|
95
|
+
shortName: 'config-server',
|
|
96
|
+
npm: '@hazeljs/config-server',
|
|
97
|
+
label: 'Git config server (@hazeljs/config-server)',
|
|
98
|
+
hint: 'import { ConfigServer } from "@hazeljs/config-server";\n // await new ConfigServer({ git: { uri: process.env.CONFIG_GIT_URI! }, port: 8888 }).start()',
|
|
99
|
+
moduleImport: "import { ConfigServerModule } from '@hazeljs/config-server';",
|
|
100
|
+
moduleExpression: "ConfigServerModule.forRoot({ git: { uri: process.env.CONFIG_GIT_URI || '' }, port: 8888 })",
|
|
101
|
+
setupTemplate: `import { ConfigServerModule } from '@hazeljs/config-server';
|
|
102
|
+
|
|
103
|
+
// Add ConfigServerModule.forRoot(...) to your HazelModule imports, then start:
|
|
104
|
+
// await new ConfigServer({ git: { uri: process.env.CONFIG_GIT_URI! }, port: 8888 }).start();
|
|
105
|
+
export const configServerImports = [
|
|
106
|
+
ConfigServerModule.forRoot({ git: { uri: process.env.CONFIG_GIT_URI || '' }, port: 8888 }),
|
|
107
|
+
];
|
|
108
|
+
`,
|
|
109
|
+
},
|
|
94
110
|
{
|
|
95
111
|
shortName: 'cron',
|
|
96
112
|
npm: '@hazeljs/cron',
|
|
@@ -269,6 +285,26 @@ export const gatekeeper = new AgentGatekeeper({
|
|
|
269
285
|
defaultDecision: 'deny',
|
|
270
286
|
policies: [],
|
|
271
287
|
});
|
|
288
|
+
`,
|
|
289
|
+
},
|
|
290
|
+
{
|
|
291
|
+
shortName: 'organism',
|
|
292
|
+
npm: '@hazeljs/organism',
|
|
293
|
+
label: 'Agentic Organism Runtime (@hazeljs/organism)',
|
|
294
|
+
hint: 'import { createOrganism, OrganismRuntime } from "@hazeljs/organism";\n // const organism = await createOrganism({ mission, genes, constitution })',
|
|
295
|
+
moduleImport: null,
|
|
296
|
+
moduleExpression: null,
|
|
297
|
+
setupTemplate: `import { createOrganism } from '@hazeljs/organism';
|
|
298
|
+
|
|
299
|
+
export async function startOrganism() {
|
|
300
|
+
const organism = await createOrganism({
|
|
301
|
+
mission: { id: 'ops', objective: 'Operate within budget and constraints' },
|
|
302
|
+
genes: [],
|
|
303
|
+
limits: { maxAgents: 10, maxGenerationDepth: 3 },
|
|
304
|
+
});
|
|
305
|
+
await organism.start();
|
|
306
|
+
return organism;
|
|
307
|
+
}
|
|
272
308
|
`,
|
|
273
309
|
},
|
|
274
310
|
{
|
|
@@ -363,6 +399,37 @@ export class ExampleResilienceService {
|
|
|
363
399
|
return 'ok';
|
|
364
400
|
}
|
|
365
401
|
}
|
|
402
|
+
`,
|
|
403
|
+
},
|
|
404
|
+
{
|
|
405
|
+
shortName: 'self-healing',
|
|
406
|
+
npm: '@hazeljs/self-healing',
|
|
407
|
+
label: 'Self-Healing Microservices (@hazeljs/self-healing)',
|
|
408
|
+
hint: 'import { SelfHealing, SelfHeal, createHealingCoordinator } from "@hazeljs/self-healing";',
|
|
409
|
+
moduleImport: null,
|
|
410
|
+
moduleExpression: null,
|
|
411
|
+
setupTemplate: `import { createHealingCoordinator } from '@hazeljs/self-healing';
|
|
412
|
+
|
|
413
|
+
export const healing = createHealingCoordinator({
|
|
414
|
+
strategies: ['config-rollback', 'hpa-boost', 'pod-restart'],
|
|
415
|
+
});
|
|
416
|
+
`,
|
|
417
|
+
},
|
|
418
|
+
{
|
|
419
|
+
shortName: 'predictive-scaling',
|
|
420
|
+
npm: '@hazeljs/predictive-scaling',
|
|
421
|
+
label: 'Predictive Auto-Scaling (@hazeljs/predictive-scaling)',
|
|
422
|
+
hint: 'import { PredictiveScaling, createPredictiveScaler } from "@hazeljs/predictive-scaling";',
|
|
423
|
+
moduleImport: null,
|
|
424
|
+
moduleExpression: null,
|
|
425
|
+
setupTemplate: `import { createPredictiveScaler } from '@hazeljs/predictive-scaling';
|
|
426
|
+
|
|
427
|
+
// Wire hpa.client to your Kubernetes scaling client
|
|
428
|
+
export const scaler = createPredictiveScaler({
|
|
429
|
+
horizon: '30m',
|
|
430
|
+
metrics: ['requests', 'latency'],
|
|
431
|
+
hpa: { name: 'api-hpa', client: scalingClient },
|
|
432
|
+
});
|
|
366
433
|
`,
|
|
367
434
|
},
|
|
368
435
|
{
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hazeljs/cli",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.7",
|
|
4
4
|
"description": "Command-line interface for scaffolding and generating HazelJS applications and components",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -31,11 +31,11 @@
|
|
|
31
31
|
"mustache": "^4.2.0"
|
|
32
32
|
},
|
|
33
33
|
"peerDependencies": {
|
|
34
|
-
"@hazeljs/eval": "^2.0.
|
|
35
|
-
"@hazeljs/benchmark": "^2.0.
|
|
36
|
-
"@hazeljs/agent": "^2.0.
|
|
37
|
-
"@hazeljs/skillgate": "^2.0.
|
|
38
|
-
"@hazeljs/agent-gatekeeper": "^2.0.
|
|
34
|
+
"@hazeljs/eval": "^2.0.7",
|
|
35
|
+
"@hazeljs/benchmark": "^2.0.7",
|
|
36
|
+
"@hazeljs/agent": "^2.0.7",
|
|
37
|
+
"@hazeljs/skillgate": "^2.0.7",
|
|
38
|
+
"@hazeljs/agent-gatekeeper": "^2.0.7"
|
|
39
39
|
},
|
|
40
40
|
"peerDependenciesMeta": {
|
|
41
41
|
"@hazeljs/eval": {
|
|
@@ -55,11 +55,11 @@
|
|
|
55
55
|
}
|
|
56
56
|
},
|
|
57
57
|
"devDependencies": {
|
|
58
|
-
"@hazeljs/eval": "^2.0.
|
|
59
|
-
"@hazeljs/benchmark": "^2.0.
|
|
60
|
-
"@hazeljs/agent": "^2.0.
|
|
61
|
-
"@hazeljs/skillgate": "^2.0.
|
|
62
|
-
"@hazeljs/agent-gatekeeper": "^2.0.
|
|
58
|
+
"@hazeljs/eval": "^2.0.7",
|
|
59
|
+
"@hazeljs/benchmark": "^2.0.7",
|
|
60
|
+
"@hazeljs/agent": "^2.0.7",
|
|
61
|
+
"@hazeljs/skillgate": "^2.0.7",
|
|
62
|
+
"@hazeljs/agent-gatekeeper": "^2.0.7",
|
|
63
63
|
"@types/inquirer": "^8.2.10",
|
|
64
64
|
"@types/mustache": "^4.2.5",
|
|
65
65
|
"typescript": "^6.0.3"
|