@aweeclaw/scenario-cli 1.0.0

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/dist/bin.js ADDED
@@ -0,0 +1,980 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/bin.ts
4
+ import { Command } from "commander";
5
+
6
+ // src/commands/init.ts
7
+ import fs2 from "fs";
8
+ import path2 from "path";
9
+
10
+ // src/utils/helpers.ts
11
+ import fs from "fs";
12
+ import path from "path";
13
+ import crypto from "crypto";
14
+
15
+ // src/utils/types.ts
16
+ import { z } from "zod";
17
+ var DeclarativeToolParamSchema = z.lazy(
18
+ () => z.object({
19
+ type: z.enum(["string", "number", "boolean", "array", "object"]),
20
+ description: z.string(),
21
+ required: z.boolean().optional(),
22
+ default: z.unknown().optional(),
23
+ enum: z.array(z.string()).optional(),
24
+ items: DeclarativeToolParamSchema.optional(),
25
+ properties: z.record(DeclarativeToolParamSchema).optional()
26
+ })
27
+ );
28
+ var DeclarativeCustomToolSchema = z.object({
29
+ name: z.string().min(1),
30
+ description: z.string(),
31
+ parameters: z.record(DeclarativeToolParamSchema),
32
+ executor: z.enum(["sql_query", "run_command", "read_file", "write_file", "web_search", "read_url"]),
33
+ template: z.string().optional(),
34
+ postProcess: z.enum(["json", "table", "text", "markdown"]).optional()
35
+ });
36
+ var DeclarativeIdentitySchema = z.object({
37
+ systemPrompt: z.string().optional(),
38
+ systemPromptFile: z.string().optional(),
39
+ securityRules: z.string().optional(),
40
+ securityRulesFile: z.string().optional(),
41
+ conventions: z.string().optional(),
42
+ conventionsFile: z.string().optional(),
43
+ workflow: z.string().optional(),
44
+ workflowFile: z.string().optional(),
45
+ outputFormat: z.string().optional(),
46
+ toolGuidelines: z.string().optional()
47
+ });
48
+ var DeclarativeUISchema = z.object({
49
+ layout: z.enum(["chat-centric", "split", "editor-centric", "custom"]).optional(),
50
+ panels: z.array(z.string()).optional(),
51
+ sidebarItems: z.array(z.object({
52
+ id: z.string(),
53
+ icon: z.string(),
54
+ label: z.string(),
55
+ labelZh: z.string(),
56
+ component: z.string(),
57
+ position: z.number().optional()
58
+ })).optional(),
59
+ welcomeComponent: z.string().optional()
60
+ });
61
+ var DeclarativeDatabaseSchema = z.object({
62
+ installScriptFiles: z.array(z.string()).optional(),
63
+ uninstallScriptFiles: z.array(z.string()).optional(),
64
+ installScripts: z.array(z.object({
65
+ id: z.string(),
66
+ description: z.string().optional(),
67
+ sql: z.string()
68
+ })).optional(),
69
+ uninstallScripts: z.array(z.object({
70
+ id: z.string(),
71
+ description: z.string().optional(),
72
+ sql: z.string()
73
+ })).optional()
74
+ });
75
+ var DeclarativeCapabilitiesSchema = z.object({
76
+ builtinTools: z.array(z.string()).optional(),
77
+ customTools: z.array(DeclarativeCustomToolSchema).optional(),
78
+ modes: z.array(z.object({
79
+ id: z.string(),
80
+ label: z.string(),
81
+ labelZh: z.string(),
82
+ icon: z.string(),
83
+ description: z.string(),
84
+ descriptionZh: z.string(),
85
+ toolPolicy: z.object({
86
+ enabled: z.boolean(),
87
+ requireApproval: z.boolean().optional()
88
+ })
89
+ })).optional(),
90
+ contextTypes: z.array(z.object({
91
+ type: z.string(),
92
+ label: z.string(),
93
+ labelZh: z.string(),
94
+ priority: z.number()
95
+ })).optional(),
96
+ outputFormats: z.array(z.string()).optional()
97
+ });
98
+ var DeclarativeScriptToolSchema = z.object({
99
+ name: z.string().min(1),
100
+ description: z.string(),
101
+ scriptFile: z.string(),
102
+ parameters: z.record(DeclarativeToolParamSchema)
103
+ });
104
+ var DeclarativeScriptsSchema = z.object({
105
+ onActivate: z.string().optional(),
106
+ onActivateFile: z.string().optional(),
107
+ onDeactivate: z.string().optional(),
108
+ onDeactivateFile: z.string().optional(),
109
+ onHealthCheck: z.string().optional(),
110
+ onHealthCheckFile: z.string().optional(),
111
+ tools: z.array(DeclarativeScriptToolSchema).optional()
112
+ });
113
+ var ScenarioConfigSchema = z.object({
114
+ id: z.string().min(1).regex(/^[a-z0-9-]+$/, "Scenario ID must be lowercase alphanumeric with hyphens"),
115
+ version: z.string().regex(/^\d+\.\d+\.\d+/, "Version must be semver (e.g. 1.0.0)"),
116
+ name: z.string().min(1),
117
+ nameZh: z.string().min(1),
118
+ description: z.string().default(""),
119
+ descriptionZh: z.string().default(""),
120
+ author: z.string().default("unknown"),
121
+ icon: z.string().default("Package"),
122
+ category: z.enum([
123
+ "productivity",
124
+ "development",
125
+ "education",
126
+ "business",
127
+ "creative",
128
+ "lifestyle",
129
+ "health",
130
+ "finance",
131
+ "legal",
132
+ "custom"
133
+ ]).default("custom"),
134
+ tags: z.array(z.string()).default([]),
135
+ minAppVersion: z.string().optional(),
136
+ homepage: z.string().optional(),
137
+ license: z.string().default("MIT"),
138
+ type: z.enum(["declarative", "programmatic"]).default("programmatic"),
139
+ entryPoint: z.string().default("src/index.ts"),
140
+ permissions: z.array(z.string()).default([]),
141
+ sharedDeps: z.record(z.string()).optional(),
142
+ dependencies: z.array(z.object({
143
+ id: z.string(),
144
+ versionRange: z.string().optional(),
145
+ required: z.boolean().default(true)
146
+ })).default([]),
147
+ identity: DeclarativeIdentitySchema.optional(),
148
+ capabilities: DeclarativeCapabilitiesSchema.optional(),
149
+ ui: DeclarativeUISchema.optional(),
150
+ database: DeclarativeDatabaseSchema.optional(),
151
+ scripts: DeclarativeScriptsSchema.optional(),
152
+ source: z.string().optional(),
153
+ hasSettings: z.boolean().optional(),
154
+ requiresWorkspace: z.boolean().optional()
155
+ });
156
+ var SHARED_EXTERNALS = [
157
+ "react",
158
+ "react-dom",
159
+ "react-dom/client",
160
+ "react/jsx-runtime",
161
+ "zustand",
162
+ "lucide-react"
163
+ ];
164
+ var SHARED_GLOBALS = {
165
+ "react": "__AWEECLAW_SHARED__.modules.react",
166
+ "react-dom": "__AWEECLAW_SHARED__.modules.reactDom",
167
+ "react-dom/client": "__AWEECLAW_SHARED__.modules.reactDom",
168
+ "react/jsx-runtime": "__AWEECLAW_SHARED__.modules.jsxRuntime",
169
+ "zustand": "__AWEECLAW_SHARED__.modules.zustand",
170
+ "lucide-react": "__AWEECLAW_SHARED__.modules.lucideReact"
171
+ };
172
+ var DEFAULT_SHARED_DEPS = {
173
+ react: "^18.3.0",
174
+ "react-dom": "^18.3.0",
175
+ zustand: "^5.0.0",
176
+ "lucide-react": "^0.562.0"
177
+ };
178
+
179
+ // src/utils/helpers.ts
180
+ function readScenarioConfig(dir) {
181
+ const configPath = path.join(dir, "scenario.json");
182
+ if (!fs.existsSync(configPath)) {
183
+ throw new Error(`scenario.json not found in ${dir}`);
184
+ }
185
+ const raw = fs.readFileSync(configPath, "utf-8");
186
+ const json = JSON.parse(raw);
187
+ const parsed = ScenarioConfigSchema.safeParse(json);
188
+ if (!parsed.success) {
189
+ const errors = parsed.error.issues.map((i) => ` - ${i.path.join(".")}: ${i.message}`).join("\n");
190
+ throw new Error(`Invalid scenario.json:
191
+ ${errors}`);
192
+ }
193
+ return parsed.data;
194
+ }
195
+ function writeJson(filePath, data) {
196
+ const dir = path.dirname(filePath);
197
+ if (!fs.existsSync(dir)) {
198
+ fs.mkdirSync(dir, { recursive: true });
199
+ }
200
+ fs.writeFileSync(filePath, JSON.stringify(data, null, 2) + "\n", "utf-8");
201
+ }
202
+ function computeDirectoryChecksum(dir) {
203
+ const hash = crypto.createHash("sha256");
204
+ const files = listFiles(dir).filter((f) => !f.endsWith("manifest.json")).sort();
205
+ for (const file of files) {
206
+ const rel = path.relative(dir, file);
207
+ hash.update(rel);
208
+ hash.update(fs.readFileSync(file));
209
+ }
210
+ return hash.digest("hex");
211
+ }
212
+ function listFiles(dir) {
213
+ const results = [];
214
+ if (!fs.existsSync(dir)) return results;
215
+ const entries = fs.readdirSync(dir, { withFileTypes: true });
216
+ for (const entry of entries) {
217
+ const fullPath = path.join(dir, entry.name);
218
+ if (entry.isDirectory()) {
219
+ results.push(...listFiles(fullPath));
220
+ } else {
221
+ results.push(fullPath);
222
+ }
223
+ }
224
+ return results;
225
+ }
226
+ function buildManifest(config, checksum) {
227
+ return {
228
+ id: config.id,
229
+ version: config.version,
230
+ name: config.name,
231
+ nameZh: config.nameZh,
232
+ description: config.description,
233
+ descriptionZh: config.descriptionZh,
234
+ author: config.author,
235
+ icon: config.icon,
236
+ category: config.category,
237
+ tags: config.tags,
238
+ minAppVersion: config.minAppVersion,
239
+ homepage: config.homepage,
240
+ license: config.license,
241
+ type: config.type,
242
+ bundleEntry: config.type === "programmatic" ? "bundle/index.js" : void 0,
243
+ sharedDeps: config.sharedDeps ? Object.keys(config.sharedDeps) : void 0,
244
+ apiVersion: "1.0.0",
245
+ checksum,
246
+ createdAt: (/* @__PURE__ */ new Date()).toISOString()
247
+ };
248
+ }
249
+ function ensureDir(dir) {
250
+ if (!fs.existsSync(dir)) {
251
+ fs.mkdirSync(dir, { recursive: true });
252
+ }
253
+ }
254
+
255
+ // src/commands/init.ts
256
+ function initScenario(cwd, options) {
257
+ const scenarioId = options.name.toLowerCase().replace(/[^a-z0-9-]/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "");
258
+ if (!scenarioId) {
259
+ throw new Error("Invalid scenario name: must produce a non-empty ID");
260
+ }
261
+ const scenarioDir = path2.join(cwd, scenarioId);
262
+ if (fs2.existsSync(scenarioDir)) {
263
+ throw new Error(`Directory "${scenarioId}" already exists`);
264
+ }
265
+ ensureDir(scenarioDir);
266
+ const scenarioType = options.type || "programmatic";
267
+ const config = {
268
+ id: scenarioId,
269
+ version: "1.0.0",
270
+ name: options.name,
271
+ nameZh: options.nameZh || options.name,
272
+ description: "",
273
+ descriptionZh: "",
274
+ author: options.author || "unknown",
275
+ icon: "Package",
276
+ category: options.category || "custom",
277
+ tags: [],
278
+ license: "MIT",
279
+ type: scenarioType,
280
+ entryPoint: scenarioType === "programmatic" ? "src/index.ts" : void 0,
281
+ permissions: [],
282
+ sharedDeps: scenarioType === "programmatic" ? DEFAULT_SHARED_DEPS : void 0,
283
+ dependencies: []
284
+ };
285
+ if (scenarioType === "declarative") {
286
+ config.identity = {
287
+ systemPromptFile: "prompts/system.md",
288
+ workflowFile: "prompts/workflow.md"
289
+ };
290
+ config.capabilities = {
291
+ builtinTools: ["web_search", "ask_user", "remember"]
292
+ };
293
+ config.ui = {
294
+ layout: "chat-centric"
295
+ };
296
+ config.database = {
297
+ installScriptFiles: [],
298
+ uninstallScriptFiles: []
299
+ };
300
+ config.scripts = {
301
+ onActivateFile: "scripts/onActivate.js",
302
+ onDeactivateFile: "scripts/onDeactivate.js"
303
+ };
304
+ }
305
+ writeJson(path2.join(scenarioDir, "scenario.json"), config);
306
+ if (scenarioType === "programmatic") {
307
+ writeProgrammaticTemplate(scenarioDir, config);
308
+ } else {
309
+ writeDeclarativeTemplate(scenarioDir, config);
310
+ }
311
+ const packageJson = {
312
+ name: `@aweeclaw/scenario-${scenarioId}`,
313
+ version: config.version,
314
+ private: true,
315
+ type: "module",
316
+ scripts: {
317
+ build: "aweeclaw-scenario build",
318
+ pack: "aweeclaw-scenario pack",
319
+ dev: "aweeclaw-scenario dev",
320
+ validate: "aweeclaw-scenario validate"
321
+ }
322
+ };
323
+ if (scenarioType === "programmatic") {
324
+ packageJson.devDependencies = {
325
+ "@aweeclaw/scenario-sdk": "^1.0.0",
326
+ "@types/react": "^18.3.0",
327
+ typescript: "^5.3.0"
328
+ };
329
+ } else {
330
+ packageJson.devDependencies = {
331
+ "@aweeclaw/scenario-sdk": "^1.0.0"
332
+ };
333
+ }
334
+ writeJson(path2.join(scenarioDir, "package.json"), packageJson);
335
+ if (scenarioType === "programmatic") {
336
+ writeTsConfig(scenarioDir);
337
+ }
338
+ console.log(`\u2705 Scenario "${scenarioId}" initialized at ${scenarioDir}`);
339
+ console.log(`
340
+ cd ${scenarioId}`);
341
+ console.log(` npm install`);
342
+ if (scenarioType === "programmatic") {
343
+ console.log(` aweeclaw-scenario dev`);
344
+ } else {
345
+ console.log(` # Edit scenario.json and prompts/ files`);
346
+ console.log(` aweeclaw-scenario build`);
347
+ }
348
+ console.log();
349
+ }
350
+ function writeProgrammaticTemplate(dir, config) {
351
+ const srcDir = path2.join(dir, "src");
352
+ const componentsDir = path2.join(srcDir, "components");
353
+ ensureDir(componentsDir);
354
+ fs2.writeFileSync(
355
+ path2.join(srcDir, "index.ts"),
356
+ `import type { ScenarioModule, ScenarioModuleContext, ScenarioToolDefinition } from '@aweeclaw/scenario-sdk'
357
+ import { DashboardPanel } from './components/DashboardPanel.js'
358
+
359
+ export default {
360
+ id: '${config.id}',
361
+ version: '${config.version}',
362
+
363
+ getManifest() {
364
+ return {
365
+ id: '${config.id}',
366
+ version: '${config.version}',
367
+ name: '${config.name}',
368
+ nameZh: '${config.nameZh}',
369
+ description: '${config.description}',
370
+ descriptionZh: '${config.descriptionZh}',
371
+ author: '${config.author}',
372
+ icon: '${config.icon}',
373
+ category: '${config.category}',
374
+ tags: ${JSON.stringify(config.tags)},
375
+ entryPoint: 'bundle/index.js',
376
+ }
377
+ },
378
+
379
+ getPlugin() {
380
+ return {
381
+ id: '${config.id}',
382
+ name: '${config.name}',
383
+ nameZh: '${config.nameZh}',
384
+ icon: '${config.icon}',
385
+ description: '${config.description}',
386
+ descriptionZh: '${config.descriptionZh}',
387
+ version: '${config.version}',
388
+ author: '${config.author}',
389
+ category: '${config.category}',
390
+ tags: ${JSON.stringify(config.tags)},
391
+ identity: { systemPrompt: '', securityRules: '', conventions: '', workflow: '' },
392
+ capabilities: { toolPacks: [], modes: [], contextTypes: [], outputFormats: [] },
393
+ ui: { layout: 'chat-centric', panels: [], sidebarItems: [], statusBarItems: [] },
394
+ dataSources: { workspace: false },
395
+ }
396
+ },
397
+
398
+ getTools(): ScenarioToolDefinition[] {
399
+ return []
400
+ },
401
+
402
+ getComponents() {
403
+ return {
404
+ DashboardPanel,
405
+ }
406
+ },
407
+
408
+ async onActivate(context: ScenarioModuleContext): Promise<void> {
409
+ context.publishData('scenario:activated', {
410
+ scenarioId: '${config.id}',
411
+ version: '${config.version}',
412
+ type: 'programmatic',
413
+ })
414
+ },
415
+
416
+ async onDeactivate(context: ScenarioModuleContext): Promise<void> {
417
+ context.publishData('scenario:deactivated', { scenarioId: '${config.id}' })
418
+ },
419
+
420
+ async onHealthCheck() {
421
+ return [{ name: 'module', status: 'healthy' as const, message: '${config.name} is running' }]
422
+ },
423
+ } satisfies ScenarioModule
424
+ `,
425
+ "utf-8"
426
+ );
427
+ fs2.writeFileSync(
428
+ path2.join(componentsDir, "DashboardPanel.tsx"),
429
+ `export function DashboardPanel() {
430
+ return (
431
+ <div style={{ padding: 16 }}>
432
+ <h2 style={{ fontSize: 16, fontWeight: 600, marginBottom: 8 }}>
433
+ ${config.name}
434
+ </h2>
435
+ <p style={{ fontSize: 13, opacity: 0.7 }}>
436
+ This is the dashboard panel for ${config.name} scenario.
437
+ Customize this component to build your scenario UI.
438
+ </p>
439
+ </div>
440
+ )
441
+ }
442
+ `,
443
+ "utf-8"
444
+ );
445
+ const promptsDir = path2.join(dir, "prompts");
446
+ ensureDir(promptsDir);
447
+ fs2.writeFileSync(
448
+ path2.join(promptsDir, "system.md"),
449
+ `You are the ${config.name} scenario assistant.
450
+ Help users with ${config.category} related tasks.
451
+ `,
452
+ "utf-8"
453
+ );
454
+ }
455
+ function writeDeclarativeTemplate(dir, config) {
456
+ const promptsDir = path2.join(dir, "prompts");
457
+ ensureDir(promptsDir);
458
+ fs2.writeFileSync(
459
+ path2.join(promptsDir, "system.md"),
460
+ `You are the ${config.name} scenario assistant.
461
+
462
+ Your role is to help users with ${config.category} related tasks.
463
+ Be professional, accurate, and helpful.
464
+ `,
465
+ "utf-8"
466
+ );
467
+ fs2.writeFileSync(
468
+ path2.join(promptsDir, "workflow.md"),
469
+ `## Workflow
470
+
471
+ 1. Understand user request
472
+ 2. Process and respond
473
+ 3. Offer follow-up suggestions
474
+ `,
475
+ "utf-8"
476
+ );
477
+ const scriptsDir = path2.join(dir, "scripts");
478
+ ensureDir(scriptsDir);
479
+ fs2.writeFileSync(
480
+ path2.join(scriptsDir, "onActivate.js"),
481
+ `// This script runs when the scenario is activated
482
+ // Available globals: console, fetch, JSON
483
+ console.log('${config.name} activated')
484
+ `,
485
+ "utf-8"
486
+ );
487
+ fs2.writeFileSync(
488
+ path2.join(scriptsDir, "onDeactivate.js"),
489
+ `// This script runs when the scenario is deactivated
490
+ console.log('${config.name} deactivated')
491
+ `,
492
+ "utf-8"
493
+ );
494
+ const dbDir = path2.join(dir, "db");
495
+ ensureDir(dbDir);
496
+ fs2.writeFileSync(
497
+ path2.join(dbDir, "install.sql"),
498
+ `-- Database install script for ${config.name}
499
+ -- Add your table creation statements here
500
+ `,
501
+ "utf-8"
502
+ );
503
+ fs2.writeFileSync(
504
+ path2.join(dbDir, "uninstall.sql"),
505
+ `-- Database uninstall script for ${config.name}
506
+ -- Add your table drop statements here
507
+ `,
508
+ "utf-8"
509
+ );
510
+ }
511
+ function writeTsConfig(dir) {
512
+ writeJson(path2.join(dir, "tsconfig.json"), {
513
+ compilerOptions: {
514
+ target: "ES2022",
515
+ module: "ESNext",
516
+ moduleResolution: "bundler",
517
+ strict: true,
518
+ esModuleInterop: true,
519
+ skipLibCheck: true,
520
+ jsx: "react-jsx",
521
+ outDir: "dist",
522
+ rootDir: "src",
523
+ types: ["node"],
524
+ paths: {
525
+ "@aweeclaw/scenario-sdk": ["./node_modules/@aweeclaw/scenario-sdk"]
526
+ }
527
+ },
528
+ include: ["src"],
529
+ exclude: ["node_modules", "dist"]
530
+ });
531
+ }
532
+
533
+ // src/commands/build.ts
534
+ import fs3 from "fs";
535
+ import path3 from "path";
536
+ import { build } from "vite";
537
+ async function buildScenario(cwd, options = {}) {
538
+ const config = readScenarioConfig(cwd);
539
+ if (config.type === "declarative") {
540
+ buildDeclarative(cwd, config, options);
541
+ return;
542
+ }
543
+ await buildProgrammatic(cwd, config, options);
544
+ }
545
+ async function buildProgrammatic(cwd, config, options) {
546
+ const outDir = path3.resolve(cwd, options.outDir || "dist");
547
+ const entryPoint = path3.resolve(cwd, config.entryPoint || "src/index.ts");
548
+ if (!fs3.existsSync(entryPoint)) {
549
+ throw new Error(`Entry point not found: ${entryPoint}`);
550
+ }
551
+ const sharedExternalsPlugin = {
552
+ name: "aweeclaw-shared-externals",
553
+ enforce: "pre",
554
+ resolveId(source) {
555
+ if (SHARED_EXTERNALS.includes(source)) {
556
+ return { id: source, external: true };
557
+ }
558
+ return null;
559
+ }
560
+ };
561
+ const viteConfig = {
562
+ root: cwd,
563
+ logLevel: "warn",
564
+ build: {
565
+ lib: {
566
+ entry: entryPoint,
567
+ formats: ["es"],
568
+ fileName: () => "bundle/index.js"
569
+ },
570
+ outDir,
571
+ emptyOutDir: true,
572
+ minify: options.minify ?? false,
573
+ sourcemap: options.sourcemap ?? true,
574
+ target: "esnext",
575
+ cssCodeSplit: true,
576
+ rollupOptions: {
577
+ external: SHARED_EXTERNALS,
578
+ output: {
579
+ format: "es",
580
+ globals: SHARED_GLOBALS,
581
+ paths(id) {
582
+ if (SHARED_GLOBALS[id]) {
583
+ return SHARED_GLOBALS[id];
584
+ }
585
+ return id;
586
+ },
587
+ manualChunks(id) {
588
+ if (id.includes("/src/components/")) {
589
+ const match = id.match(/\/src\/components\/(.+?)\./);
590
+ if (match) {
591
+ return match[1];
592
+ }
593
+ }
594
+ return void 0;
595
+ },
596
+ chunkFileNames: "bundle/[name].js",
597
+ assetFileNames: "bundle/[name].[ext]"
598
+ }
599
+ }
600
+ },
601
+ plugins: [sharedExternalsPlugin]
602
+ };
603
+ console.log(`\u{1F528} Building scenario "${config.id}" v${config.version}...`);
604
+ await build(viteConfig);
605
+ const configDir = path3.join(outDir, "config");
606
+ ensureDir(configDir);
607
+ writeJson(path3.join(configDir, "scenario.json"), config);
608
+ if (fs3.existsSync(path3.join(cwd, "prompts"))) {
609
+ copyDir(path3.join(cwd, "prompts"), path3.join(outDir, "prompts"));
610
+ }
611
+ if (fs3.existsSync(path3.join(cwd, "db"))) {
612
+ copyDir(path3.join(cwd, "db"), path3.join(outDir, "db"));
613
+ }
614
+ if (fs3.existsSync(path3.join(cwd, "assets"))) {
615
+ copyDir(path3.join(cwd, "assets"), path3.join(outDir, "assets"));
616
+ }
617
+ const checksum = computeDirectoryChecksum(outDir);
618
+ const manifest = buildManifest(config, checksum);
619
+ writeJson(path3.join(outDir, "manifest.json"), manifest);
620
+ console.log(`\u2705 Build complete: ${outDir}`);
621
+ console.log(` Checksum: ${checksum.substring(0, 16)}...`);
622
+ }
623
+ function buildDeclarative(cwd, config, options) {
624
+ const outDir = path3.resolve(cwd, options.outDir || "dist");
625
+ ensureDir(outDir);
626
+ const configDir = path3.join(outDir, "config");
627
+ ensureDir(configDir);
628
+ writeJson(path3.join(configDir, "scenario.json"), config);
629
+ if (fs3.existsSync(path3.join(cwd, "prompts"))) {
630
+ copyDir(path3.join(cwd, "prompts"), path3.join(outDir, "prompts"));
631
+ }
632
+ if (fs3.existsSync(path3.join(cwd, "scripts"))) {
633
+ copyDir(path3.join(cwd, "scripts"), path3.join(outDir, "scripts"));
634
+ }
635
+ if (fs3.existsSync(path3.join(cwd, "db"))) {
636
+ copyDir(path3.join(cwd, "db"), path3.join(outDir, "db"));
637
+ }
638
+ if (fs3.existsSync(path3.join(cwd, "assets"))) {
639
+ copyDir(path3.join(cwd, "assets"), path3.join(outDir, "assets"));
640
+ }
641
+ const checksum = computeDirectoryChecksum(outDir);
642
+ const manifest = buildManifest(config, checksum);
643
+ writeJson(path3.join(outDir, "manifest.json"), manifest);
644
+ console.log(`\u2705 Declarative scenario packaged: ${outDir}`);
645
+ }
646
+ function copyDir(src, dest) {
647
+ if (!fs3.existsSync(src)) return;
648
+ ensureDir(dest);
649
+ const entries = fs3.readdirSync(src, { withFileTypes: true });
650
+ for (const entry of entries) {
651
+ const srcPath = path3.join(src, entry.name);
652
+ const destPath = path3.join(dest, entry.name);
653
+ if (entry.isDirectory()) {
654
+ copyDir(srcPath, destPath);
655
+ } else {
656
+ fs3.copyFileSync(srcPath, destPath);
657
+ }
658
+ }
659
+ }
660
+
661
+ // src/commands/pack.ts
662
+ import fs4 from "fs";
663
+ import path4 from "path";
664
+ import { create as createTar } from "tar";
665
+ async function packScenario(cwd, options = {}) {
666
+ const distDir = path4.resolve(cwd, "dist");
667
+ if (!fs4.existsSync(distDir)) {
668
+ throw new Error('dist/ directory not found. Run "aweeclaw-scenario build" first.');
669
+ }
670
+ const config = readScenarioConfig(cwd);
671
+ const checksum = computeDirectoryChecksum(distDir);
672
+ const manifest = buildManifest(config, checksum);
673
+ writeJson(path4.join(distDir, "manifest.json"), manifest);
674
+ const outDir = path4.resolve(cwd, options.outDir || ".");
675
+ const fileName = `${config.id}-${config.version}.tar.gz`;
676
+ const outputPath = path4.join(outDir, fileName);
677
+ if (!fs4.existsSync(outDir)) {
678
+ fs4.mkdirSync(outDir, { recursive: true });
679
+ }
680
+ if (fs4.existsSync(outputPath)) {
681
+ fs4.unlinkSync(outputPath);
682
+ }
683
+ await createTar(
684
+ {
685
+ gzip: true,
686
+ file: outputPath,
687
+ cwd: distDir,
688
+ portable: true
689
+ },
690
+ fs4.readdirSync(distDir)
691
+ );
692
+ const stats = fs4.statSync(outputPath);
693
+ const sizeKB = (stats.size / 1024).toFixed(1);
694
+ console.log(`\u{1F4E6} Packed: ${outputPath}`);
695
+ console.log(` Size: ${sizeKB} KB`);
696
+ console.log(` Checksum: ${checksum.substring(0, 16)}...`);
697
+ return outputPath;
698
+ }
699
+
700
+ // src/commands/dev.ts
701
+ import fs5 from "fs";
702
+ import path5 from "path";
703
+ import { createServer } from "vite";
704
+ async function devScenario(cwd, options = {}) {
705
+ const config = readScenarioConfig(cwd);
706
+ if (config.type === "declarative") {
707
+ console.log("\u2139\uFE0F Declarative scenarios do not require a dev server.");
708
+ console.log(' Edit prompts/ and scenario.json directly, then run "aweeclaw-scenario build".');
709
+ return;
710
+ }
711
+ const entryPoint = path5.resolve(cwd, config.entryPoint || "src/index.ts");
712
+ if (!fs5.existsSync(entryPoint)) {
713
+ throw new Error(`Entry point not found: ${entryPoint}`);
714
+ }
715
+ const port = options.port || 3210;
716
+ const server = await createServer({
717
+ root: cwd,
718
+ logLevel: "info",
719
+ server: {
720
+ port,
721
+ strictPort: false
722
+ },
723
+ resolve: {
724
+ alias: {
725
+ "@aweeclaw/scenario-sdk": path5.resolve(cwd, "node_modules/@aweeclaw/scenario-sdk")
726
+ }
727
+ },
728
+ build: {
729
+ target: "esnext"
730
+ },
731
+ define: {
732
+ "__AWEECLAW_SHARED__": "window.__AWEECLAW_SHARED__"
733
+ }
734
+ });
735
+ await server.listen();
736
+ console.log(`
737
+ \u{1F680} Dev server for "${config.id}" running at:`);
738
+ console.log(` ${server.resolvedUrls?.local?.[0] || `http://localhost:${port}`}`);
739
+ console.log(`
740
+ Edit src/ files and changes will hot-reload.`);
741
+ console.log(` Press Ctrl+C to stop.
742
+ `);
743
+ }
744
+
745
+ // src/commands/validate.ts
746
+ import fs6 from "fs";
747
+ import path6 from "path";
748
+ async function validateScenario(cwd, options = {}) {
749
+ const errors = [];
750
+ const warnings = [];
751
+ console.log(`\u{1F50D} Validating scenario in ${cwd}...
752
+ `);
753
+ let config = null;
754
+ try {
755
+ config = readScenarioConfig(cwd);
756
+ console.log(` \u2713 scenario.json is valid (id: ${config.id}, v${config.version})`);
757
+ } catch (err) {
758
+ errors.push(err instanceof Error ? err.message : String(err));
759
+ printResults(errors, warnings);
760
+ return false;
761
+ }
762
+ const distDir = path6.resolve(cwd, "dist");
763
+ if (!fs6.existsSync(distDir)) {
764
+ errors.push('dist/ directory not found. Run "aweeclaw-scenario build" first.');
765
+ printResults(errors, warnings);
766
+ return false;
767
+ }
768
+ const manifestPath = path6.join(distDir, "manifest.json");
769
+ if (!fs6.existsSync(manifestPath)) {
770
+ errors.push("manifest.json not found in dist/");
771
+ } else {
772
+ try {
773
+ const manifest = JSON.parse(fs6.readFileSync(manifestPath, "utf-8"));
774
+ if (!manifest.id) errors.push('manifest.json missing "id"');
775
+ if (!manifest.version) errors.push('manifest.json missing "version"');
776
+ if (!manifest.type) errors.push('manifest.json missing "type"');
777
+ if (!manifest.checksum) errors.push('manifest.json missing "checksum"');
778
+ console.log(" \u2713 manifest.json is valid");
779
+ } catch {
780
+ errors.push("manifest.json is not valid JSON");
781
+ }
782
+ }
783
+ if (config.type === "programmatic") {
784
+ validateProgrammatic(distDir, config, errors, warnings, options.strict);
785
+ }
786
+ if (config.type === "declarative") {
787
+ validateDeclarative(distDir, config, errors, warnings);
788
+ }
789
+ if (fs6.existsSync(manifestPath)) {
790
+ const checksum = computeDirectoryChecksum(distDir);
791
+ const manifest = JSON.parse(fs6.readFileSync(manifestPath, "utf-8"));
792
+ if (manifest.checksum === checksum) {
793
+ console.log(" \u2713 Checksum matches");
794
+ } else {
795
+ errors.push(`Checksum mismatch: manifest has "${manifest.checksum?.substring(0, 16)}...", actual is "${checksum.substring(0, 16)}..."`);
796
+ }
797
+ }
798
+ const files = listFiles(distDir);
799
+ const totalSize = files.reduce((sum, f) => sum + fs6.statSync(f).size, 0);
800
+ const sizeMB = (totalSize / 1024 / 1024).toFixed(2);
801
+ console.log(` \u2713 Total size: ${sizeMB} MB (${files.length} files)`);
802
+ if (totalSize > 50 * 1024 * 1024) {
803
+ warnings.push(`Package size (${sizeMB} MB) exceeds 50 MB limit`);
804
+ }
805
+ printResults(errors, warnings);
806
+ return errors.length === 0;
807
+ }
808
+ function validateProgrammatic(distDir, config, errors, warnings, strict) {
809
+ const bundleEntry = path6.join(distDir, "bundle", "index.js");
810
+ if (!fs6.existsSync(bundleEntry)) {
811
+ errors.push("bundle/index.js not found (required for programmatic scenarios)");
812
+ return;
813
+ }
814
+ const content = fs6.readFileSync(bundleEntry, "utf-8");
815
+ for (const ext of SHARED_EXTERNALS) {
816
+ const importPattern = new RegExp(`import.*from\\s+['"]${ext.replace("/", "\\/")}['"]`);
817
+ const requirePattern = new RegExp(`require\\(['"]${ext.replace("/", "\\/")}['"]\\)`);
818
+ if (importPattern.test(content) || requirePattern.test(content)) {
819
+ console.log(` \u2713 Shared dependency "${ext}" is externalized`);
820
+ }
821
+ }
822
+ if (content.includes("__AWEECLAW_SHARED__")) {
823
+ console.log(" \u2713 Uses __AWEECLAW_SHARED__ for shared dependencies");
824
+ } else if (strict) {
825
+ warnings.push("Bundle does not reference __AWEECLAW_SHARED__. Ensure shared deps are externalized.");
826
+ }
827
+ }
828
+ function validateDeclarative(distDir, config, errors, warnings) {
829
+ const configDir = path6.join(distDir, "config");
830
+ const distConfigPath = path6.join(configDir, "scenario.json");
831
+ if (!fs6.existsSync(distConfigPath)) {
832
+ errors.push("config/scenario.json not found in dist/");
833
+ }
834
+ const promptsDir = path6.join(distDir, "prompts");
835
+ if (!fs6.existsSync(promptsDir)) {
836
+ warnings.push("prompts/ directory not found (recommended for declarative scenarios)");
837
+ } else {
838
+ const systemMd = path6.join(promptsDir, "system.md");
839
+ if (!fs6.existsSync(systemMd)) {
840
+ if (config.identity?.systemPromptFile) {
841
+ errors.push(`prompts/system.md not found (referenced by identity.systemPromptFile)`);
842
+ } else {
843
+ warnings.push("prompts/system.md not found (recommended for declarative scenarios)");
844
+ }
845
+ } else {
846
+ console.log(" \u2713 prompts/system.md exists");
847
+ }
848
+ }
849
+ if (config.identity) {
850
+ const fileRefs = [
851
+ { key: "systemPromptFile", path: config.identity.systemPromptFile },
852
+ { key: "securityRulesFile", path: config.identity.securityRulesFile },
853
+ { key: "conventionsFile", path: config.identity.conventionsFile },
854
+ { key: "workflowFile", path: config.identity.workflowFile }
855
+ ];
856
+ for (const ref of fileRefs) {
857
+ if (ref.path && !fs6.existsSync(path6.join(distDir, ref.path))) {
858
+ errors.push(`identity.${ref.key} references "${ref.path}" but file not found in dist/`);
859
+ }
860
+ }
861
+ }
862
+ if (config.database) {
863
+ const scriptRefs = [
864
+ { key: "installScriptFiles", paths: config.database.installScriptFiles },
865
+ { key: "uninstallScriptFiles", paths: config.database.uninstallScriptFiles }
866
+ ];
867
+ for (const ref of scriptRefs) {
868
+ if (ref.paths) {
869
+ for (const scriptPath of ref.paths) {
870
+ if (!fs6.existsSync(path6.join(distDir, scriptPath))) {
871
+ errors.push(`database.${ref.key} references "${scriptPath}" but file not found in dist/`);
872
+ }
873
+ }
874
+ }
875
+ }
876
+ }
877
+ if (config.scripts) {
878
+ const scriptRefs = [
879
+ { key: "onActivateFile", path: config.scripts.onActivateFile },
880
+ { key: "onDeactivateFile", path: config.scripts.onDeactivateFile },
881
+ { key: "onHealthCheckFile", path: config.scripts.onHealthCheckFile }
882
+ ];
883
+ for (const ref of scriptRefs) {
884
+ if (ref.path && !fs6.existsSync(path6.join(distDir, ref.path))) {
885
+ errors.push(`scripts.${ref.key} references "${ref.path}" but file not found in dist/`);
886
+ }
887
+ }
888
+ if (config.scripts.tools) {
889
+ for (const tool of config.scripts.tools) {
890
+ if (tool.scriptFile && !fs6.existsSync(path6.join(distDir, tool.scriptFile))) {
891
+ errors.push(`scripts.tools[${tool.name}].scriptFile references "${tool.scriptFile}" but file not found in dist/`);
892
+ }
893
+ }
894
+ }
895
+ }
896
+ if (config.capabilities?.customTools) {
897
+ for (const tool of config.capabilities.customTools) {
898
+ if (!tool.name) errors.push(`capabilities.customTools: tool missing "name"`);
899
+ if (!tool.executor) errors.push(`capabilities.customTools[${tool.name}]: tool missing "executor"`);
900
+ }
901
+ }
902
+ console.log(" \u2713 Declarative scenario structure validated");
903
+ }
904
+ function printResults(errors, warnings) {
905
+ console.log();
906
+ if (warnings.length > 0) {
907
+ console.log("\u26A0\uFE0F Warnings:");
908
+ for (const w of warnings) {
909
+ console.log(` - ${w}`);
910
+ }
911
+ console.log();
912
+ }
913
+ if (errors.length > 0) {
914
+ console.log("\u274C Errors:");
915
+ for (const e of errors) {
916
+ console.log(` - ${e}`);
917
+ }
918
+ console.log();
919
+ console.log("Validation failed.");
920
+ } else {
921
+ console.log("\u2705 Validation passed!");
922
+ }
923
+ }
924
+
925
+ // src/bin.ts
926
+ var program = new Command();
927
+ program.name("aweeclaw-scenario").description("CLI tool for building, packing, and managing Aweeclaw scenarios").version("1.0.0");
928
+ program.command("init").description("Initialize a new scenario project").argument("<name>", "Scenario name (e.g. my-scenario)").option("--name-zh <nameZh>", "Chinese name for the scenario").option("--category <category>", "Scenario category", "custom").option("--type <type>", "Scenario type: declarative or programmatic", "programmatic").option("--author <author>", "Author name").action((name, opts) => {
929
+ try {
930
+ initScenario(process.cwd(), {
931
+ name,
932
+ nameZh: opts.nameZh,
933
+ category: opts.category,
934
+ type: opts.type,
935
+ author: opts.author
936
+ });
937
+ } catch (err) {
938
+ console.error("\u274C Init failed:", err instanceof Error ? err.message : String(err));
939
+ process.exit(1);
940
+ }
941
+ });
942
+ program.command("build").description("Build the scenario into dist/").option("--out-dir <dir>", "Output directory", "dist").option("--minify", "Minify output", false).option("--no-sourcemap", "Disable sourcemap").action(async (opts) => {
943
+ try {
944
+ await buildScenario(process.cwd(), {
945
+ outDir: opts.outDir,
946
+ minify: opts.minify,
947
+ sourcemap: opts.sourcemap
948
+ });
949
+ } catch (err) {
950
+ console.error("\u274C Build failed:", err instanceof Error ? err.message : String(err));
951
+ process.exit(1);
952
+ }
953
+ });
954
+ program.command("pack").description("Pack dist/ into a tar.gz archive").option("--out-dir <dir>", "Output directory for the archive", ".").action(async (opts) => {
955
+ try {
956
+ await packScenario(process.cwd(), { outDir: opts.outDir });
957
+ } catch (err) {
958
+ console.error("\u274C Pack failed:", err instanceof Error ? err.message : String(err));
959
+ process.exit(1);
960
+ }
961
+ });
962
+ program.command("dev").description("Start local development server with hot reload").option("--port <port>", "Dev server port", "3210").action(async (opts) => {
963
+ try {
964
+ await devScenario(process.cwd(), { port: parseInt(opts.port, 10) });
965
+ } catch (err) {
966
+ console.error("\u274C Dev server failed:", err instanceof Error ? err.message : String(err));
967
+ process.exit(1);
968
+ }
969
+ });
970
+ program.command("validate").description("Validate the built scenario package").option("--strict", "Enable strict validation", false).action(async (opts) => {
971
+ try {
972
+ const valid = await validateScenario(process.cwd(), { strict: opts.strict });
973
+ process.exit(valid ? 0 : 1);
974
+ } catch (err) {
975
+ console.error("\u274C Validation failed:", err instanceof Error ? err.message : String(err));
976
+ process.exit(1);
977
+ }
978
+ });
979
+ program.parse();
980
+ //# sourceMappingURL=bin.js.map