@growthub/cli 0.3.43 → 0.3.45

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 CHANGED
@@ -35,6 +35,7 @@ Use this when you want to create or reopen a full Growthub local surface.
35
35
 
36
36
  ```bash
37
37
  growthub
38
+ growthub list
38
39
  growthub discover
39
40
  growthub onboard
40
41
  growthub run
@@ -55,6 +56,9 @@ Use this when you want a working-directory-ready environment for an agent.
55
56
  ### Discovery
56
57
 
57
58
  ```bash
59
+ # Interactive discovery hub
60
+ growthub list
61
+
58
62
  # Interactive browser — type filter → kit selector → actions
59
63
  growthub kit
60
64
 
@@ -142,7 +146,7 @@ For the agent-facing extension workflow, see [docs/CLI_TEMPLATE_CONTRIBUTION_EXT
142
146
 
143
147
  ## Development Notes
144
148
 
145
- - `@growthub/cli` version: `0.3.43`
149
+ - `@growthub/cli` version: `0.3.45`
146
150
  - Node.js: `>=20`
147
151
  - Source of truth repo: [Growthub Local](https://github.com/Growthub-ai/growthub-local)
148
152
 
package/dist/index.js CHANGED
@@ -6657,9 +6657,9 @@ import pc2 from "picocolors";
6657
6657
  function printPaperclipCliBanner() {
6658
6658
  const lines = [
6659
6659
  "",
6660
- ...GROWTHUB_ART.map((line) => pc2.cyan(line)),
6661
- pc2.blue(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"),
6662
- pc2.bold(pc2.white(` ${TAGLINE}`)),
6660
+ ...GROWTHUB_ART,
6661
+ pc2.dim(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"),
6662
+ pc2.bold(` ${TAGLINE}`),
6663
6663
  ""
6664
6664
  ];
6665
6665
  console.log(lines.join("\n"));
@@ -14727,7 +14727,7 @@ applyDataDirOverride(bootstrapOptions, {
14727
14727
  loadPaperclipEnvFile(bootstrapOptions.config);
14728
14728
  var bootstrapConfig = readConfig(resolveConfigPath(bootstrapOptions.config));
14729
14729
  var surfaceRuntime = initializeSurfaceRuntimeContract(resolveSurfaceProfile(bootstrapConfig) ?? void 0);
14730
- program.name("growthub").description("Growthub CLI \u2014 setup, configure, and run your local Growthub instance").version("0.3.43").addHelpText("after", `
14730
+ program.name("growthub").description("Growthub CLI \u2014 setup, configure, and run your local Growthub instance").version("0.3.45").addHelpText("after", `
14731
14731
  Worker Kits (agent execution environments):
14732
14732
 
14733
14733
  Discovery:
@@ -14762,6 +14762,9 @@ Instance setup:
14762
14762
  program.action(async () => {
14763
14763
  await runDiscoveryHub();
14764
14764
  });
14765
+ program.command("list").description("Open the interactive Growthub discovery hub").action(async () => {
14766
+ await runDiscoveryHub();
14767
+ });
14765
14768
  program.hook("preAction", (_thisCommand, actionCommand) => {
14766
14769
  const options = actionCommand.optsWithGlobals();
14767
14770
  const optionNames = new Set(actionCommand.options.map((option) => option.attributeName()));
@@ -1,7 +1,5 @@
1
1
  import { Router } from "express";
2
2
  import multer from "multer";
3
- import createDOMPurify from "dompurify";
4
- import { JSDOM } from "jsdom";
5
3
  import { createAssetImageMetadataSchema } from "@paperclipai/shared";
6
4
  import { assetService, logActivity } from "../services/index.js";
7
5
  import { isAllowedContentType, MAX_ATTACHMENT_BYTES } from "../attachment-types.js";
@@ -15,53 +13,32 @@ const ALLOWED_COMPANY_LOGO_CONTENT_TYPES = new Set([
15
13
  "image/gif",
16
14
  SVG_CONTENT_TYPE,
17
15
  ]);
16
+ const DISALLOWED_SVG_TAGS = /<\/?\s*(script|foreignObject)\b[^>]*>/gi;
17
+ const DISALLOWED_SVG_BLOCKS = /<\s*(script|foreignObject)\b[^>]*>[\s\S]*?<\s*\/\s*\1\s*>/gi;
18
+ const XML_DECLARATIONS_AND_COMMENTS = /<\?(?:xml|[\s\S]*?)\?>|<!--[\s\S]*?-->|<!DOCTYPE[\s\S]*?>/gi;
19
+ const EVENT_HANDLER_ATTRS = /\s+on[\w:-]+\s*=\s*(?:"[^"]*"|'[^']*'|[^\s>]+)/gi;
20
+ const STYLE_ATTRS = /\s+style\s*=\s*(?:"[^"]*"|'[^']*'|[^\s>]+)/gi;
21
+ const HREF_ATTRS = /\s+(href|xlink:href)\s*=\s*("([^"]*)"|'([^']*)'|([^\s>]+))/gi;
22
+ const SVG_ROOT_PATTERN = /^<svg\b[\s\S]*<\/svg>$/i;
18
23
  function sanitizeSvgBuffer(input) {
19
24
  const raw = input.toString("utf8").trim();
20
25
  if (!raw)
21
26
  return null;
22
- const baseDom = new JSDOM("");
23
- const domPurify = createDOMPurify(baseDom.window);
24
- domPurify.addHook("uponSanitizeAttribute", (_node, data) => {
25
- const attrName = data.attrName.toLowerCase();
26
- const attrValue = (data.attrValue ?? "").trim();
27
- if (attrName.startsWith("on")) {
28
- data.keepAttr = false;
29
- return;
30
- }
31
- if ((attrName === "href" || attrName === "xlink:href") && attrValue && !attrValue.startsWith("#")) {
32
- data.keepAttr = false;
33
- }
34
- });
35
- let parsedDom = null;
36
27
  try {
37
- const sanitized = domPurify.sanitize(raw, {
38
- USE_PROFILES: { svg: true, svgFilters: true, html: false },
39
- FORBID_TAGS: ["script", "foreignObject"],
40
- FORBID_CONTENTS: ["script", "foreignObject"],
41
- RETURN_TRUSTED_TYPE: false,
42
- });
43
- parsedDom = new JSDOM(sanitized, { contentType: SVG_CONTENT_TYPE });
44
- const document = parsedDom.window.document;
45
- const root = document.documentElement;
46
- if (!root || root.tagName.toLowerCase() !== "svg")
28
+ const normalized = raw.replace(XML_DECLARATIONS_AND_COMMENTS, "").trim();
29
+ if (!SVG_ROOT_PATTERN.test(normalized))
47
30
  return null;
48
- for (const el of Array.from(root.querySelectorAll("script, foreignObject"))) {
49
- el.remove();
50
- }
51
- for (const el of Array.from(root.querySelectorAll("*"))) {
52
- for (const attr of Array.from(el.attributes)) {
53
- const attrName = attr.name.toLowerCase();
54
- const attrValue = attr.value.trim();
55
- if (attrName.startsWith("on")) {
56
- el.removeAttribute(attr.name);
57
- continue;
58
- }
59
- if ((attrName === "href" || attrName === "xlink:href") && attrValue && !attrValue.startsWith("#")) {
60
- el.removeAttribute(attr.name);
61
- }
62
- }
63
- }
64
- const output = root.outerHTML.trim();
31
+ const sanitized = normalized
32
+ .replace(DISALLOWED_SVG_BLOCKS, "")
33
+ .replace(DISALLOWED_SVG_TAGS, "")
34
+ .replace(EVENT_HANDLER_ATTRS, "")
35
+ .replace(STYLE_ATTRS, "")
36
+ .replace(HREF_ATTRS, (_match, attrName, _fullValue, doubleQuoted, singleQuoted, bare) => {
37
+ const attrValue = (doubleQuoted ?? singleQuoted ?? bare ?? "").trim();
38
+ return attrValue.startsWith("#") ? ` ${attrName}="${attrValue}"` : "";
39
+ })
40
+ .trim();
41
+ const output = sanitized.trim();
65
42
  if (!output || !/^<svg[\s>]/i.test(output))
66
43
  return null;
67
44
  return Buffer.from(output, "utf8");
@@ -69,10 +46,6 @@ function sanitizeSvgBuffer(input) {
69
46
  catch {
70
47
  return null;
71
48
  }
72
- finally {
73
- parsedDom?.window.close();
74
- baseDom.window.close();
75
- }
76
49
  }
77
50
  export function assetRoutes(db, storage) {
78
51
  const router = Router();
@@ -306,4 +279,4 @@ export function assetRoutes(db, storage) {
306
279
  });
307
280
  return router;
308
281
  }
309
- //# sourceMappingURL=assets.js.map
282
+ //# sourceMappingURL=assets.js.map
@@ -0,0 +1,21 @@
1
+ import pc from "picocolors";
2
+ const GROWTHUB_ART = [
3
+ " ██████╗ ██████╗ ██████╗ ██╗ ██╗████████╗██╗ ██╗██╗ ██╗██████╗ ",
4
+ "██╔════╝ ██╔══██╗██╔═══██╗██║ ██║╚══██╔══╝██║ ██║██║ ██║██╔══██╗",
5
+ "██║ ███╗██████╔╝██║ ██║██║ █╗ ██║ ██║ ███████║██║ ██║██████╔╝",
6
+ "██║ ██║██╔══██╗██║ ██║██║███╗██║ ██║ ██╔══██║██║ ██║██╔══██╗",
7
+ "╚██████╔╝██║ ██║╚██████╔╝╚███╔███╔╝ ██║ ██║ ██║╚██████╔╝██████╔╝",
8
+ " ╚═════╝ ╚═╝ ╚═╝ ╚═════╝ ╚══╝╚══╝ ╚═╝ ╚═╝ ╚═╝ ╚═════╝ ╚═════╝ ",
9
+ ];
10
+ const TAGLINE = "Growth infrastructure over a stable agentic substrate";
11
+ export function printPaperclipCliBanner() {
12
+ const lines = [
13
+ "",
14
+ ...GROWTHUB_ART,
15
+ pc.dim(" ───────────────────────────────────────────────────────"),
16
+ pc.bold(` ${TAGLINE}`),
17
+ "",
18
+ ];
19
+ console.log(lines.join("\n"));
20
+ }
21
+ //# sourceMappingURL=banner.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@growthub/cli",
3
- "version": "0.3.43",
3
+ "version": "0.3.45",
4
4
  "description": "Growthub CLI — orchestrate AI agent teams to run a business",
5
5
  "type": "module",
6
6
  "bin": {
@@ -37,20 +37,17 @@
37
37
  "dependencies": {
38
38
  "@aws-sdk/client-s3": "^3.888.0",
39
39
  "@clack/prompts": "^0.10.0",
40
- "@paperclipai/server": "0.3.1",
41
40
  "ajv": "^8.18.0",
42
41
  "ajv-formats": "^3.0.1",
43
42
  "better-auth": "1.4.18",
44
43
  "chokidar": "^4.0.3",
45
44
  "commander": "^13.1.0",
46
45
  "detect-port": "^2.1.0",
47
- "dompurify": "^3.3.2",
48
46
  "dotenv": "^17.0.1",
49
- "drizzle-orm": "0.38.4",
47
+ "drizzle-orm": "^0.45.2",
50
48
  "embedded-postgres": "^18.1.0-beta.16",
51
49
  "express": "^5.1.0",
52
50
  "hermes-paperclip-adapter": "0.1.1",
53
- "jsdom": "^28.1.0",
54
51
  "multer": "^2.0.2",
55
52
  "open": "^11.0.0",
56
53
  "picocolors": "^1.1.1",
@@ -59,6 +56,6 @@
59
56
  "pino-pretty": "^13.1.3",
60
57
  "postgres": "^3.4.5",
61
58
  "ws": "^8.19.0",
62
- "zod": "^3.24.2"
59
+ "zod": "^4.3.6"
63
60
  }
64
61
  }