@open-agent-toolkit/cli 0.1.14 → 0.1.15
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/public-package-versions.json +4 -4
- package/assets/skills/oat-review-provide/SKILL.md +2 -2
- package/assets/skills/oat-review-provide/scripts/resolve-review-output.sh +16 -6
- package/dist/commands/project/new/index.d.ts +1 -0
- package/dist/commands/project/new/index.d.ts.map +1 -1
- package/dist/commands/project/new/index.js +23 -0
- package/dist/commands/project/new/scaffold.d.ts +17 -0
- package/dist/commands/project/new/scaffold.d.ts.map +1 -1
- package/dist/commands/project/new/scaffold.js +74 -0
- package/package.json +2 -2
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: oat-review-provide
|
|
3
|
-
version: 1.2.
|
|
3
|
+
version: 1.2.1
|
|
4
4
|
description: Use when you need an ad-hoc review outside an active OAT project lifecycle. Reviews code or artifacts without project phase state, unlike oat-project-review-provide.
|
|
5
5
|
argument-hint: '[unstaged|staged|base_branch=<branch>|base_sha=<sha>|<sha1>..<sha2>|--files <path1,path2,...>] [--output <path>] [--mode auto|local|tracked|inline]'
|
|
6
6
|
disable-model-invocation: true
|
|
@@ -137,7 +137,7 @@ bash .agents/skills/oat-review-provide/scripts/resolve-review-output.sh --mode a
|
|
|
137
137
|
|
|
138
138
|
Policy:
|
|
139
139
|
|
|
140
|
-
- If `.oat/repo/reviews` exists and
|
|
140
|
+
- If `.oat/repo/reviews` exists and new review artifacts under it are not gitignored, assume user wants tracked active artifacts there.
|
|
141
141
|
- Otherwise default to active local `.oat/projects/local/orphan-reviews`.
|
|
142
142
|
- Do **not** write new review artifacts directly into any `archived/` directory; those are historical locations used after `oat-review-receive` processes a review.
|
|
143
143
|
- If user preference is unclear, ask and recommend local-only.
|
|
@@ -18,7 +18,7 @@ Policy:
|
|
|
18
18
|
- If --output is provided, use it directly.
|
|
19
19
|
- If mode=inline, no artifact file is written.
|
|
20
20
|
- In auto mode:
|
|
21
|
-
- If .oat/repo/reviews exists and
|
|
21
|
+
- If .oat/repo/reviews exists and new review artifacts under it are NOT gitignored, use it (tracked convention).
|
|
22
22
|
- Otherwise, use .oat/projects/local/orphan-reviews (local-only default).
|
|
23
23
|
USAGE
|
|
24
24
|
}
|
|
@@ -67,12 +67,22 @@ is_gitignored() {
|
|
|
67
67
|
fi
|
|
68
68
|
}
|
|
69
69
|
|
|
70
|
+
artifact_probe_path() {
|
|
71
|
+
local output_dir="$1"
|
|
72
|
+
printf '%s/%s\n' "${output_dir%/}" "ad-hoc-review-gitignore-probe.md"
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
is_output_gitignored() {
|
|
76
|
+
local output_dir="$1"
|
|
77
|
+
is_gitignored "$(artifact_probe_path "$output_dir")"
|
|
78
|
+
}
|
|
79
|
+
|
|
70
80
|
# Resolve explicit output first
|
|
71
81
|
if [[ -n "$OUTPUT" ]]; then
|
|
72
82
|
echo "review_mode=file"
|
|
73
83
|
echo "output_dir=$OUTPUT"
|
|
74
84
|
echo "output_kind=custom"
|
|
75
|
-
echo "output_gitignored=$(
|
|
85
|
+
echo "output_gitignored=$(is_output_gitignored "$OUTPUT")"
|
|
76
86
|
echo "reason=explicit_output"
|
|
77
87
|
exit 0
|
|
78
88
|
fi
|
|
@@ -93,7 +103,7 @@ if [[ "$MODE" == "tracked" ]]; then
|
|
|
93
103
|
echo "review_mode=file"
|
|
94
104
|
echo "output_dir=$TRACKED_DIR"
|
|
95
105
|
echo "output_kind=tracked"
|
|
96
|
-
echo "output_gitignored=$(
|
|
106
|
+
echo "output_gitignored=$(is_output_gitignored "$TRACKED_DIR")"
|
|
97
107
|
echo "reason=forced_tracked"
|
|
98
108
|
exit 0
|
|
99
109
|
fi
|
|
@@ -102,13 +112,13 @@ if [[ "$MODE" == "local" ]]; then
|
|
|
102
112
|
echo "review_mode=file"
|
|
103
113
|
echo "output_dir=$LOCAL_DIR"
|
|
104
114
|
echo "output_kind=local"
|
|
105
|
-
echo "output_gitignored=$(
|
|
115
|
+
echo "output_gitignored=$(is_output_gitignored "$LOCAL_DIR")"
|
|
106
116
|
echo "reason=forced_local"
|
|
107
117
|
exit 0
|
|
108
118
|
fi
|
|
109
119
|
|
|
110
120
|
# auto mode
|
|
111
|
-
if [[ -d "$TRACKED_DIR" ]] && [[ "$(
|
|
121
|
+
if [[ -d "$TRACKED_DIR" ]] && [[ "$(is_output_gitignored "$TRACKED_DIR")" == "false" ]]; then
|
|
112
122
|
echo "review_mode=file"
|
|
113
123
|
echo "output_dir=$TRACKED_DIR"
|
|
114
124
|
echo "output_kind=tracked"
|
|
@@ -120,5 +130,5 @@ fi
|
|
|
120
130
|
echo "review_mode=file"
|
|
121
131
|
echo "output_dir=$LOCAL_DIR"
|
|
122
132
|
echo "output_kind=local"
|
|
123
|
-
echo "output_gitignored=$(
|
|
133
|
+
echo "output_gitignored=$(is_output_gitignored "$LOCAL_DIR")"
|
|
124
134
|
echo "reason=default_local_only"
|
|
@@ -10,6 +10,7 @@ interface ProjectNewDependencies {
|
|
|
10
10
|
force: boolean;
|
|
11
11
|
setActive: boolean;
|
|
12
12
|
refreshDashboard: boolean;
|
|
13
|
+
commit: boolean;
|
|
13
14
|
}) => Promise<ScaffoldProjectResult>;
|
|
14
15
|
}
|
|
15
16
|
export declare function createProjectNewCommand(overrides?: Partial<ProjectNewDependencies>): Command;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/commands/project/new/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,cAAc,EACnB,KAAK,aAAa,EACnB,MAAM,sBAAsB,CAAC;AAE9B,OAAO,EAAE,OAAO,EAAU,MAAM,WAAW,CAAC;AAE5C,OAAO,EAEL,KAAK,mBAAmB,EACxB,KAAK,qBAAqB,EAC3B,MAAM,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/commands/project/new/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,cAAc,EACnB,KAAK,aAAa,EACnB,MAAM,sBAAsB,CAAC;AAE9B,OAAO,EAAE,OAAO,EAAU,MAAM,WAAW,CAAC;AAE5C,OAAO,EAEL,KAAK,mBAAmB,EACxB,KAAK,qBAAqB,EAC3B,MAAM,YAAY,CAAC;AAgBpB,UAAU,sBAAsB;IAC9B,mBAAmB,EAAE,CAAC,OAAO,EAAE,aAAa,KAAK,cAAc,CAAC;IAChE,eAAe,EAAE,CAAC,OAAO,EAAE;QACzB,QAAQ,EAAE,MAAM,CAAC;QACjB,WAAW,EAAE,MAAM,CAAC;QACpB,IAAI,EAAE,mBAAmB,CAAC;QAC1B,KAAK,EAAE,OAAO,CAAC;QACf,SAAS,EAAE,OAAO,CAAC;QACnB,gBAAgB,EAAE,OAAO,CAAC;QAC1B,MAAM,EAAE,OAAO,CAAC;KACjB,KAAK,OAAO,CAAC,qBAAqB,CAAC,CAAC;CACtC;AAoFD,wBAAgB,uBAAuB,CACrC,SAAS,GAAE,OAAO,CAAC,sBAAsB,CAAM,GAC9C,OAAO,CAkCT"}
|
|
@@ -2,6 +2,11 @@ import { buildCommandContext, } from '../../../app/command-context.js';
|
|
|
2
2
|
import { readGlobalOptions } from '../../shared/shared.utils.js';
|
|
3
3
|
import { Command, Option } from 'commander';
|
|
4
4
|
import { scaffoldProject as defaultScaffoldProject, } from './scaffold.js';
|
|
5
|
+
const COMMIT_STATUS_MESSAGES = {
|
|
6
|
+
skipped_disabled: 'Scaffold commit: skipped (--no-commit)',
|
|
7
|
+
skipped_no_worktree: 'Scaffold commit: skipped (not a git work tree)',
|
|
8
|
+
skipped_nothing: 'Scaffold commit: skipped (nothing to commit)',
|
|
9
|
+
};
|
|
5
10
|
const DEFAULT_DEPENDENCIES = {
|
|
6
11
|
buildCommandContext,
|
|
7
12
|
scaffoldProject: defaultScaffoldProject,
|
|
@@ -18,6 +23,10 @@ function reportSuccess(context, projectName, result) {
|
|
|
18
23
|
skippedFiles: result.skippedFiles,
|
|
19
24
|
activePointerUpdated: result.activePointerUpdated,
|
|
20
25
|
dashboardRefreshed: result.dashboardRefreshed,
|
|
26
|
+
committed: result.committed,
|
|
27
|
+
commitSha: result.commitSha,
|
|
28
|
+
commitStatus: result.commitStatus,
|
|
29
|
+
commitError: result.commitError,
|
|
21
30
|
});
|
|
22
31
|
return;
|
|
23
32
|
}
|
|
@@ -26,6 +35,18 @@ function reportSuccess(context, projectName, result) {
|
|
|
26
35
|
if (result.activePointerUpdated) {
|
|
27
36
|
context.logger.info('Active project updated in local config: .oat/config.local.json');
|
|
28
37
|
}
|
|
38
|
+
if (result.commitStatus === 'committed') {
|
|
39
|
+
context.logger.info(`Scaffold commit: ${result.commitSha?.slice(0, 7) ?? 'committed'}`);
|
|
40
|
+
}
|
|
41
|
+
else if (result.commitStatus === 'failed') {
|
|
42
|
+
// The scaffold itself succeeded, so this is a warning, not an error: do not
|
|
43
|
+
// change the process exit code. Make clear the baseline was NOT committed.
|
|
44
|
+
const detail = result.commitError ? `: ${result.commitError}` : '';
|
|
45
|
+
context.logger.warn(`Warning: scaffold commit failed${detail}. The scaffolded files were written but NOT committed.`);
|
|
46
|
+
}
|
|
47
|
+
else {
|
|
48
|
+
context.logger.info(COMMIT_STATUS_MESSAGES[result.commitStatus]);
|
|
49
|
+
}
|
|
29
50
|
}
|
|
30
51
|
async function runProjectNew(projectName, options, context, dependencies) {
|
|
31
52
|
try {
|
|
@@ -36,6 +57,7 @@ async function runProjectNew(projectName, options, context, dependencies) {
|
|
|
36
57
|
force: options.force,
|
|
37
58
|
setActive: options.setActive,
|
|
38
59
|
refreshDashboard: options.dashboard,
|
|
60
|
+
commit: options.commit,
|
|
39
61
|
});
|
|
40
62
|
reportSuccess(context, projectName, result);
|
|
41
63
|
process.exitCode = 0;
|
|
@@ -65,6 +87,7 @@ export function createProjectNewCommand(overrides = {}) {
|
|
|
65
87
|
.option('--force', 'Non-destructive scaffold; create missing files only')
|
|
66
88
|
.option('--no-set-active', 'Do not update active project in local config')
|
|
67
89
|
.option('--no-dashboard', 'Do not refresh .oat/state.md after scaffold')
|
|
90
|
+
.option('--no-commit', 'Do not git-commit the scaffolded project directory')
|
|
68
91
|
.action(async (name, options, command) => {
|
|
69
92
|
if (name.startsWith('-')) {
|
|
70
93
|
command.help();
|
|
@@ -6,11 +6,24 @@ export interface ScaffoldProjectOptions {
|
|
|
6
6
|
force?: boolean;
|
|
7
7
|
setActive?: boolean;
|
|
8
8
|
refreshDashboard?: boolean;
|
|
9
|
+
/**
|
|
10
|
+
* Commit the freshly scaffolded project directory so the artifact baseline is
|
|
11
|
+
* git-tracked from t=0. Opt-in (default false) so library callers that manage
|
|
12
|
+
* their own commits (e.g. the project-split flow) are unaffected; only the
|
|
13
|
+
* `oat project new` command enables it by default.
|
|
14
|
+
*/
|
|
15
|
+
commit?: boolean;
|
|
9
16
|
env?: NodeJS.ProcessEnv;
|
|
10
17
|
today?: string;
|
|
11
18
|
nowUtc?: string;
|
|
12
19
|
refreshDashboardCallback?: (repoRoot: string) => void | Promise<void>;
|
|
13
20
|
}
|
|
21
|
+
/**
|
|
22
|
+
* Classified outcome of the scoped scaffold commit. Distinguishing the skip
|
|
23
|
+
* reasons (and `failed`) lets the CLI surface accurate, distinct messaging
|
|
24
|
+
* instead of collapsing every non-commit into a single benign "skipped" line.
|
|
25
|
+
*/
|
|
26
|
+
export type CommitScaffoldStatus = 'committed' | 'skipped_disabled' | 'skipped_no_worktree' | 'skipped_nothing' | 'failed';
|
|
14
27
|
export interface ScaffoldProjectResult {
|
|
15
28
|
mode: ProjectScaffoldMode;
|
|
16
29
|
projectsRoot: string;
|
|
@@ -19,6 +32,10 @@ export interface ScaffoldProjectResult {
|
|
|
19
32
|
skippedFiles: string[];
|
|
20
33
|
activePointerUpdated: boolean;
|
|
21
34
|
dashboardRefreshed: boolean;
|
|
35
|
+
committed: boolean;
|
|
36
|
+
commitSha?: string;
|
|
37
|
+
commitStatus: CommitScaffoldStatus;
|
|
38
|
+
commitError?: string;
|
|
22
39
|
}
|
|
23
40
|
export declare function scaffoldProject(options: ScaffoldProjectOptions): Promise<ScaffoldProjectResult>;
|
|
24
41
|
//# sourceMappingURL=scaffold.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"scaffold.d.ts","sourceRoot":"","sources":["../../../../src/commands/project/new/scaffold.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"scaffold.d.ts","sourceRoot":"","sources":["../../../../src/commands/project/new/scaffold.ts"],"names":[],"mappings":"AAUA,MAAM,MAAM,mBAAmB,GAAG,aAAa,GAAG,OAAO,GAAG,QAAQ,CAAC;AAErE,MAAM,WAAW,sBAAsB;IACrC,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,MAAM,CAAC;IACpB,IAAI,CAAC,EAAE,mBAAmB,CAAC;IAC3B,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B;;;;;OAKG;IACH,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,GAAG,CAAC,EAAE,MAAM,CAAC,UAAU,CAAC;IACxB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,wBAAwB,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACvE;AAED;;;;GAIG;AACH,MAAM,MAAM,oBAAoB,GAC5B,WAAW,GACX,kBAAkB,GAClB,qBAAqB,GACrB,iBAAiB,GACjB,QAAQ,CAAC;AAEb,MAAM,WAAW,qBAAqB;IACpC,IAAI,EAAE,mBAAmB,CAAC;IAC1B,YAAY,EAAE,MAAM,CAAC;IACrB,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,oBAAoB,EAAE,OAAO,CAAC;IAC9B,kBAAkB,EAAE,OAAO,CAAC;IAC5B,SAAS,EAAE,OAAO,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,YAAY,EAAE,oBAAoB,CAAC;IACnC,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAoRD,wBAAsB,eAAe,CACnC,OAAO,EAAE,sBAAsB,GAC9B,OAAO,CAAC,qBAAqB,CAAC,CA4EhC"}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { execFileSync } from 'node:child_process';
|
|
1
2
|
import { mkdir, readFile, writeFile } from 'node:fs/promises';
|
|
2
3
|
import { join } from 'node:path';
|
|
3
4
|
import { resolveProjectsRoot } from '../../shared/oat-paths.js';
|
|
@@ -105,6 +106,62 @@ function applyTemplateReplacements(template, projectName, today, nowUtc, mode) {
|
|
|
105
106
|
async function defaultRefreshDashboard(repoRoot) {
|
|
106
107
|
await generateStateDashboard({ repoRoot });
|
|
107
108
|
}
|
|
109
|
+
/**
|
|
110
|
+
* Scoped, fail-safe commit of just the files this run created.
|
|
111
|
+
*
|
|
112
|
+
* Stages and commits only the pathspecs derived from `createdFiles` (under
|
|
113
|
+
* `projectPath`) so unrelated working-tree changes — including pre-existing
|
|
114
|
+
* dirty edits inside the same project directory on a re-run, and the
|
|
115
|
+
* `.oat/state.md` dashboard outside it — are never swept in. The returned
|
|
116
|
+
* status distinguishes a clean commit from each skip reason and from a genuine
|
|
117
|
+
* git failure; on failure the captured git stderr is surfaced via `error`. This
|
|
118
|
+
* never throws: any git error is classified as `failed`, not propagated.
|
|
119
|
+
*/
|
|
120
|
+
function commitScaffold(cwd, projectPath, projectName, createdFiles) {
|
|
121
|
+
const run = (args) => execFileSync('git', args, {
|
|
122
|
+
cwd,
|
|
123
|
+
encoding: 'utf8',
|
|
124
|
+
// Capture stderr instead of inheriting it so deliberate skip/failure
|
|
125
|
+
// probes (e.g. tests) do not leak raw `git fatal:` lines to the terminal.
|
|
126
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
127
|
+
}).trim();
|
|
128
|
+
try {
|
|
129
|
+
run(['rev-parse', '--is-inside-work-tree']);
|
|
130
|
+
}
|
|
131
|
+
catch {
|
|
132
|
+
return { status: 'skipped_no_worktree', committed: false };
|
|
133
|
+
}
|
|
134
|
+
// Only commit files this run created. Nothing created => nothing to commit,
|
|
135
|
+
// which guarantees a re-run never touches unrelated working-tree edits.
|
|
136
|
+
if (createdFiles.length === 0) {
|
|
137
|
+
return { status: 'skipped_nothing', committed: false };
|
|
138
|
+
}
|
|
139
|
+
const pathspecs = createdFiles.map((file) => join(projectPath, file));
|
|
140
|
+
try {
|
|
141
|
+
run(['add', '--', ...pathspecs]);
|
|
142
|
+
const staged = run(['diff', '--cached', '--name-only', '--', ...pathspecs]);
|
|
143
|
+
if (staged.length === 0) {
|
|
144
|
+
return { status: 'skipped_nothing', committed: false };
|
|
145
|
+
}
|
|
146
|
+
run([
|
|
147
|
+
'commit',
|
|
148
|
+
'-m',
|
|
149
|
+
`chore(oat): scaffold ${projectName}`,
|
|
150
|
+
'--',
|
|
151
|
+
...pathspecs,
|
|
152
|
+
]);
|
|
153
|
+
const commitSha = run(['rev-parse', 'HEAD']);
|
|
154
|
+
return { status: 'committed', committed: true, commitSha };
|
|
155
|
+
}
|
|
156
|
+
catch (error) {
|
|
157
|
+
const stderr = error && typeof error === 'object' && 'stderr' in error
|
|
158
|
+
? error.stderr
|
|
159
|
+
: undefined;
|
|
160
|
+
const message = (stderr != null ? stderr.toString().trim() : '') ||
|
|
161
|
+
(error instanceof Error ? error.message : String(error));
|
|
162
|
+
return { status: 'failed', committed: false, error: message };
|
|
163
|
+
}
|
|
164
|
+
}
|
|
108
165
|
async function scaffoldModeTemplates(repoRoot, projectPath, projectName, mode, today, nowUtc) {
|
|
109
166
|
const templatesDir = join(repoRoot, '.oat', 'templates');
|
|
110
167
|
const createdFiles = [];
|
|
@@ -169,6 +226,19 @@ export async function scaffoldProject(options) {
|
|
|
169
226
|
console.error(`Warning: dashboard refresh failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
170
227
|
}
|
|
171
228
|
}
|
|
229
|
+
let committed = false;
|
|
230
|
+
let commitSha;
|
|
231
|
+
let commitStatus = 'skipped_disabled';
|
|
232
|
+
let commitError;
|
|
233
|
+
if (options.commit) {
|
|
234
|
+
// `projectPath` is relative to `repoRoot`, so git must run there for the
|
|
235
|
+
// pathspecs to resolve to the scaffolded files.
|
|
236
|
+
const commitResult = commitScaffold(options.repoRoot, projectPath, options.projectName, createdFiles);
|
|
237
|
+
committed = commitResult.committed;
|
|
238
|
+
commitSha = commitResult.commitSha;
|
|
239
|
+
commitStatus = commitResult.status;
|
|
240
|
+
commitError = commitResult.error;
|
|
241
|
+
}
|
|
172
242
|
return {
|
|
173
243
|
mode,
|
|
174
244
|
projectsRoot,
|
|
@@ -177,5 +247,9 @@ export async function scaffoldProject(options) {
|
|
|
177
247
|
skippedFiles,
|
|
178
248
|
activePointerUpdated: setActive,
|
|
179
249
|
dashboardRefreshed,
|
|
250
|
+
committed,
|
|
251
|
+
commitSha,
|
|
252
|
+
commitStatus,
|
|
253
|
+
commitError,
|
|
180
254
|
};
|
|
181
255
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@open-agent-toolkit/cli",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.15",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "Open Agent Toolkit CLI",
|
|
6
6
|
"homepage": "https://github.com/voxmedia/open-agent-toolkit/tree/main/packages/cli",
|
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
"ora": "^9.0.0",
|
|
35
35
|
"yaml": "2.8.2",
|
|
36
36
|
"zod": "^3.25.76",
|
|
37
|
-
"@open-agent-toolkit/control-plane": "0.1.
|
|
37
|
+
"@open-agent-toolkit/control-plane": "0.1.15"
|
|
38
38
|
},
|
|
39
39
|
"devDependencies": {
|
|
40
40
|
"@types/node": "^22.10.0",
|