@ghl-ai/aw 0.1.36-beta.26 → 0.1.36-beta.28
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/commands/init.mjs +4 -4
- package/commands/link-project.mjs +9 -10
- package/commands/pull.mjs +7 -5
- package/commands/push.mjs +8 -9
- package/commands/status.mjs +7 -6
- package/constants.mjs +7 -0
- package/ecc.mjs +1 -0
- package/fmt.mjs +1 -1
- package/hooks.mjs +4 -0
- package/integrate.mjs +2 -2
- package/link.mjs +9 -4
- package/package.json +7 -4
package/commands/init.mjs
CHANGED
|
@@ -29,7 +29,7 @@ import {
|
|
|
29
29
|
sparseCheckoutAsync,
|
|
30
30
|
cleanup,
|
|
31
31
|
} from '../git.mjs';
|
|
32
|
-
import { REGISTRY_DIR, REGISTRY_REPO } from '../constants.mjs';
|
|
32
|
+
import { REGISTRY_DIR, REGISTRY_REPO, REGISTRY_URL } from '../constants.mjs';
|
|
33
33
|
|
|
34
34
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
35
35
|
const VERSION = JSON.parse(readFileSync(join(__dirname, '..', 'package.json'), 'utf8')).version;
|
|
@@ -124,7 +124,7 @@ export async function initCommand(args) {
|
|
|
124
124
|
|
|
125
125
|
// ── Detect installation state ─────────────────────────────────────────
|
|
126
126
|
|
|
127
|
-
const repoUrl =
|
|
127
|
+
const repoUrl = REGISTRY_URL;
|
|
128
128
|
const isGitNative = isValidClone(AW_HOME, repoUrl);
|
|
129
129
|
const isLegacy = !isGitNative && existsSync(GLOBAL_AW_DIR) && !lstatSync(GLOBAL_AW_DIR).isSymbolicLink();
|
|
130
130
|
|
|
@@ -309,8 +309,8 @@ export async function initCommand(args) {
|
|
|
309
309
|
await installAwEcc(cwd, { silent });
|
|
310
310
|
const instructionFiles = copyInstructions(HOME, null, team) || [];
|
|
311
311
|
initAwDocs(HOME);
|
|
312
|
-
const mcpFiles = await setupMcp(HOME, team) || [];
|
|
313
|
-
if (cwd !== HOME) await setupMcp(cwd, team);
|
|
312
|
+
const mcpFiles = await setupMcp(HOME, team, { silent }) || [];
|
|
313
|
+
if (cwd !== HOME) await setupMcp(cwd, team, { silent });
|
|
314
314
|
const hooksInstalled = installGlobalHooks();
|
|
315
315
|
installIdeTasks();
|
|
316
316
|
|
|
@@ -6,7 +6,7 @@ import { homedir } from 'node:os';
|
|
|
6
6
|
import * as fmt from '../fmt.mjs';
|
|
7
7
|
import { chalk } from '../fmt.mjs';
|
|
8
8
|
import { addProjectWorktree, isWorktree, isValidClone } from '../git.mjs';
|
|
9
|
-
import { REGISTRY_DIR,
|
|
9
|
+
import { REGISTRY_DIR, REGISTRY_URL } from '../constants.mjs';
|
|
10
10
|
import { linkWorkspace } from '../link.mjs';
|
|
11
11
|
import { generateCommands } from '../integrate.mjs';
|
|
12
12
|
|
|
@@ -18,8 +18,7 @@ export function linkProjectCommand(args) {
|
|
|
18
18
|
|
|
19
19
|
fmt.intro('aw link');
|
|
20
20
|
|
|
21
|
-
|
|
22
|
-
if (!isValidClone(AW_HOME, repoUrl)) {
|
|
21
|
+
if (!isValidClone(AW_HOME, REGISTRY_URL)) {
|
|
23
22
|
fmt.cancel('Registry not initialized. Run: aw init');
|
|
24
23
|
return;
|
|
25
24
|
}
|
|
@@ -39,9 +38,9 @@ export function linkProjectCommand(args) {
|
|
|
39
38
|
// Worktree exists — refresh global IDE symlinks pointing to this project's registry
|
|
40
39
|
const projectRegistryDir = join(worktreeDir, REGISTRY_DIR);
|
|
41
40
|
const awDirForLinks = existsSync(projectRegistryDir) ? projectRegistryDir : null;
|
|
42
|
-
linkWorkspace(HOME, awDirForLinks);
|
|
43
|
-
generateCommands(HOME);
|
|
44
|
-
fmt.logSuccess(`Already linked — refreshed IDE symlinks`);
|
|
41
|
+
const symlinks = linkWorkspace(HOME, awDirForLinks, { silent: true });
|
|
42
|
+
const commands = generateCommands(HOME, { silent: true });
|
|
43
|
+
fmt.logSuccess(`Already linked — refreshed ${chalk.bold(symlinks)} IDE symlinks · ${chalk.bold(commands)} commands`);
|
|
45
44
|
return;
|
|
46
45
|
}
|
|
47
46
|
|
|
@@ -49,10 +48,10 @@ export function linkProjectCommand(args) {
|
|
|
49
48
|
addProjectWorktree(AW_HOME, cwd);
|
|
50
49
|
const projectRegistryDir = join(worktreeDir, REGISTRY_DIR);
|
|
51
50
|
const awDirForLinks = existsSync(projectRegistryDir) ? projectRegistryDir : null;
|
|
52
|
-
linkWorkspace(HOME, awDirForLinks);
|
|
53
|
-
generateCommands(HOME);
|
|
51
|
+
const symlinks = linkWorkspace(HOME, awDirForLinks, { silent: true });
|
|
52
|
+
const commands = generateCommands(HOME, { silent: true });
|
|
54
53
|
fmt.logSuccess([
|
|
55
|
-
`
|
|
54
|
+
`Project linked — ${chalk.bold(symlinks)} IDE symlinks · ${chalk.bold(commands)} commands`,
|
|
56
55
|
'',
|
|
57
56
|
` ${chalk.green('✓')} ${chalk.dim('.aw/')} git worktree (IDE git panel enabled)`,
|
|
58
57
|
` ${chalk.green('✓')} ${chalk.dim(`.aw/${REGISTRY_DIR}/`)} registry content`,
|
|
@@ -62,5 +61,5 @@ export function linkProjectCommand(args) {
|
|
|
62
61
|
fmt.cancel(`Failed to link project: ${e.message}`);
|
|
63
62
|
}
|
|
64
63
|
|
|
65
|
-
fmt.outro('Done');
|
|
64
|
+
fmt.outro('⟁ Done');
|
|
66
65
|
}
|
package/commands/pull.mjs
CHANGED
|
@@ -8,7 +8,7 @@ import * as config from '../config.mjs';
|
|
|
8
8
|
import * as fmt from '../fmt.mjs';
|
|
9
9
|
import { chalk } from '../fmt.mjs';
|
|
10
10
|
import { fetchAndMerge, addToSparseCheckout, isValidClone, isWorktree, rebaseOntoOriginMain } from '../git.mjs';
|
|
11
|
-
import { REGISTRY_DIR, REGISTRY_REPO, DOCS_SOURCE_DIR } from '../constants.mjs';
|
|
11
|
+
import { REGISTRY_DIR, REGISTRY_REPO, REGISTRY_URL, DOCS_SOURCE_DIR } from '../constants.mjs';
|
|
12
12
|
import { linkWorkspace } from '../link.mjs';
|
|
13
13
|
import { generateCommands, copyInstructions } from '../integrate.mjs';
|
|
14
14
|
|
|
@@ -33,7 +33,9 @@ export async function pullCommand(args) {
|
|
|
33
33
|
spinner: silent ? () => ({ start: () => {}, stop: () => {} }) : fmt.spinner,
|
|
34
34
|
};
|
|
35
35
|
|
|
36
|
-
|
|
36
|
+
if (!silent) fmt.intro('aw pull');
|
|
37
|
+
|
|
38
|
+
const repoUrl = REGISTRY_URL;
|
|
37
39
|
const hasClone = isValidClone(AW_HOME, repoUrl);
|
|
38
40
|
|
|
39
41
|
if (!hasClone) {
|
|
@@ -128,14 +130,14 @@ export async function pullCommand(args) {
|
|
|
128
130
|
// so edits to project/.aw/.aw_registry/ are instantly visible in global IDE dirs.
|
|
129
131
|
const projectRegistryDir = cwd !== HOME ? join(cwd, '.aw', REGISTRY_DIR) : null;
|
|
130
132
|
const awDirForLinks = (projectRegistryDir && existsSync(projectRegistryDir)) ? projectRegistryDir : null;
|
|
131
|
-
linkWorkspace(HOME, awDirForLinks);
|
|
132
|
-
generateCommands(HOME);
|
|
133
|
+
linkWorkspace(HOME, awDirForLinks, { silent });
|
|
134
|
+
generateCommands(HOME, { silent });
|
|
133
135
|
copyInstructions(HOME, null, cfg.namespace);
|
|
134
136
|
}
|
|
135
137
|
|
|
136
138
|
if (!silent) {
|
|
137
139
|
registerMcp(cfg.namespace);
|
|
138
|
-
log.outro('Pull complete');
|
|
140
|
+
log.outro('⟁ Pull complete');
|
|
139
141
|
}
|
|
140
142
|
}
|
|
141
143
|
|
package/commands/push.mjs
CHANGED
|
@@ -6,7 +6,7 @@ import { execSync, execFileSync } from 'node:child_process';
|
|
|
6
6
|
import { homedir } from 'node:os';
|
|
7
7
|
import * as fmt from '../fmt.mjs';
|
|
8
8
|
import { chalk } from '../fmt.mjs';
|
|
9
|
-
import { REGISTRY_REPO, REGISTRY_BASE_BRANCH, REGISTRY_DIR } from '../constants.mjs';
|
|
9
|
+
import { REGISTRY_REPO, REGISTRY_URL, REGISTRY_BASE_BRANCH, REGISTRY_DIR } from '../constants.mjs';
|
|
10
10
|
import { resolveInput } from '../paths.mjs';
|
|
11
11
|
import { walkRegistryTree } from '../registry.mjs';
|
|
12
12
|
import {
|
|
@@ -179,8 +179,8 @@ function generatePrBody(files, newNamespaces, awHome = null) {
|
|
|
179
179
|
function generateCommitMsg(files) {
|
|
180
180
|
const added = files.filter(f => !f.deleted);
|
|
181
181
|
const deleted = files.filter(f => f.deleted);
|
|
182
|
-
const addedParts = Object.entries(groupBy(added, 'type')).map(([t, items]) => `${items.length} ${t}`);
|
|
183
|
-
const deletedParts = Object.entries(groupBy(deleted, 'type')).map(([t, items]) => `${items.length} ${t} removed`);
|
|
182
|
+
const addedParts = Object.entries(groupBy(added, 'type')).map(([t, items]) => `${items.length} ${singular(t, items.length)}`);
|
|
183
|
+
const deletedParts = Object.entries(groupBy(deleted, 'type')).map(([t, items]) => `${items.length} ${singular(t, items.length)} removed`);
|
|
184
184
|
const countParts = [...addedParts, ...deletedParts];
|
|
185
185
|
|
|
186
186
|
if (files.length === 1) {
|
|
@@ -284,7 +284,7 @@ function createOrUpdatePR(awHome, branch, prTitle, prBody) {
|
|
|
284
284
|
try {
|
|
285
285
|
const url = execFileSync('gh', [
|
|
286
286
|
'pr', 'view', branch, '--json', 'url', '--jq', '.url',
|
|
287
|
-
], { cwd: awHome, encoding: 'utf8' }).trim();
|
|
287
|
+
], { cwd: awHome, encoding: 'utf8', stdio: ['pipe', 'pipe', 'pipe'] }).trim();
|
|
288
288
|
if (url) return { url, updated: true };
|
|
289
289
|
} catch { /* no existing PR */ }
|
|
290
290
|
|
|
@@ -316,8 +316,8 @@ function doPush(files, awHome, dryRun, worktreeFlow = false, preStaged = false)
|
|
|
316
316
|
const added = files.filter(f => !f.deleted);
|
|
317
317
|
const deleted = files.filter(f => f.deleted);
|
|
318
318
|
|
|
319
|
-
const addedParts = Object.entries(groupBy(added, 'type')).map(([t, items]) => `${items.length} ${t}`);
|
|
320
|
-
const deletedParts = Object.entries(groupBy(deleted, 'type')).map(([t, items]) => `${items.length} ${t} removed`);
|
|
319
|
+
const addedParts = Object.entries(groupBy(added, 'type')).map(([t, items]) => `${items.length} ${singular(t, items.length)}`);
|
|
320
|
+
const deletedParts = Object.entries(groupBy(deleted, 'type')).map(([t, items]) => `${items.length} ${singular(t, items.length)} removed`);
|
|
321
321
|
const countParts = [...addedParts, ...deletedParts];
|
|
322
322
|
|
|
323
323
|
if (files.length === 0) {
|
|
@@ -401,7 +401,7 @@ function doPush(files, awHome, dryRun, worktreeFlow = false, preStaged = false)
|
|
|
401
401
|
fmt.logInfo(chalk.dim(`On branch ${finalBranch} — run aw push again to open a new PR`));
|
|
402
402
|
}
|
|
403
403
|
fmt.logSuccess(`PR: ${chalk.cyan(prUrl)}`);
|
|
404
|
-
fmt.outro('Push complete');
|
|
404
|
+
fmt.outro('⟁ Push complete');
|
|
405
405
|
}
|
|
406
406
|
|
|
407
407
|
// ── Main command ─────────────────────────────────────────────────────
|
|
@@ -426,7 +426,7 @@ export function pushCommand(args) {
|
|
|
426
426
|
|
|
427
427
|
fmt.intro('aw push');
|
|
428
428
|
|
|
429
|
-
const repoUrl =
|
|
429
|
+
const repoUrl = REGISTRY_URL;
|
|
430
430
|
if (!hasWorktree && !isValidClone(awHome, repoUrl)) {
|
|
431
431
|
fmt.cancel('Registry not initialized. Run: aw init');
|
|
432
432
|
return;
|
|
@@ -475,7 +475,6 @@ export function pushCommand(args) {
|
|
|
475
475
|
}
|
|
476
476
|
|
|
477
477
|
const files = allEntries
|
|
478
|
-
.filter(f => parseRegistryPath(f.registryPath) !== null)
|
|
479
478
|
.map(f => {
|
|
480
479
|
const meta = parseRegistryPath(f.registryPath);
|
|
481
480
|
const parts = f.registryPath.split('/');
|
package/commands/status.mjs
CHANGED
|
@@ -5,8 +5,8 @@ import { homedir } from 'node:os';
|
|
|
5
5
|
import * as config from '../config.mjs';
|
|
6
6
|
import * as fmt from '../fmt.mjs';
|
|
7
7
|
import { chalk } from '../fmt.mjs';
|
|
8
|
-
import { detectChanges, getCurrentBranch, isValidClone, isWorktree } from '../git.mjs';
|
|
9
|
-
import { REGISTRY_DIR, REGISTRY_REPO } from '../constants.mjs';
|
|
8
|
+
import { detectChanges, getCurrentBranch, commitsAheadOfMain, isValidClone, isWorktree } from '../git.mjs';
|
|
9
|
+
import { REGISTRY_DIR, REGISTRY_REPO, REGISTRY_URL } from '../constants.mjs';
|
|
10
10
|
|
|
11
11
|
export function statusCommand(args) {
|
|
12
12
|
const HOME = homedir();
|
|
@@ -20,8 +20,7 @@ export function statusCommand(args) {
|
|
|
20
20
|
|
|
21
21
|
fmt.intro('aw status');
|
|
22
22
|
|
|
23
|
-
|
|
24
|
-
if (!isWorktree(localAw) && !isValidClone(AW_HOME, repoUrl)) {
|
|
23
|
+
if (!isWorktree(localAw) && !isValidClone(AW_HOME, REGISTRY_URL)) {
|
|
25
24
|
fmt.cancel('Registry not initialized. Run: aw init');
|
|
26
25
|
return;
|
|
27
26
|
}
|
|
@@ -93,7 +92,9 @@ export function statusCommand(args) {
|
|
|
93
92
|
}
|
|
94
93
|
|
|
95
94
|
if (!isOnMain) {
|
|
96
|
-
|
|
95
|
+
const ahead = commitsAheadOfMain(AW_HOME);
|
|
96
|
+
const aheadStr = ahead > 0 ? ` (${chalk.yellow(`${ahead} commit${ahead !== 1 ? 's' : ''} ahead`)})` : '';
|
|
97
|
+
fmt.logWarn(`On branch ${chalk.yellow(branch)}${aheadStr} — run ${chalk.dim('aw push')} to open a PR or ${chalk.dim('aw pull')} to sync`);
|
|
97
98
|
}
|
|
98
99
|
|
|
99
100
|
// Hints
|
|
@@ -101,5 +102,5 @@ export function statusCommand(args) {
|
|
|
101
102
|
fmt.logInfo(`Push changes: ${chalk.dim('aw push')} or ${chalk.dim('aw push <path>')}`);
|
|
102
103
|
}
|
|
103
104
|
|
|
104
|
-
fmt.outro(
|
|
105
|
+
fmt.outro(`⟁ ${chalk.dim('aw pull')} to fetch latest`);
|
|
105
106
|
}
|
package/constants.mjs
CHANGED
|
@@ -9,6 +9,13 @@ export const REGISTRY_BASE_BRANCH = 'main';
|
|
|
9
9
|
/** Default registry repository */
|
|
10
10
|
export const REGISTRY_REPO = 'GoHighLevel/platform-docs';
|
|
11
11
|
|
|
12
|
+
/**
|
|
13
|
+
* Full registry clone URL.
|
|
14
|
+
* Tests override via AW_REGISTRY_URL env var (file:// bare repo).
|
|
15
|
+
*/
|
|
16
|
+
export const REGISTRY_URL = process.env.AW_REGISTRY_URL
|
|
17
|
+
|| `https://github.com/${REGISTRY_REPO}.git`;
|
|
18
|
+
|
|
12
19
|
/** Directory inside the registry repo that holds platform/ and [template]/ */
|
|
13
20
|
export const REGISTRY_DIR = '.aw_registry';
|
|
14
21
|
|
package/ecc.mjs
CHANGED
package/fmt.mjs
CHANGED
|
@@ -33,7 +33,7 @@ export function banner(text, opts = {}) {
|
|
|
33
33
|
|
|
34
34
|
// ─── Clack wrappers ───
|
|
35
35
|
|
|
36
|
-
export const intro = (msg) => p.intro(chalk.
|
|
36
|
+
export const intro = (msg) => p.intro(chalk.bgHex('#FF6B35').black(` ⟁ ${msg} `));
|
|
37
37
|
export const outro = (msg) => p.outro(chalk.green(msg));
|
|
38
38
|
export const spinner = () => p.spinner();
|
|
39
39
|
export const select = p.select;
|
package/hooks.mjs
CHANGED
|
@@ -111,6 +111,10 @@ exit 0
|
|
|
111
111
|
* @returns {boolean} true if hooks were installed
|
|
112
112
|
*/
|
|
113
113
|
export function installGlobalHooks() {
|
|
114
|
+
// AW_NO_HOOKS=1 lets test environments skip hook installation to prevent
|
|
115
|
+
// recursive aw init calls during git commits in tests.
|
|
116
|
+
if (process.env.AW_NO_HOOKS === '1') return false;
|
|
117
|
+
|
|
114
118
|
try {
|
|
115
119
|
mkdirSync(HOOKS_DIR, { recursive: true });
|
|
116
120
|
|
package/integrate.mjs
CHANGED
|
@@ -11,7 +11,7 @@ import { getLocalRegistryDir } from './git.mjs';
|
|
|
11
11
|
* Count hand-written commands already present in the registry.
|
|
12
12
|
* No CLI stub generation — all commands come from the registry itself.
|
|
13
13
|
*/
|
|
14
|
-
export function generateCommands(cwd) {
|
|
14
|
+
export function generateCommands(cwd, { silent = false } = {}) {
|
|
15
15
|
const awDir = getLocalRegistryDir(cwd, join(homedir(), '.aw_registry'));
|
|
16
16
|
|
|
17
17
|
// Clean old .generated-commands if it exists (migration)
|
|
@@ -29,7 +29,7 @@ export function generateCommands(cwd) {
|
|
|
29
29
|
}
|
|
30
30
|
}
|
|
31
31
|
|
|
32
|
-
if (count > 0) {
|
|
32
|
+
if (count > 0 && !silent) {
|
|
33
33
|
fmt.logSuccess(`Generated ${count} aw commands`);
|
|
34
34
|
}
|
|
35
35
|
|
package/link.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// link.mjs — Create symlinks from IDE dirs → .aw_registry/
|
|
2
2
|
|
|
3
|
-
import { existsSync, lstatSync, mkdirSync, readdirSync, unlinkSync, symlinkSync, rmdirSync } from 'node:fs';
|
|
3
|
+
import { existsSync, lstatSync, mkdirSync, readdirSync, unlinkSync, symlinkSync, rmdirSync, realpathSync } from 'node:fs';
|
|
4
4
|
import { join, relative } from 'node:path';
|
|
5
5
|
import { homedir } from 'node:os';
|
|
6
6
|
import * as fmt from './fmt.mjs';
|
|
@@ -129,9 +129,14 @@ function flatName(ns, name) {
|
|
|
129
129
|
* @param {string} cwd - Directory where IDE dirs (.claude/.cursor/.codex) live
|
|
130
130
|
* @param {string|null} awDirOverride - Explicit registry dir; overrides auto-detection
|
|
131
131
|
*/
|
|
132
|
-
export function linkWorkspace(cwd, awDirOverride = null) {
|
|
132
|
+
export function linkWorkspace(cwd, awDirOverride = null, { silent = false } = {}) {
|
|
133
|
+
// Normalize cwd to real path so relative() produces valid symlink targets on macOS
|
|
134
|
+
// where $HOME may be /var/... but process.cwd() resolves to /private/var/...
|
|
135
|
+
try { cwd = realpathSync(cwd); } catch { /* use as-is */ }
|
|
136
|
+
|
|
133
137
|
const GLOBAL_AW_DIR = join(homedir(), '.aw_registry');
|
|
134
|
-
|
|
138
|
+
let awDir = awDirOverride || getLocalRegistryDir(cwd, GLOBAL_AW_DIR);
|
|
139
|
+
try { awDir = realpathSync(awDir); } catch { /* use as-is if it doesn't exist */ }
|
|
135
140
|
if (!existsSync(awDir)) return 0;
|
|
136
141
|
|
|
137
142
|
let created = 0;
|
|
@@ -255,7 +260,7 @@ export function linkWorkspace(cwd, awDirOverride = null) {
|
|
|
255
260
|
}
|
|
256
261
|
}
|
|
257
262
|
|
|
258
|
-
if (created > 0) {
|
|
263
|
+
if (created > 0 && !silent) {
|
|
259
264
|
fmt.logSuccess(`Linked ${created} IDE symlink${created > 1 ? 's' : ''}`);
|
|
260
265
|
}
|
|
261
266
|
|
package/package.json
CHANGED
|
@@ -1,11 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ghl-ai/aw",
|
|
3
|
-
"version": "0.1.36-beta.
|
|
3
|
+
"version": "0.1.36-beta.28",
|
|
4
4
|
"description": "Agentic Workspace CLI — pull, push & manage agents, skills and commands from the registry",
|
|
5
5
|
"type": "module",
|
|
6
|
-
"bin":
|
|
7
|
-
"aw": "bin.js"
|
|
8
|
-
},
|
|
6
|
+
"bin": "bin.js",
|
|
9
7
|
"files": [
|
|
10
8
|
"bin.js",
|
|
11
9
|
"cli.mjs",
|
|
@@ -40,6 +38,8 @@
|
|
|
40
38
|
"author": "GoHighLevel",
|
|
41
39
|
"license": "MIT",
|
|
42
40
|
"scripts": {
|
|
41
|
+
"test": "vitest run --reporter=verbose",
|
|
42
|
+
"test:watch": "vitest --reporter=verbose",
|
|
43
43
|
"preuninstall": "node bin.js nuke 2>/dev/null || true"
|
|
44
44
|
},
|
|
45
45
|
"publishConfig": {
|
|
@@ -49,5 +49,8 @@
|
|
|
49
49
|
"@clack/prompts": "0.8.2",
|
|
50
50
|
"chalk": "^5.6.2",
|
|
51
51
|
"figlet": "^1.11.0"
|
|
52
|
+
},
|
|
53
|
+
"devDependencies": {
|
|
54
|
+
"vitest": "^4.1.2"
|
|
52
55
|
}
|
|
53
56
|
}
|