@codyswann/lisa 2.219.0 → 2.220.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.
Files changed (81) hide show
  1. package/all/copy-contents/gitignore +6 -1
  2. package/cdk/copy-overwrite/eslint.config.ts +25 -3
  3. package/cdk/copy-overwrite/eslint.slow.config.ts +28 -1
  4. package/dist/core/learnings-document.d.ts +22 -0
  5. package/dist/core/learnings-document.d.ts.map +1 -0
  6. package/dist/core/learnings-document.js +106 -0
  7. package/dist/core/learnings-document.js.map +1 -0
  8. package/dist/core/learnings-writer.d.ts +1 -13
  9. package/dist/core/learnings-writer.d.ts.map +1 -1
  10. package/dist/core/learnings-writer.js +2 -70
  11. package/dist/core/learnings-writer.js.map +1 -1
  12. package/expo/copy-overwrite/eslint.config.ts +26 -3
  13. package/expo/copy-overwrite/eslint.slow.config.ts +32 -1
  14. package/expo/copy-overwrite/scripts/check-e2e-coverage.mjs +1 -1
  15. package/expo/create-only/e2e.thresholds.json +3 -2
  16. package/expo/package-lisa/package.lisa.json +4 -2
  17. package/harper-fabric/copy-overwrite/eslint.config.ts +24 -2
  18. package/nestjs/copy-overwrite/eslint.config.ts +27 -3
  19. package/nestjs/copy-overwrite/eslint.slow.config.ts +33 -2
  20. package/package.json +2 -1
  21. package/phaser/copy-contents/gitignore +13 -0
  22. package/phaser/copy-overwrite/.github/workflows/ci.yml +3 -0
  23. package/phaser/copy-overwrite/.husky/pre-push.verify +4 -1
  24. package/phaser/copy-overwrite/eslint.config.ts +28 -3
  25. package/plugins/lisa/.claude-plugin/plugin.json +1 -1
  26. package/plugins/lisa/.codex-plugin/plugin.json +1 -1
  27. package/plugins/lisa-agy/plugin.json +1 -1
  28. package/plugins/lisa-cdk/.claude-plugin/plugin.json +1 -1
  29. package/plugins/lisa-cdk/.codex-plugin/plugin.json +1 -1
  30. package/plugins/lisa-cdk-agy/plugin.json +1 -1
  31. package/plugins/lisa-cdk-copilot/.claude-plugin/plugin.json +1 -1
  32. package/plugins/lisa-cdk-cursor/.claude-plugin/plugin.json +1 -1
  33. package/plugins/lisa-copilot/.claude-plugin/plugin.json +1 -1
  34. package/plugins/lisa-cursor/.claude-plugin/plugin.json +1 -1
  35. package/plugins/lisa-expo/.claude-plugin/plugin.json +1 -1
  36. package/plugins/lisa-expo/.codex-plugin/plugin.json +1 -1
  37. package/plugins/lisa-expo-agy/plugin.json +1 -1
  38. package/plugins/lisa-expo-copilot/.claude-plugin/plugin.json +1 -1
  39. package/plugins/lisa-expo-cursor/.claude-plugin/plugin.json +1 -1
  40. package/plugins/lisa-harper-fabric/.claude-plugin/plugin.json +1 -1
  41. package/plugins/lisa-harper-fabric/.codex-plugin/plugin.json +1 -1
  42. package/plugins/lisa-harper-fabric-agy/plugin.json +1 -1
  43. package/plugins/lisa-harper-fabric-copilot/.claude-plugin/plugin.json +1 -1
  44. package/plugins/lisa-harper-fabric-cursor/.claude-plugin/plugin.json +1 -1
  45. package/plugins/lisa-nestjs/.claude-plugin/plugin.json +1 -1
  46. package/plugins/lisa-nestjs/.codex-plugin/plugin.json +1 -1
  47. package/plugins/lisa-nestjs-agy/plugin.json +1 -1
  48. package/plugins/lisa-nestjs-copilot/.claude-plugin/plugin.json +1 -1
  49. package/plugins/lisa-nestjs-cursor/.claude-plugin/plugin.json +1 -1
  50. package/plugins/lisa-openclaw/.claude-plugin/plugin.json +1 -1
  51. package/plugins/lisa-openclaw/.codex-plugin/plugin.json +1 -1
  52. package/plugins/lisa-openclaw-agy/plugin.json +1 -1
  53. package/plugins/lisa-openclaw-copilot/.claude-plugin/plugin.json +1 -1
  54. package/plugins/lisa-openclaw-cursor/.claude-plugin/plugin.json +1 -1
  55. package/plugins/lisa-phaser/.claude-plugin/plugin.json +1 -1
  56. package/plugins/lisa-phaser/.codex-plugin/plugin.json +1 -1
  57. package/plugins/lisa-phaser-agy/plugin.json +1 -1
  58. package/plugins/lisa-phaser-copilot/.claude-plugin/plugin.json +1 -1
  59. package/plugins/lisa-phaser-cursor/.claude-plugin/plugin.json +1 -1
  60. package/plugins/lisa-rails/.claude-plugin/plugin.json +1 -1
  61. package/plugins/lisa-rails/.codex-plugin/plugin.json +1 -1
  62. package/plugins/lisa-rails-agy/plugin.json +1 -1
  63. package/plugins/lisa-rails-copilot/.claude-plugin/plugin.json +1 -1
  64. package/plugins/lisa-rails-cursor/.claude-plugin/plugin.json +1 -1
  65. package/plugins/lisa-typescript/.claude-plugin/plugin.json +1 -1
  66. package/plugins/lisa-typescript/.codex-plugin/plugin.json +1 -1
  67. package/plugins/lisa-typescript-agy/plugin.json +1 -1
  68. package/plugins/lisa-typescript-copilot/.claude-plugin/plugin.json +1 -1
  69. package/plugins/lisa-typescript-cursor/.claude-plugin/plugin.json +1 -1
  70. package/plugins/lisa-wiki/.claude-plugin/plugin.json +1 -1
  71. package/plugins/lisa-wiki/.codex-plugin/plugin.json +1 -1
  72. package/plugins/lisa-wiki-agy/plugin.json +1 -1
  73. package/plugins/lisa-wiki-copilot/.claude-plugin/plugin.json +1 -1
  74. package/plugins/lisa-wiki-cursor/.claude-plugin/plugin.json +1 -1
  75. package/rails/create-only/.github/workflows/ci.yml +12 -0
  76. package/scripts/check-learnings-budget.ts +219 -0
  77. package/typescript/copy-contents/.husky/pre-commit +4 -1
  78. package/typescript/copy-overwrite/audit.ignore.config.json +0 -5
  79. package/typescript/copy-overwrite/eslint.config.ts +27 -3
  80. package/typescript/copy-overwrite/eslint.slow.config.ts +32 -1
  81. package/typescript/package-lisa/package.lisa.json +4 -2
