@growthub/cli 0.9.6 → 0.9.8
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/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/globals.css +261 -1
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/workspace-builder.jsx +583 -96
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/workspace-schema.js +310 -1
- package/package.json +1 -1
|
@@ -3,6 +3,9 @@ const GRID_ROWS = 16;
|
|
|
3
3
|
const KNOWN_WIDGET_KINDS = ["chart", "view", "iframe", "rich-text"];
|
|
4
4
|
const KNOWN_FIELDS = ["dashboards", "widgetTypes", "canvas"];
|
|
5
5
|
const KNOWN_DATA_BINDING_MODES = ["manual", "json", "csv"];
|
|
6
|
+
const WORKSPACE_TEMPLATE_KIND = "growthub-workspace-template";
|
|
7
|
+
const WORKSPACE_TEMPLATE_VERSION = 1;
|
|
8
|
+
const WORKSPACE_TEMPLATE_SOURCE = "growthub-custom-workspace-starter-v1";
|
|
6
9
|
|
|
7
10
|
const WIDGET_SCHEMA_CONTRACTS = {
|
|
8
11
|
WidgetPosition: {
|
|
@@ -121,12 +124,25 @@ const DASHBOARD_TEMPLATES = [
|
|
|
121
124
|
id: "blank",
|
|
122
125
|
name: "Blank",
|
|
123
126
|
description: "Empty governed canvas",
|
|
127
|
+
category: "blank",
|
|
128
|
+
bestFor: ["Custom layouts", "Fresh starts"],
|
|
129
|
+
tags: ["blank", "starter"],
|
|
130
|
+
preview: { layout: "empty", summary: "Start from an empty fixed-grid canvas" },
|
|
131
|
+
dashboard: { name: "Blank", status: "draft" },
|
|
124
132
|
widgets: []
|
|
125
133
|
},
|
|
126
134
|
{
|
|
127
135
|
id: "client-portal",
|
|
128
136
|
name: "Client Portal",
|
|
129
137
|
description: "Client status, documents, and embedded portal area",
|
|
138
|
+
category: "agency",
|
|
139
|
+
bestFor: ["Agencies", "Consultants", "Client delivery"],
|
|
140
|
+
tags: ["client", "portal", "delivery"],
|
|
141
|
+
preview: {
|
|
142
|
+
layout: "multi-panel",
|
|
143
|
+
summary: "Client summary, companies table, portal embed, and delivery health"
|
|
144
|
+
},
|
|
145
|
+
dashboard: { name: "Client Portal", status: "draft" },
|
|
130
146
|
widgets: [
|
|
131
147
|
createWidget("rich-text", "Client Summary", { x: 0, y: 0, w: 4, h: 4 }, { text: "Current client priorities, owner notes, and next milestone.", binding: { mode: "manual", source: "Manual text", rows: [] } }),
|
|
132
148
|
createWidget("view", "Companies", { x: 4, y: 0, w: 5, h: 5 }),
|
|
@@ -138,6 +154,14 @@ const DASHBOARD_TEMPLATES = [
|
|
|
138
154
|
id: "content-ops",
|
|
139
155
|
name: "Content Ops",
|
|
140
156
|
description: "Editorial pipeline and review snapshot",
|
|
157
|
+
category: "content",
|
|
158
|
+
bestFor: ["Editorial teams", "Content marketers", "Content reviewers"],
|
|
159
|
+
tags: ["content", "editorial", "review"],
|
|
160
|
+
preview: {
|
|
161
|
+
layout: "queue-and-mix",
|
|
162
|
+
summary: "Content queue, publishing mix chart, and review notes"
|
|
163
|
+
},
|
|
164
|
+
dashboard: { name: "Content Ops", status: "draft" },
|
|
141
165
|
widgets: [
|
|
142
166
|
createWidget("view", "Content Queue", { x: 0, y: 0, w: 5, h: 5 }, {
|
|
143
167
|
source: "Content",
|
|
@@ -158,6 +182,14 @@ const DASHBOARD_TEMPLATES = [
|
|
|
158
182
|
id: "reporting-dashboard",
|
|
159
183
|
name: "Reporting Dashboard",
|
|
160
184
|
description: "KPIs, table, and executive readout",
|
|
185
|
+
category: "reporting",
|
|
186
|
+
bestFor: ["Executives", "Analytics teams", "Operations"],
|
|
187
|
+
tags: ["kpi", "reporting", "analytics"],
|
|
188
|
+
preview: {
|
|
189
|
+
layout: "kpi-grid",
|
|
190
|
+
summary: "Pipeline trend, conversion chart, performance table, executive summary"
|
|
191
|
+
},
|
|
192
|
+
dashboard: { name: "Reporting Dashboard", status: "draft" },
|
|
161
193
|
widgets: [
|
|
162
194
|
createWidget("chart", "Pipeline Trend", { x: 0, y: 0, w: 4, h: 5 }, { values: [42, 58, 63, 71, 86], binding: SAMPLE_DATA_BINDINGS.reportingJson }),
|
|
163
195
|
createWidget("chart", "Conversion", { x: 4, y: 0, w: 4, h: 5 }, { values: [28, 36, 44, 39, 52], binding: SAMPLE_DATA_BINDINGS.reportingJson }),
|
|
@@ -169,6 +201,14 @@ const DASHBOARD_TEMPLATES = [
|
|
|
169
201
|
id: "creative-review",
|
|
170
202
|
name: "Creative Review",
|
|
171
203
|
description: "Creative artifact embed and approval notes",
|
|
204
|
+
category: "creative",
|
|
205
|
+
bestFor: ["Creative leads", "Designers", "Account managers"],
|
|
206
|
+
tags: ["creative", "review", "approvals"],
|
|
207
|
+
preview: {
|
|
208
|
+
layout: "embed-and-queue",
|
|
209
|
+
summary: "Creative preview embed, approval notes, and review queue"
|
|
210
|
+
},
|
|
211
|
+
dashboard: { name: "Creative Review", status: "draft" },
|
|
172
212
|
widgets: [
|
|
173
213
|
createWidget("iframe", "Creative Preview", { x: 0, y: 0, w: 7, h: 6 }, { url: "" }),
|
|
174
214
|
createWidget("rich-text", "Approval Notes", { x: 7, y: 0, w: 5, h: 3 }, { text: "Feedback, approvals, and revision requests.", binding: { mode: "manual", source: "Manual text", rows: [] } }),
|
|
@@ -189,6 +229,14 @@ const DASHBOARD_TEMPLATES = [
|
|
|
189
229
|
id: "agency-delivery",
|
|
190
230
|
name: "Agency Delivery",
|
|
191
231
|
description: "Agency workstream, KPI, and delivery notes",
|
|
232
|
+
category: "agency",
|
|
233
|
+
bestFor: ["Agencies", "Delivery leads", "Producers"],
|
|
234
|
+
tags: ["agency", "delivery", "ops"],
|
|
235
|
+
preview: {
|
|
236
|
+
layout: "delivery-grid",
|
|
237
|
+
summary: "Delivery board, utilization chart, client commitments, delivery portal"
|
|
238
|
+
},
|
|
239
|
+
dashboard: { name: "Agency Delivery", status: "draft" },
|
|
192
240
|
widgets: [
|
|
193
241
|
createWidget("view", "Delivery Board", { x: 0, y: 0, w: 5, h: 5 }, {
|
|
194
242
|
source: "Tasks",
|
|
@@ -306,9 +354,46 @@ function validateDashboardArray(dashboards, errors) {
|
|
|
306
354
|
if (dashboard.status !== undefined && !["draft", "active", "archived"].includes(dashboard.status)) {
|
|
307
355
|
errors.push(`${prefix}.status must be draft, active, or archived`);
|
|
308
356
|
}
|
|
357
|
+
if (dashboard.tabs !== undefined) {
|
|
358
|
+
validateDashboardTabs(dashboard.tabs, dashboard.activeTabId, `${prefix}.tabs`, errors, new Set());
|
|
359
|
+
}
|
|
360
|
+
if (dashboard.activeTabId !== undefined && typeof dashboard.activeTabId !== "string") {
|
|
361
|
+
errors.push(`${prefix}.activeTabId must be a string`);
|
|
362
|
+
}
|
|
309
363
|
});
|
|
310
364
|
}
|
|
311
365
|
|
|
366
|
+
function validateDashboardTabs(tabs, activeTabId, contextPath, errors, seenWidgetIds) {
|
|
367
|
+
if (!Array.isArray(tabs)) {
|
|
368
|
+
errors.push(`${contextPath} must be an array`);
|
|
369
|
+
return;
|
|
370
|
+
}
|
|
371
|
+
if (!tabs.length) {
|
|
372
|
+
errors.push(`${contextPath} must include at least one tab`);
|
|
373
|
+
return;
|
|
374
|
+
}
|
|
375
|
+
const seenTabIds = new Set();
|
|
376
|
+
tabs.forEach((tab, index) => {
|
|
377
|
+
const tabPrefix = `${contextPath}[${index}]`;
|
|
378
|
+
if (!isPlainObject(tab)) {
|
|
379
|
+
errors.push(`${tabPrefix} must be an object`);
|
|
380
|
+
return;
|
|
381
|
+
}
|
|
382
|
+
if (typeof tab.id !== "string" || !tab.id) {
|
|
383
|
+
errors.push(`${tabPrefix}.id must be a non-empty string`);
|
|
384
|
+
} else if (seenTabIds.has(tab.id)) {
|
|
385
|
+
errors.push(`${tabPrefix}.id duplicates an earlier tab id`);
|
|
386
|
+
} else {
|
|
387
|
+
seenTabIds.add(tab.id);
|
|
388
|
+
}
|
|
389
|
+
if (typeof tab.name !== "string" || !tab.name) errors.push(`${tabPrefix}.name must be a non-empty string`);
|
|
390
|
+
validateWidgetArray(tab.widgets || [], `${tabPrefix}.widgets`, errors, seenWidgetIds);
|
|
391
|
+
});
|
|
392
|
+
if (activeTabId !== undefined && !seenTabIds.has(activeTabId)) {
|
|
393
|
+
errors.push(`${contextPath.replace(/\.tabs$/, "")}.activeTabId must match an existing tab id`);
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
|
|
312
397
|
function validateWidgetArray(widgets, contextPath, errors, seenIds) {
|
|
313
398
|
if (!Array.isArray(widgets)) {
|
|
314
399
|
errors.push(`${contextPath} must be an array`);
|
|
@@ -424,6 +509,220 @@ function validateCanvasConfig(canvas, errors) {
|
|
|
424
509
|
}
|
|
425
510
|
}
|
|
426
511
|
|
|
512
|
+
function validateTemplateWidgetArray(widgets, contextPath, errors) {
|
|
513
|
+
if (!Array.isArray(widgets)) {
|
|
514
|
+
errors.push(`${contextPath} must be an array`);
|
|
515
|
+
return;
|
|
516
|
+
}
|
|
517
|
+
const occupied = new Map();
|
|
518
|
+
widgets.forEach((widget, index) => {
|
|
519
|
+
const prefix = `${contextPath}[${index}]`;
|
|
520
|
+
if (!isPlainObject(widget)) {
|
|
521
|
+
errors.push(`${prefix} must be an object`);
|
|
522
|
+
return;
|
|
523
|
+
}
|
|
524
|
+
if (!KNOWN_WIDGET_KINDS.includes(widget.kind)) {
|
|
525
|
+
errors.push(`${prefix}.kind must be one of ${KNOWN_WIDGET_KINDS.join(", ")}`);
|
|
526
|
+
}
|
|
527
|
+
if (typeof widget.title !== "string" || !widget.title) {
|
|
528
|
+
errors.push(`${prefix}.title must be a non-empty string`);
|
|
529
|
+
}
|
|
530
|
+
if (!isPlainObject(widget.position)) {
|
|
531
|
+
errors.push(`${prefix}.position must be an object`);
|
|
532
|
+
return;
|
|
533
|
+
}
|
|
534
|
+
for (const k of ["x", "y", "w", "h"]) {
|
|
535
|
+
if (!isFiniteInt(widget.position[k])) errors.push(`${prefix}.position.${k} must be a finite integer`);
|
|
536
|
+
}
|
|
537
|
+
if (
|
|
538
|
+
isFiniteInt(widget.position.x) &&
|
|
539
|
+
isFiniteInt(widget.position.w) &&
|
|
540
|
+
(widget.position.x < 0 || widget.position.w < 1 || widget.position.x + widget.position.w > GRID_COLUMNS)
|
|
541
|
+
) {
|
|
542
|
+
errors.push(`${prefix} x/w out of [0..${GRID_COLUMNS}] grid`);
|
|
543
|
+
}
|
|
544
|
+
if (
|
|
545
|
+
isFiniteInt(widget.position.y) &&
|
|
546
|
+
isFiniteInt(widget.position.h) &&
|
|
547
|
+
(widget.position.y < 0 || widget.position.h < 1 || widget.position.y + widget.position.h > GRID_ROWS)
|
|
548
|
+
) {
|
|
549
|
+
errors.push(`${prefix} y/h out of [0..${GRID_ROWS}] grid`);
|
|
550
|
+
}
|
|
551
|
+
if (
|
|
552
|
+
isFiniteInt(widget.position.x) &&
|
|
553
|
+
isFiniteInt(widget.position.y) &&
|
|
554
|
+
isFiniteInt(widget.position.w) &&
|
|
555
|
+
isFiniteInt(widget.position.h)
|
|
556
|
+
) {
|
|
557
|
+
for (let dx = 0; dx < widget.position.w; dx += 1) {
|
|
558
|
+
for (let dy = 0; dy < widget.position.h; dy += 1) {
|
|
559
|
+
const cell = `${widget.position.x + dx}:${widget.position.y + dy}`;
|
|
560
|
+
const previous = occupied.get(cell);
|
|
561
|
+
if (previous) {
|
|
562
|
+
errors.push(`${prefix} overlaps ${previous} at grid cell ${cell}`);
|
|
563
|
+
} else {
|
|
564
|
+
occupied.set(cell, `${prefix}.position`);
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
validateWidgetConfig(widget.kind, widget.config, `${prefix}.config`, errors);
|
|
570
|
+
});
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
function normalizeWorkspaceTemplate(template) {
|
|
574
|
+
if (!isPlainObject(template)) return template;
|
|
575
|
+
const widgets = Array.isArray(template.widgets) ? template.widgets : [];
|
|
576
|
+
const tags = Array.isArray(template.tags) ? template.tags.filter((tag) => typeof tag === "string") : [];
|
|
577
|
+
const bestFor = Array.isArray(template.bestFor) ? template.bestFor.filter((item) => typeof item === "string") : [];
|
|
578
|
+
const preview = isPlainObject(template.preview)
|
|
579
|
+
? { layout: template.preview.layout || "custom", summary: template.preview.summary || "" }
|
|
580
|
+
: { layout: "custom", summary: "" };
|
|
581
|
+
const dashboard = isPlainObject(template.dashboard)
|
|
582
|
+
? {
|
|
583
|
+
name: typeof template.dashboard.name === "string" && template.dashboard.name ? template.dashboard.name : template.name || "Untitled",
|
|
584
|
+
status: ["draft", "active", "archived"].includes(template.dashboard.status) ? template.dashboard.status : "draft"
|
|
585
|
+
}
|
|
586
|
+
: { name: template.name || "Untitled", status: "draft" };
|
|
587
|
+
return {
|
|
588
|
+
id: template.id,
|
|
589
|
+
name: template.name,
|
|
590
|
+
description: typeof template.description === "string" ? template.description : "",
|
|
591
|
+
category: typeof template.category === "string" && template.category ? template.category : "custom",
|
|
592
|
+
bestFor,
|
|
593
|
+
tags,
|
|
594
|
+
widgetCount: widgets.length,
|
|
595
|
+
preview,
|
|
596
|
+
dashboard,
|
|
597
|
+
widgets
|
|
598
|
+
};
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
function validateWorkspaceTemplate(template) {
|
|
602
|
+
if (!isPlainObject(template)) {
|
|
603
|
+
const error = new Error("workspace template must be a plain object");
|
|
604
|
+
error.code = "INVALID_WORKSPACE_TEMPLATE";
|
|
605
|
+
error.details = ["root must be a plain object"];
|
|
606
|
+
throw error;
|
|
607
|
+
}
|
|
608
|
+
const errors = [];
|
|
609
|
+
if (typeof template.id !== "string" || !template.id) errors.push("template.id must be a non-empty string");
|
|
610
|
+
if (typeof template.name !== "string" || !template.name) errors.push("template.name must be a non-empty string");
|
|
611
|
+
if (template.description !== undefined && typeof template.description !== "string") {
|
|
612
|
+
errors.push("template.description must be a string");
|
|
613
|
+
}
|
|
614
|
+
if (template.category !== undefined && typeof template.category !== "string") {
|
|
615
|
+
errors.push("template.category must be a string");
|
|
616
|
+
}
|
|
617
|
+
if (template.bestFor !== undefined) validateStringArray(template.bestFor, "template.bestFor", errors);
|
|
618
|
+
if (template.tags !== undefined) validateStringArray(template.tags, "template.tags", errors);
|
|
619
|
+
if (template.preview !== undefined) {
|
|
620
|
+
if (!isPlainObject(template.preview)) errors.push("template.preview must be a plain object");
|
|
621
|
+
}
|
|
622
|
+
if (template.dashboard !== undefined) {
|
|
623
|
+
if (!isPlainObject(template.dashboard)) {
|
|
624
|
+
errors.push("template.dashboard must be a plain object");
|
|
625
|
+
} else {
|
|
626
|
+
if (typeof template.dashboard.name !== "string" || !template.dashboard.name) {
|
|
627
|
+
errors.push("template.dashboard.name must be a non-empty string");
|
|
628
|
+
}
|
|
629
|
+
if (template.dashboard.status !== undefined && !["draft", "active", "archived"].includes(template.dashboard.status)) {
|
|
630
|
+
errors.push("template.dashboard.status must be draft, active, or archived");
|
|
631
|
+
}
|
|
632
|
+
}
|
|
633
|
+
}
|
|
634
|
+
if (template.widgets !== undefined) {
|
|
635
|
+
validateTemplateWidgetArray(template.widgets, "template.widgets", errors);
|
|
636
|
+
}
|
|
637
|
+
if (errors.length) {
|
|
638
|
+
const error = new Error(`invalid workspace template: ${errors.join("; ")}`);
|
|
639
|
+
error.code = "INVALID_WORKSPACE_TEMPLATE";
|
|
640
|
+
error.details = errors;
|
|
641
|
+
throw error;
|
|
642
|
+
}
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
function requireIdFactory(idFactory) {
|
|
646
|
+
if (typeof idFactory !== "function") {
|
|
647
|
+
const error = new Error("idFactory function is required to clone a template");
|
|
648
|
+
error.code = "MISSING_TEMPLATE_ID_FACTORY";
|
|
649
|
+
throw error;
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
function cloneTemplateWidgets(template, idFactory) {
|
|
654
|
+
const widgets = Array.isArray(template.widgets) ? template.widgets : [];
|
|
655
|
+
return widgets.map((widget) => ({
|
|
656
|
+
id: idFactory("widget"),
|
|
657
|
+
kind: widget.kind,
|
|
658
|
+
title: widget.title,
|
|
659
|
+
position: { ...widget.position },
|
|
660
|
+
config: widget.config !== undefined ? JSON.parse(JSON.stringify(widget.config)) : defaultConfigFor(widget.kind)
|
|
661
|
+
}));
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
function cloneTemplateToTab(template, options = {}) {
|
|
665
|
+
validateWorkspaceTemplate(template);
|
|
666
|
+
requireIdFactory(options.idFactory);
|
|
667
|
+
const widgets = cloneTemplateWidgets(template, options.idFactory);
|
|
668
|
+
return {
|
|
669
|
+
id: options.idFactory("tab"),
|
|
670
|
+
name: typeof options.tabName === "string" && options.tabName ? options.tabName : template.name,
|
|
671
|
+
widgets
|
|
672
|
+
};
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
function cloneTemplateToDashboard(template, options = {}) {
|
|
676
|
+
validateWorkspaceTemplate(template);
|
|
677
|
+
requireIdFactory(options.idFactory);
|
|
678
|
+
const tab = cloneTemplateToTab(template, { tabName: template.name, idFactory: options.idFactory });
|
|
679
|
+
const baseDashboard = isPlainObject(template.dashboard) ? template.dashboard : {};
|
|
680
|
+
return {
|
|
681
|
+
dashboard: {
|
|
682
|
+
id: options.idFactory("dashboard"),
|
|
683
|
+
name: typeof options.dashboardName === "string" && options.dashboardName ? options.dashboardName : baseDashboard.name || template.name,
|
|
684
|
+
createdBy: "Workspace owner",
|
|
685
|
+
updatedAt: "new",
|
|
686
|
+
status: ["draft", "active", "archived"].includes(baseDashboard.status) ? baseDashboard.status : "draft"
|
|
687
|
+
},
|
|
688
|
+
tab
|
|
689
|
+
};
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
function unwrapWorkspaceTemplateImport(parsed) {
|
|
693
|
+
if (!isPlainObject(parsed)) {
|
|
694
|
+
const error = new Error("template import must be a plain object");
|
|
695
|
+
error.code = "INVALID_WORKSPACE_TEMPLATE_IMPORT";
|
|
696
|
+
throw error;
|
|
697
|
+
}
|
|
698
|
+
if (parsed.kind !== undefined && parsed.kind !== WORKSPACE_TEMPLATE_KIND) {
|
|
699
|
+
const error = new Error(`unrecognized template kind: ${parsed.kind}`);
|
|
700
|
+
error.code = "INVALID_WORKSPACE_TEMPLATE_IMPORT";
|
|
701
|
+
throw error;
|
|
702
|
+
}
|
|
703
|
+
if (parsed.kind === WORKSPACE_TEMPLATE_KIND) {
|
|
704
|
+
if (!isPlainObject(parsed.payload)) {
|
|
705
|
+
const error = new Error("template import payload must be a plain object");
|
|
706
|
+
error.code = "INVALID_WORKSPACE_TEMPLATE_IMPORT";
|
|
707
|
+
throw error;
|
|
708
|
+
}
|
|
709
|
+
return parsed.payload;
|
|
710
|
+
}
|
|
711
|
+
return parsed;
|
|
712
|
+
}
|
|
713
|
+
|
|
714
|
+
function wrapWorkspaceTemplateExport(payload, metadata = {}) {
|
|
715
|
+
return {
|
|
716
|
+
version: WORKSPACE_TEMPLATE_VERSION,
|
|
717
|
+
kind: WORKSPACE_TEMPLATE_KIND,
|
|
718
|
+
exportedAt: new Date().toISOString(),
|
|
719
|
+
source: WORKSPACE_TEMPLATE_SOURCE,
|
|
720
|
+
name: typeof metadata.name === "string" && metadata.name ? metadata.name : "Workspace template",
|
|
721
|
+
description: typeof metadata.description === "string" ? metadata.description : "",
|
|
722
|
+
payload
|
|
723
|
+
};
|
|
724
|
+
}
|
|
725
|
+
|
|
427
726
|
function validateWorkspaceConfig(nextConfig) {
|
|
428
727
|
if (!isPlainObject(nextConfig)) {
|
|
429
728
|
const error = new Error("workspace config must be a plain object");
|
|
@@ -456,6 +755,16 @@ export {
|
|
|
456
755
|
SAMPLE_DATA_BINDINGS,
|
|
457
756
|
SAMPLE_VIEW_ROWS,
|
|
458
757
|
WIDGET_SCHEMA_CONTRACTS,
|
|
758
|
+
WORKSPACE_TEMPLATE_KIND,
|
|
759
|
+
WORKSPACE_TEMPLATE_SOURCE,
|
|
760
|
+
WORKSPACE_TEMPLATE_VERSION,
|
|
761
|
+
cloneTemplateToDashboard,
|
|
762
|
+
cloneTemplateToTab,
|
|
459
763
|
defaultConfigFor,
|
|
460
|
-
|
|
764
|
+
normalizeWorkspaceTemplate,
|
|
765
|
+
unwrapWorkspaceTemplateImport,
|
|
766
|
+
validateTemplateWidgetArray,
|
|
767
|
+
validateWorkspaceConfig,
|
|
768
|
+
validateWorkspaceTemplate,
|
|
769
|
+
wrapWorkspaceTemplateExport
|
|
461
770
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@growthub/cli",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.8",
|
|
4
4
|
"description": "Growthub Local is a control plane for forked worker kits. The CLI is the executor, the hosted app is the identity authority, the worker kit is the unit of portable agent infrastructure, and the fork is the operator's personal branch of that infrastructure — policy-governed, trace-backed, and self-healing.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|