@@ -11,7 +11,19 @@ on:
11
11
  jobs:
12
12
  quality:
13
13
  name: Quality Checks
14
+ # quality-rails.yml declares checks:write + pull-requests:write. A reusable
15
+ # workflow can only use permissions the caller grants, so on repos whose
16
+ # default GITHUB_TOKEN is read-only this block is required — without it the
17
+ # called workflow fails at startup (startup_failure) and required checks
18
+ # never report.
19
+ permissions:
20
+ contents: read
21
+ checks: write
22
+ pull-requests: write
14
23
  uses: CodySwannGT/lisa/.github/workflows/quality-rails.yml@main
24
+ # Rails projects default to PostgreSQL. For a MySQL app, uncomment:
25
+ # with:
26
+ # database_type: 'mysql'
15
27
  secrets:
16
28
  GITGUARDIAN_API_KEY: ${{ secrets.GITGUARDIAN_API_KEY }}
17
29
  FOSSA_API_KEY: ${{ secrets.FOSSA_API_KEY }}
@@ -0,0 +1,219 @@
1
+ /** CI gate for the canonical project learnings document and its hard budgets. */
2
+ import { constants, existsSync } from "node:fs";
3
+ import { open } from "node:fs/promises";
4
+ import * as path from "node:path";
5
+ import { pathToFileURL } from "node:url";
6
+ import type * as LearningsModule from "@codyswann/lisa/learnings";
7
+
8
+ type LearningsCheckerModule = Pick<
9
+ typeof LearningsModule,
10
+ | "LEARNINGS_CONTRACT"
11
+ | "estimateLearningTokens"
12
+ | "parseLearningsFile"
13
+ | "renderLearningsFile"
14
+ | "validateLearningEntry"
15
+ >;
16
+
17
+ const DEFAULT_LEARNINGS_FILE = path.resolve(
18
+ import.meta.dir,
19
+ "..",
20
+ "all",
21
+ "create-only",
22
+ ".claude",
23
+ "rules",
24
+ "PROJECT_LEARNINGS.md"
25
+ );
26
+
27
+ /** Run the package-facing checker with zero or one explicit file path. */
28
+ async function main(): Promise<void> {
29
+ const arguments_ = process.argv.slice(2);
30
+ if (arguments_.length > 1) {
31
+ fail("Usage: bun run check:learnings-budget -- [PROJECT_LEARNINGS.md]");
32
+ }
33
+
34
+ const file =
35
+ arguments_.length === 0
36
+ ? DEFAULT_LEARNINGS_FILE
37
+ : path.resolve(process.cwd(), arguments_[0] as string);
38
+
39
+ try {
40
+ const {
41
+ LEARNINGS_CONTRACT,
42
+ estimateLearningTokens,
43
+ parseLearningsFile,
44
+ renderLearningsFile,
45
+ validateLearningEntry,
46
+ } = await loadLearningsModule();
47
+ const content = await readBoundedRegularFile(
48
+ file,
49
+ LEARNINGS_CONTRACT.maxTokens
50
+ );
51
+ const measuredTokens = estimateLearningTokens(content);
52
+ if (measuredTokens > LEARNINGS_CONTRACT.maxTokens) {
53
+ throw new Error(
54
+ `maxTokens exceeded: measured ${measuredTokens}, allowed ${LEARNINGS_CONTRACT.maxTokens}`
55
+ );
56
+ }
57
+
58
+ const entries = parseLearningsFile(content);
59
+ for (const entry of entries) {
60
+ validateLearningEntry(entry);
61
+ }
62
+ if (renderLearningsFile(entries) !== content) {
63
+ throw new Error("non-canonical project learnings format");
64
+ }
65
+
66
+ console.log(
67
+ `${formatDiagnosticPath(file)}: learnings budget passed (${entries.length}/${LEARNINGS_CONTRACT.maxEntries} entries, ${measuredTokens}/${LEARNINGS_CONTRACT.maxTokens} maxTokens)`
68
+ );
69
+ } catch (error) {
70
+ fail(`${formatDiagnosticPath(file)}: ${formatErrorDetail(error)}`);
71
+ }
72
+ }
73
+
74
+ /**
75
+ * Render a caught failure without allowing filesystem paths or control bytes
76
+ * embedded in an Error message to forge additional terminal/CI output.
77
+ * @param error - Unknown thrown failure
78
+ * @returns Stable, single-line diagnostic detail
79
+ */
80
+ function formatErrorDetail(error: unknown): string {
81
+ if (error !== null && typeof error === "object") {
82
+ const code = readOwnString(error, "code");
83
+ if (code !== undefined && /^[A-Z][A-Z0-9_]*$/u.test(code)) {
84
+ const syscall = readOwnString(error, "syscall");
85
+ return syscall === undefined
86
+ ? `filesystem error ${code}`
87
+ : `filesystem error ${code} during ${escapeDiagnosticText(syscall)}`;
88
+ }
89
+ }
90
+ const message = error instanceof Error ? error.message : String(error);
91
+ return escapeDiagnosticText(message);
92
+ }
93
+
94
+ /** Read one inert own string property without invoking an accessor. */
95
+ function readOwnString(candidate: object, key: string): string | undefined {
96
+ const descriptor = Object.getOwnPropertyDescriptor(candidate, key);
97
+ return descriptor !== undefined &&
98
+ "value" in descriptor &&
99
+ typeof descriptor.value === "string"
100
+ ? descriptor.value
101
+ : undefined;
102
+ }
103
+
104
+ /** Escape terminal controls while retaining ordinary diagnostic wording. */
105
+ function escapeDiagnosticText(value: string): string {
106
+ const jsonBody = JSON.stringify(value).slice(1, -1);
107
+ return jsonBody.replace(
108
+ /[\u007F-\u009F\u2028\u2029]/gu,
109
+ character => `\\u${character.charCodeAt(0).toString(16).padStart(4, "0")}`
110
+ );
111
+ }
112
+
113
+ /** Quote one path after applying terminal-safe JSON-style escaping. */
114
+ function formatDiagnosticPath(file: string): string {
115
+ return `"${escapeDiagnosticText(file)}"`;
116
+ }
117
+
118
+ /**
119
+ * Read at most one byte beyond the hard budget from one verified regular-file
120
+ * handle. Non-blocking open prevents a FIFO path from stalling the CI gate.
121
+ * @param file - Absolute candidate learnings path
122
+ * @param maximumBytes - Shared executable maxTokens byte ceiling
123
+ * @returns Strictly decoded UTF-8 content within the byte ceiling
124
+ */
125
+ async function readBoundedRegularFile(
126
+ file: string,
127
+ maximumBytes: number
128
+ ): Promise<string> {
129
+ const handle = await open(file, constants.O_RDONLY | constants.O_NONBLOCK);
130
+ try {
131
+ const before = await handle.stat({ bigint: true });
132
+ if (!before.isFile()) {
133
+ throw new Error("unsafe input: expected a regular file");
134
+ }
135
+ if (before.size > BigInt(maximumBytes)) {
136
+ throw new Error(
137
+ `maxTokens exceeded: measured ${before.size}, allowed ${maximumBytes}`
138
+ );
139
+ }
140
+
141
+ const buffer = Buffer.allocUnsafe(maximumBytes + 1);
142
+ let bytesRead = 0;
143
+ while (bytesRead < buffer.length) {
144
+ const result = await handle.read(
145
+ buffer,
146
+ bytesRead,
147
+ buffer.length - bytesRead,
148
+ null
149
+ );
150
+ if (result.bytesRead === 0) {
151
+ break;
152
+ }
153
+ bytesRead += result.bytesRead;
154
+ }
155
+ if (bytesRead > maximumBytes) {
156
+ throw new Error(
157
+ `maxTokens exceeded: measured at least ${bytesRead}, allowed ${maximumBytes}`
158
+ );
159
+ }
160
+
161
+ const after = await handle.stat({ bigint: true });
162
+ if (
163
+ after.dev !== before.dev ||
164
+ after.ino !== before.ino ||
165
+ after.size !== before.size ||
166
+ after.mtimeNs !== before.mtimeNs ||
167
+ after.ctimeNs !== before.ctimeNs
168
+ ) {
169
+ throw new Error("unsafe input: file changed during bounded read");
170
+ }
171
+ return new TextDecoder("utf-8", { fatal: true }).decode(
172
+ buffer.subarray(0, bytesRead)
173
+ );
174
+ } finally {
175
+ await handle.close();
176
+ }
177
+ }
178
+
179
+ /**
180
+ * Load the executable contract from current source in a checkout or compiled
181
+ * output in an npm package. The `.js` source specifier keeps Bun development
182
+ * runs aligned with TypeScript's NodeNext resolution while publishing no
183
+ * runtime dependency on the excluded `src` tree.
184
+ * @returns Canonical executable learnings module
185
+ */
186
+ async function loadLearningsModule(): Promise<LearningsCheckerModule> {
187
+ const packageRoot = path.resolve(import.meta.dir, "..");
188
+ const sourceTypescript = path.join(
189
+ packageRoot,
190
+ "src",
191
+ "core",
192
+ "learnings.ts"
193
+ );
194
+ const runtimeRoot = path.join(
195
+ packageRoot,
196
+ existsSync(sourceTypescript) ? "src" : "dist",
197
+ "core"
198
+ );
199
+ const [contract, document, entry] = await Promise.all([
200
+ import(pathToFileURL(path.join(runtimeRoot, "learnings-contract.js")).href),
201
+ import(pathToFileURL(path.join(runtimeRoot, "learnings-document.js")).href),
202
+ import(pathToFileURL(path.join(runtimeRoot, "learnings-entry.js")).href),
203
+ ]);
204
+ return {
205
+ LEARNINGS_CONTRACT: contract.LEARNINGS_CONTRACT,
206
+ estimateLearningTokens: contract.estimateLearningTokens,
207
+ parseLearningsFile: document.parseLearningsFile,
208
+ renderLearningsFile: document.renderLearningsFile,
209
+ validateLearningEntry: entry.validateLearningEntry,
210
+ } as LearningsCheckerModule;
211
+ }
212
+
213
+ /** Print one deterministic failure diagnostic and exit non-zero. */
214
+ function fail(message: string): never {
215
+ console.error(`check:learnings-budget: ${message}`);
216
+ process.exit(1);
217
+ }
218
+
219
+ await main();
@@ -51,7 +51,10 @@ if command -v gitleaks >/dev/null 2>&1; then
51
51
  GITLEAKS_IGNORE_ARGS=""
52
52
  GITLEAKS_COMBINED_IGNORE=""
53
53
  if [ -f ".gitleaksignore.local" ]; then
54
- GITLEAKS_COMBINED_IGNORE="$(mktemp)"
54
+ # Explicit template with a trailing X run keeps this portable: BSD/macOS
55
+ # `mktemp` requires a template argument (bare `mktemp` errors), while GNU
56
+ # `mktemp` accepts it too.
57
+ GITLEAKS_COMBINED_IGNORE="$(mktemp "${TMPDIR:-/tmp}/gitleaks-ignore.XXXXXXXX")"
55
58
  if [ -f ".gitleaksignore" ]; then
56
59
  cat .gitleaksignore > "$GITLEAKS_COMBINED_IGNORE"
57
60
  printf '\n' >> "$GITLEAKS_COMBINED_IGNORE"
@@ -147,11 +147,6 @@
147
147
  "id": "GHSA-q3j6-qgpj-74h6",
148
148
  "package": "fast-uri",
149
149
  "reason": "Path traversal via percent-encoded dot segments in fast-uri parser. Reachable transitively via eslint > @eslint/eslintrc > ajv > fast-uri and (in CDK projects) via aws-cdk-lib's bundled JSON Schema validator. aws-cdk-lib is invoked only at build/synth/deploy time \u2014 its ajv usage validates CloudFormation/CDK schemas authored by the developer, not attacker-controlled URIs. No runtime/production code path passes attacker-controlled URIs through ajv schema validation that relies on fast-uri parsing."
150
- },
151
- {
152
- "id": "GHSA-gv7w-rqvm-qjhr",
153
- "package": "esbuild",
154
- "reason": "Missing binary integrity verification in esbuild's Deno install module enables RCE via a malicious NPM_CONFIG_REGISTRY. esbuild is dev/build-time only \u2014 transitive via tsx, vitest, and esbuild-register \u2014 and is never in a shipped bundle or runtime code path. Lisa-managed projects install via bun/npm (not the Deno module path) and do not use attacker-controlled NPM_CONFIG_REGISTRY at install. Remove once the tsx/vitest/esbuild-register chain bumps esbuild >= 0.28.1."
155
150
  }
156
151
  ]
157
152
  }
@@ -7,11 +7,16 @@
7
7
  * ESLint 9 Flat Config
8
8
  *
9
9
  * Thin wrapper around @codyswann/lisa eslint config factory.
10
- * Customize via eslint.config.local.ts and eslint.thresholds.json.
10
+ * Customize via eslint.config.local.ts, eslint.thresholds.json, and the
11
+ * optional (create-only) eslint.ignore.config.local.json — its `ignores` are
12
+ * merged into the ignore list so host customizations survive the
13
+ * copy-overwrite of eslint.ignore.config.json on each Lisa update.
11
14
  *
12
15
  * @see https://eslint.org/docs/latest/use/configure/configuration-files-new
13
16
  * @module eslint.config
14
17
  */
18
+ import { existsSync } from "fs";
19
+ import { createRequire } from "module";
15
20
  import path from "path";
16
21
  import { fileURLToPath } from "url";
17
22
 
@@ -25,14 +30,33 @@ import ignoreConfig from "./eslint.ignore.config.json" with { type: "json" };
25
30
  import thresholdsConfig from "./eslint.thresholds.json" with { type: "json" };
26
31
  import localConfig from "./eslint.config.local";
27
32
 
33
+ const require = createRequire(import.meta.url);
28
34
  const __filename = fileURLToPath(import.meta.url);
29
35
  const __dirname = path.dirname(__filename);
30
36
 
31
- export default [
37
+ // Merge the optional project-owned eslint.ignore.config.local.json (create-only,
38
+ // never copy-overwritten) so host ignore customizations are not wiped when Lisa
39
+ // overwrites eslint.ignore.config.json on update.
40
+ const localIgnores: string[] = existsSync(
41
+ path.join(__dirname, "eslint.ignore.config.local.json")
42
+ )
43
+ ? (require("./eslint.ignore.config.local.json").ignores ?? [])
44
+ : [];
45
+
46
+ // Annotate with the factory's return type so `tsc --noEmit` under
47
+ // `declaration: true` does not surface @eslint/core's `RulesConfig` in the
48
+ // emitted declaration (TS2883). Do NOT import `Linter` from "eslint" directly —
49
+ // that trips knip's unlisted-dependency check.
50
+ const config: ReturnType<typeof getTypescriptConfig> = [
32
51
  ...getTypescriptConfig({
33
52
  tsconfigRootDir: __dirname,
34
- ignorePatterns: ignoreConfig.ignores || defaultIgnores,
53
+ ignorePatterns: [
54
+ ...(ignoreConfig.ignores || defaultIgnores),
55
+ ...localIgnores,
56
+ ],
35
57
  thresholds: { ...defaultThresholds, ...thresholdsConfig },
36
58
  }),
37
59
  ...localConfig,
38
60
  ];
61
+
62
+ export default config;
@@ -9,10 +9,41 @@
9
9
  * Thin wrapper around @codyswann/lisa slow eslint config factory.
10
10
  * Run periodically via `lint:slow` rather than on every lint pass.
11
11
  *
12
+ * The optional (create-only) eslint.ignore.config.local.json `ignores` are
13
+ * merged into the ignore list so host customizations survive the
14
+ * copy-overwrite of eslint.ignore.config.json on each Lisa update.
15
+ *
12
16
  * @see https://github.com/import-js/eslint-plugin-import
13
17
  * @module eslint.slow.config
14
18
  */
19
+ import { existsSync } from "fs";
20
+ import { createRequire } from "module";
21
+ import path from "path";
22
+ import { fileURLToPath } from "url";
23
+
15
24
  import { getSlowConfig } from "@codyswann/lisa/eslint/slow";
25
+
16
26
  import ignoreConfig from "./eslint.ignore.config.json" with { type: "json" };
17
27
 
18
- export default getSlowConfig({ ignorePatterns: ignoreConfig.ignores || [] });
28
+ const require = createRequire(import.meta.url);
29
+ const __filename = fileURLToPath(import.meta.url);
30
+ const __dirname = path.dirname(__filename);
31
+
32
+ // Merge the optional project-owned eslint.ignore.config.local.json (create-only,
33
+ // never copy-overwritten) so host ignore customizations are not wiped when Lisa
34
+ // overwrites eslint.ignore.config.json on update.
35
+ const localIgnores: string[] = existsSync(
36
+ path.join(__dirname, "eslint.ignore.config.local.json")
37
+ )
38
+ ? (require("./eslint.ignore.config.local.json").ignores ?? [])
39
+ : [];
40
+
41
+ // Annotate with the factory's return type so `tsc --noEmit` under
42
+ // `declaration: true` does not surface @eslint/core's `RulesConfig` in the
43
+ // emitted declaration (TS2883). Do NOT import `Linter` from "eslint" directly —
44
+ // that trips knip's unlisted-dependency check.
45
+ const config: ReturnType<typeof getSlowConfig> = getSlowConfig({
46
+ ignorePatterns: [...(ignoreConfig.ignores || []), ...localIgnores],
47
+ });
48
+
49
+ export default config;
@@ -35,7 +35,8 @@
35
35
  "undici": ">=6.27.0",
36
36
  "vite": ">=8.0.16",
37
37
  "ws": ">=8.21.0",
38
- "form-data": ">=4.0.6"
38
+ "form-data": ">=4.0.6",
39
+ "systeminformation": ">=5.27.14"
39
40
  },
40
41
  "overrides": {
41
42
  "@isaacs/brace-expansion": "^5.0.1",
@@ -47,7 +48,8 @@
47
48
  "undici": ">=6.27.0",
48
49
  "vite": "^8.0.16",
49
50
  "ws": ">=8.21.0",
50
- "form-data": ">=4.0.6"
51
+ "form-data": ">=4.0.6",
52
+ "systeminformation": ">=5.27.14"
51
53
  }
52
54
  },
53
55
  "defaults": {