@baize-ai/core 0.3.14 → 0.3.16

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.
@@ -4,7 +4,7 @@
4
4
  *
5
5
  * A provider entry carries per-runtime endpoints/models:
6
6
  * cc: { baseUrl, authToken, model, smallModel } → ANTHROPIC_* env
7
- * codex: { baseUrl, apiKey, model } OPENAI_BASE_URL + auth.json + config.toml
7
+ * codex: { kind, providerKey, baseUrl, apiKey, model } dedicated provider block in config.toml
8
8
  *
9
9
  * Activation writes the same storage surfaces init uses (~/baize/.env +
10
10
  * ~/.claude/settings.json env + ~/.codex/auth.json + ~/.codex/config.toml)
@@ -31,6 +31,13 @@ import {
31
31
  restartAgentSession,
32
32
  } from './admin-auth.js';
33
33
 
34
+ // D51: Codex provider-block writers shared with the CLI (same package tree).
35
+ import {
36
+ applyCodexProviderBlock,
37
+ codexProviderKeyForId,
38
+ restoreCodexOfficialBlock,
39
+ } from '../../../cli/lib/runtime-setup.js';
40
+
34
41
  // ── Config store (~/.baize/providers.json) ───────────────────────────────────
35
42
 
36
43
  function providersPath() {
@@ -53,13 +60,49 @@ function projectCodexConfigPath() {
53
60
  return path.join(process.env.BAIZE_DIR || path.join(process.env.HOME || '', 'baize'), '.codex', 'config.toml');
54
61
  }
55
62
 
63
+ // Codex-side kind enum (D51 — DeepSeek remote-compaction fix):
64
+ // openai-official use the built-in OpenAI provider (no model_provider block)
65
+ // responses-compatible generic /responses endpoint → dedicated [model_providers.<slug>] block
66
+ // chat-only legacy /chat wire only — Codex removed the Chat wire
67
+ const CODEX_KINDS = new Set(['openai-official', 'responses-compatible', 'chat-only']);
68
+
69
+ // Home/project roots, resolved from the same env vars every other path in
70
+ // this module uses. Passed explicitly to the runtime-setup writers because
71
+ // their os.homedir()/BAIZE_DIR fallbacks don't follow test env overrides
72
+ // (jest sandboxes os.homedir()).
73
+ function homeRoot() {
74
+ return process.env.HOME || '';
75
+ }
76
+ function baizeRoot() {
77
+ return process.env.BAIZE_DIR || path.join(process.env.HOME || '', 'baize');
78
+ }
79
+
56
80
  function loadStore() {
57
81
  const data = readJson(providersPath()) || {};
58
- return {
82
+ const store = {
59
83
  active: typeof data.active === 'string' ? data.active : 'official',
60
84
  providers: Array.isArray(data.providers) ? data.providers : [],
61
85
  stash: data.stash && typeof data.stash === 'object' ? data.stash : null,
62
86
  };
87
+ // Lazy D51 migration: entries saved before kind/providerKey existed get
88
+ // backfilled from the entry shape (baseUrl → responses-compatible) and the
89
+ // store is re-persisted so slugs stay stable from the first read onward.
90
+ let migrated = false;
91
+ for (const p of store.providers) {
92
+ const codex = p.codex && typeof p.codex === 'object' ? p.codex : {};
93
+ if (!codex.kind) {
94
+ codex.kind = codex.baseUrl ? 'responses-compatible' : 'openai-official';
95
+ p.codex = codex;
96
+ migrated = true;
97
+ }
98
+ if (!codex.providerKey && p.id) {
99
+ codex.providerKey = codexProviderKeyForId(p.id);
100
+ p.codex = codex;
101
+ migrated = true;
102
+ }
103
+ }
104
+ if (migrated) saveStore(store);
105
+ return store;
63
106
  }
64
107
 
65
108
  function saveStore(store) {
@@ -91,6 +134,7 @@ function validProviderInput(data) {
91
134
  const urlRe = /^https?:\/\/.+/;
92
135
  if (cc.baseUrl && !urlRe.test(String(cc.baseUrl))) return 'cc.baseUrl must start with http(s)://';
93
136
  if (codex.baseUrl && !urlRe.test(String(codex.baseUrl))) return 'codex.baseUrl must start with http(s)://';
137
+ if (codex.kind && !CODEX_KINDS.has(String(codex.kind))) return `codex.kind must be one of: ${[...CODEX_KINDS].join(', ')}`;
94
138
  return null;
95
139
  }
96
140
 
@@ -106,6 +150,8 @@ function sanitizeProvider(p) {
106
150
  authConfigured: Boolean(p.cc?.authToken),
107
151
  },
108
152
  codex: {
153
+ kind: p.codex?.kind || null,
154
+ providerKey: p.codex?.providerKey || null,
109
155
  baseUrl: p.codex?.baseUrl || null,
110
156
  model: p.codex?.model || null,
111
157
  apiConfigured: Boolean(p.codex?.apiKey),
@@ -131,8 +177,15 @@ export async function saveProvider(input) {
131
177
  const cc = input.cc || {};
132
178
  const codex = input.codex || {};
133
179
  const exists = input.id && store.providers.some((p) => p.id === input.id);
180
+ // D51: keep the pre-existing kind/providerKey on updates (slug is generated
181
+ // once and never regenerated — a rename must not orphan the TOML block).
182
+ const prevCodex = exists ? store.providers.find((p) => p.id === input.id).codex || {} : {};
183
+ const kind = codex.kind
184
+ ? String(codex.kind)
185
+ : (prevCodex.kind || (codex.baseUrl ? 'responses-compatible' : 'openai-official'));
186
+ const id = exists ? input.id : genId(name, store);
134
187
  const entry = {
135
- id: exists ? input.id : genId(name, store),
188
+ id,
136
189
  name,
137
190
  cc: {
138
191
  baseUrl: cc.baseUrl ? String(cc.baseUrl).trim() : undefined,
@@ -141,6 +194,8 @@ export async function saveProvider(input) {
141
194
  smallModel: cc.smallModel ? String(cc.smallModel).trim() : undefined,
142
195
  },
143
196
  codex: {
197
+ kind,
198
+ providerKey: prevCodex.providerKey || codexProviderKeyForId(id),
144
199
  baseUrl: codex.baseUrl ? String(codex.baseUrl).trim() : undefined,
145
200
  apiKey: codex.apiKey ? String(codex.apiKey).trim() : undefined,
146
201
  model: codex.model ? String(codex.model).trim() : undefined,
@@ -165,9 +220,12 @@ function readRuntime() {
165
220
 
166
221
  function genId(name, store) {
167
222
  const base = name.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-+|-+$/g, '') || 'provider';
168
- // 'official' / 'official-openai' are built-in restore targets — reserve them
169
- // so a user-named provider can never shadow or be shadowed by them.
170
- let id = (base === 'official' || base === 'official-openai') ? `${base}-custom` : base;
223
+ // Reserved ids never land in the store: the built-in restore targets
224
+ // ('official' / 'official-openai') plus Codex built-in provider keys that a
225
+ // user-named provider must not shadow (D51 'openai' would make the
226
+ // generated provider block collide with the built-in OpenAI entry).
227
+ const reserved = new Set(['official', 'official-openai', 'openai', 'azure', 'amazon-bedrock', 'amazon-bedrock-runtime', 'ollama', 'lmstudio', 'gpt-oss']);
228
+ let id = reserved.has(base) ? `${base}-custom` : base;
171
229
  let n = 2;
172
230
  while (store.providers.some((p) => p.id === id)) id = `${base}-${n++}`;
173
231
  return id;
@@ -189,7 +247,8 @@ const CC_EXTERNAL_KEYS = ['ANTHROPIC_AUTH_TOKEN', 'ANTHROPIC_BASE_URL', 'ANTHROP
189
247
 
190
248
  /**
191
249
  * Activate a provider or built-in official restore target.
192
- * @returns {Promise<{success: boolean, active: string, warnings: string[], restart?: object, error?: string}>}
250
+ * @returns {Promise<{success: boolean, active: string, warnings: string[],
251
+ * restart?: object, codexProviderKey?: string|null, error?: string}>}
193
252
  */
194
253
  export async function activateProvider(id, deps = {}) {
195
254
  const store = loadStore();
@@ -206,6 +265,14 @@ export async function activateProvider(id, deps = {}) {
206
265
  // current runtime's side is meaningless (e.g. a cc-only provider while the
207
266
  // Codex runtime is active). Official restores and dual-side providers pass.
208
267
  if (provider) {
268
+ // D51: chat-only endpoints have no Codex wiring at all (the Chat wire was
269
+ // removed from Codex) — block activation before anything is written.
270
+ if (provider.codex?.kind === 'chat-only') {
271
+ return {
272
+ success: false,
273
+ error: '该供应商仅支持 Chat 接口,Codex 已移除 Chat wire,请改用 Claude 运行时',
274
+ };
275
+ }
209
276
  const runtime = readRuntime();
210
277
  const hasCc = Boolean(provider.cc?.authToken || provider.cc?.baseUrl || provider.cc?.model || provider.cc?.smallModel);
211
278
  const hasCodex = Boolean(provider.codex?.baseUrl || provider.codex?.apiKey || provider.codex?.model);
@@ -233,10 +300,11 @@ export async function activateProvider(id, deps = {}) {
233
300
  applyClaudeExternal(cc, store);
234
301
  }
235
302
 
303
+ let codexProviderKey = null;
236
304
  if (isOfficialOpenai) {
237
305
  applyCodexOfficial();
238
306
  } else if (provider?.codex) {
239
- applyCodexExternal(provider.codex);
307
+ codexProviderKey = applyCodexExternal(provider.codex) || null;
240
308
  }
241
309
 
242
310
  // Non-blocking reachability probe for configured base URLs (informational).
@@ -251,7 +319,7 @@ export async function activateProvider(id, deps = {}) {
251
319
  store.active = id;
252
320
  saveStore(store);
253
321
  const restart = await (deps.restart ?? restartAgentSession)();
254
- return { success: true, active: id, warnings, restart };
322
+ return { success: true, active: id, warnings, codexProviderKey, restart };
255
323
  }
256
324
 
257
325
  // Claude Code: external provider application
@@ -308,67 +376,37 @@ function applyOfficialClaude(store) {
308
376
  updateSettingsEnv(updates, CC_EXTERNAL_KEYS);
309
377
  }
310
378
 
311
- // Codex: external provider application
379
+ // Codex: external provider application (D51 — dedicated provider block).
380
+ // Returns the active codex provider slug, or null when nothing was written.
312
381
  function applyCodexExternal(codex) {
313
382
  let env = readEnv(envFile());
383
+ // Legacy override kept for health-probe/status consumers that still read it.
314
384
  if (codex.baseUrl) env = upsertEnv(env, 'OPENAI_BASE_URL', codex.baseUrl, 'OpenAI-compatible endpoint (web console)');
315
385
  writeEnvFile(env);
316
386
  if (codex.apiKey) writeCodexAuth(codex.apiKey);
317
- editCodexConfigToml({ model: codex.model, openaiBaseUrl: codex.baseUrl });
387
+ if (codex.kind === 'openai-official') {
388
+ // Official endpoint: keep the built-in OpenAI provider (remote compaction
389
+ // works there) — just drop any leftover custom block/override.
390
+ restoreCodexOfficialBlock({ homeDir: homeRoot(), projectDir: baizeRoot() });
391
+ return null;
392
+ }
393
+ applyCodexProviderBlock({
394
+ providerKey: codex.providerKey || codexProviderKeyForId('provider'),
395
+ baseUrl: codex.baseUrl,
396
+ token: codex.apiKey,
397
+ model: codex.model,
398
+ homeDir: homeRoot(),
399
+ projectDir: baizeRoot(),
400
+ });
401
+ return codex.providerKey || null;
318
402
  }
319
403
 
320
- // Codex: restore official (clear external base_url, default model; auth untouched)
404
+ // Codex: restore official (drop provider block + overrides, reset model; auth untouched)
321
405
  function applyCodexOfficial() {
322
406
  let env = readEnv(envFile());
323
407
  env = removeEnvKey(env, 'OPENAI_BASE_URL');
324
408
  writeEnvFile(env);
325
- editCodexConfigToml({ model: 'gpt-5.5', openaiBaseUrl: null });
326
- }
327
-
328
- /**
329
- * Conservative top-level edit of a Codex config.toml: set/replace the
330
- * `model` and `openai_base_url` keys, preserving every other line (comments,
331
- * sections, providers). Only the TOP-LEVEL prefix (before the first section
332
- * header) is touched — keys inside [table] sections are also unindented, so
333
- * regex edits must never run past the first `[` header.
334
- */
335
- function writeCodexTomlTopLevel(targetPath, { model, openaiBaseUrl }) {
336
- const dir = path.dirname(targetPath);
337
- fs.mkdirSync(dir, { recursive: true });
338
- let content = '';
339
- try { content = fs.readFileSync(targetPath, 'utf8'); } catch { /* new file */ }
340
-
341
- const sectionAt = content.search(/^\[/m);
342
- const head = sectionAt === -1 ? content : content.slice(0, sectionAt);
343
- const tail = sectionAt === -1 ? '' : content.slice(sectionAt);
344
-
345
- const setTopLevel = (text, key, value) => {
346
- const lineRe = new RegExp(`^${key}\\s*=.*$`, 'm');
347
- const line = `${key} = ${JSON.stringify(value)}`;
348
- if (lineRe.test(text)) return text.replace(lineRe, line);
349
- return `${text.trimEnd()}\n${line}\n`;
350
- };
351
- const removeTopLevel = (text, key) => text.replace(new RegExp(`^${key}\\s*=.*\\n?`, 'm'), '');
352
-
353
- let out = head;
354
- if (model) out = setTopLevel(out, 'model', model);
355
- if (openaiBaseUrl) out = setTopLevel(out, 'openai_base_url', openaiBaseUrl);
356
- if (openaiBaseUrl === null) out = removeTopLevel(out, 'openai_base_url');
357
- const joined = tail && !out.endsWith('\n') ? `${out}\n${tail}` : `${out}${tail}`;
358
- fs.writeFileSync(targetPath, joined, 'utf8');
359
- return targetPath;
360
- }
361
-
362
- /**
363
- * Apply a provider's Codex model/base_url to BOTH the global config.toml and
364
- * the baize project-level config.toml. codex prefers project-level keys, so
365
- * writing only the global file let baize init's backfilled defaults (model =
366
- * "gpt-5.5") shadow the provider config (D31).
367
- */
368
- export function editCodexConfigToml({ model, openaiBaseUrl }) {
369
- writeCodexTomlTopLevel(codexConfigPath(), { model, openaiBaseUrl });
370
- writeCodexTomlTopLevel(projectCodexConfigPath(), { model, openaiBaseUrl });
371
- return codexConfigPath();
409
+ restoreCodexOfficialBlock({ homeDir: homeRoot(), projectDir: baizeRoot() });
372
410
  }
373
411
 
374
412
  // Reachability probe: any <500 status means the endpoint answers.
@@ -71,8 +71,9 @@ import {
71
71
  cancelA2aTask,
72
72
  getA2aTaskRuns,
73
73
  getA2aMessages,
74
- installA2aTarball,
74
+ installOrUpgradeA2aTarball,
75
75
  probeA2aDaemonHealthy,
76
+ getA2aBootstrapState,
76
77
  resolveA2aMode,
77
78
  getSchedulerTasks,
78
79
  } from './a2a-admin.js';
@@ -1266,6 +1267,7 @@ app.get('/api/admin/a2a/status', async (req, res) => {
1266
1267
  const payload = { ...(result.json || {}) };
1267
1268
  payload.mode = resolveA2aMode(payload.mode);
1268
1269
  payload.daemonHealthy = await probeA2aDaemonHealthy();
1270
+ payload.bootstrap = getA2aBootstrapState(); // D49: init bootstrap result (launched/ok/failed)
1269
1271
  if (result.success) {
1270
1272
  res.json({ success: true, ...payload });
1271
1273
  } else {
@@ -1385,11 +1387,14 @@ app.get('/api/admin/a2a/messages', (req, res) => {
1385
1387
  }
1386
1388
  });
1387
1389
 
1388
- // Install @baize-ai/baize-a2a from an uploaded .tar.gz (D25 私有交付).
1389
- // multipart field `file`; ≤100MB (413 over); entries vetted against `..` /
1390
- // absolute paths / links before extraction; manifest must be the a2a package
1391
- // with a version; then extract to SKILLS_DIR/a2a → npm install --omit=dev
1392
- // components.json. Response: {ok:true, version} | {ok:false, error}.
1390
+ // Install/upgrade @baize-ai/baize-a2a from an uploaded .tar.gz (D25 私有交付,
1391
+ // D49 K3 双语义). multipart field `file`; ≤100MB (413 over); entries vetted
1392
+ // against `..` / absolute paths / links before extraction. Dispatch (in
1393
+ // a2a-admin.js): 未安装 = 安装 (extract to SKILLS_DIR/a2a → npm install →
1394
+ // components.json); 已安装 = 升级 (version gate new>local else 409 → backup →
1395
+ // extract → npm install+rebuild → components.json bump → a2a cli restart).
1396
+ // Response: {ok:true, version, upgraded?} | {ok:false, error} | 409
1397
+ // {ok:false, error:'version_not_higher', local, incoming}.
1393
1398
  app.post('/api/admin/a2a/install', (req, res) => {
1394
1399
  a2aInstallUpload.single('file')(req, res, async (err) => {
1395
1400
  if (err) {
@@ -1403,10 +1408,13 @@ app.post('/api/admin/a2a/install', (req, res) => {
1403
1408
  return res.status(400).json({ ok: false, error: '缺少 file 字段(multipart 字段名必须为 file)' });
1404
1409
  }
1405
1410
  try {
1406
- const result = await installA2aTarball(file.path, { skillsDir: SKILLS_DIR, originalName: file.originalname });
1411
+ const result = await installOrUpgradeA2aTarball(file.path, { skillsDir: SKILLS_DIR, originalName: file.originalname });
1412
+ if (result.error === 'version_not_higher') {
1413
+ return res.status(409).json(result);
1414
+ }
1407
1415
  res.status(result.ok ? 200 : 400).json(result);
1408
1416
  } catch (e) {
1409
- res.status(500).json({ ok: false, error: `安装失败: ${e.message}` });
1417
+ res.status(500).json({ ok: false, error: `安装/升级失败: ${e.message}` });
1410
1418
  } finally {
1411
1419
  fs.promises.rm(file.path, { force: true }).catch(() => {});
1412
1420
  }
@@ -1550,23 +1558,6 @@ function readAuthz() {
1550
1558
  }
1551
1559
  }
1552
1560
 
1553
- // Validates an allow/block list; returns { list } or { error }.
1554
- function validateAuthzList(value, name) {
1555
- if (!Array.isArray(value)) return { error: `${name} 必须是数组` };
1556
- const seen = new Set();
1557
- const list = [];
1558
- for (const item of value) {
1559
- if (typeof item !== 'string' || item.trim() === '') {
1560
- return { error: `${name} 包含空项(必须是非空字符串)` };
1561
- }
1562
- const v = item.trim();
1563
- if (seen.has(v)) return { error: `${name} 包含重复的 agent_id:${v}` };
1564
- seen.add(v);
1565
- list.push(v);
1566
- }
1567
- return { list };
1568
- }
1569
-
1570
1561
  // Current authz policy (config.json authz; contract defaults when unconfigured)
1571
1562
  app.get('/api/admin/a2a/authz', (req, res) => {
1572
1563
  try {
@@ -1576,8 +1567,11 @@ app.get('/api/admin/a2a/authz', (req, res) => {
1576
1567
  }
1577
1568
  });
1578
1569
 
1579
- // Save the authz policy — validates, then writes config.json (preserving other
1580
- // fields) and returns the persisted policy
1570
+ // Save the authz policy — D52 governance: the LOCAL agent may only flip the
1571
+ // mode (open/allowlist); allow/block lists are admin-managed (admin-workspace
1572
+ // pushes the cluster policy; local lists here would be dead weight / bypass
1573
+ // confusion). Any allow/block sent is ignored and forced to [] — the UI hides
1574
+ // list editing, and a direct API call cannot reintroduce local lists.
1581
1575
  app.put('/api/admin/a2a/authz', (req, res) => {
1582
1576
  try {
1583
1577
  const body = req.body || {};
@@ -1585,11 +1579,7 @@ app.put('/api/admin/a2a/authz', (req, res) => {
1585
1579
  if (mode !== 'open' && mode !== 'allowlist') {
1586
1580
  return res.status(400).json({ success: false, error: 'mode 必须是 open 或 allowlist' });
1587
1581
  }
1588
- const allow = body.allow === undefined ? { list: [] } : validateAuthzList(body.allow, 'allow');
1589
- if (allow.error) return res.status(400).json({ success: false, error: allow.error });
1590
- const block = body.block === undefined ? { list: [] } : validateAuthzList(body.block, 'block');
1591
- if (block.error) return res.status(400).json({ success: false, error: block.error });
1592
- const authz = { mode, allow: allow.list, block: block.list };
1582
+ const authz = { mode, allow: [], block: [] };
1593
1583
  const file = a2aConfigPath();
1594
1584
  let cfg = {};
1595
1585
  try {
@@ -127,6 +127,12 @@ function loadComponentServices() {
127
127
  // Skip components that haven't finished setup (AI-mode install in progress)
128
128
  if (meta && meta.setupComplete === false) continue;
129
129
 
130
+ // D49: the baize-a2a daemon is owned by its own lifecycle manager
131
+ // (daemon-ctl: pm2 + readiness probe + nohup fallback). Registering it
132
+ // here too created a second start path that raced the dedicated one
133
+ // for port 8443 (double-instance) — a2a is intentionally excluded.
134
+ if (name === 'a2a' || (meta && meta.npmPkg === '@baize-ai/baize-a2a')) continue;
135
+
130
136
  const skillDir = (meta && meta.skillDir) || path.join(SKILLS_DIR, name);
131
137
 
132
138
  // Try loading the component's own ecosystem.config.cjs
@@ -237,14 +237,16 @@ describe('installChannel / uninstallChannel', () => {
237
237
  describe('a2a builtin channel (D19 单元 C)', () => {
238
238
  const a2aSchemaKeys = ['enabled', 'adminUrl', 'agentId', 'advertiseUrl', 'listenPort', 'certKeyPath', 'certCertPath'];
239
239
 
240
- test('catalogue includes a2a with the contract config schema (target: config)', async () => {
240
+ test('a2a keeps the contract config schema but is hidden from the catalogue (D33)', async () => {
241
241
  const channels = await ca.discoverChannels({ fetch: noResultsFetch });
242
- const a2a = channels.find((c) => c.name === 'a2a');
243
- expect(a2a).toBeTruthy();
242
+ // Commercial project: a2a must not appear in any channel/component listing.
243
+ expect(channels.find((c) => c.name === 'a2a')).toBeUndefined();
244
+ // The builtin definition (used by configure/status) keeps the D19 schema.
245
+ const a2a = ca.BUILTIN_CHANNELS.a2a;
244
246
  expect(a2a).toMatchObject({
247
+ name: 'a2a',
245
248
  npmPkg: '@baize-ai/baize-a2a',
246
249
  repo: 'baize-ai/baize-a2a',
247
- installed: false,
248
250
  });
249
251
  expect(a2a.configSchema.map((f) => f.key)).toEqual(a2aSchemaKeys);
250
252
  expect(a2a.configSchema.every((f) => f.target === 'config')).toBe(true);
@@ -286,7 +288,7 @@ describe('a2a builtin channel (D19 单元 C)', () => {
286
288
  });
287
289
 
288
290
  test('channelStatus for installed a2a: configured only when enabled + both urls', async () => {
289
- const entry = (await ca.discoverChannels({ fetch: noResultsFetch })).find((c) => c.name === 'a2a');
291
+ const entry = ca.BUILTIN_CHANNELS.a2a;
290
292
  fs.writeFileSync(path.join(baizeDir, '.baize', 'components.json'), JSON.stringify({
291
293
  a2a: { version: '0.1.0', source: { type: 'npm' } },
292
294
  }));
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Child-process driver for the K4 --file upgrade e2e tests: runs the REAL
3
+ * upgradeComponent() flow in a clean process so BAIZE_DIR resolves the fixture
4
+ * root (cli/lib/config.js reads env at import time) — Jest's sandboxed
5
+ * process.env is not inherited by grandchildren, so the flow must run outside
6
+ * the Jest process.
7
+ *
8
+ * argv: <component> <tgzPath> [--check]
9
+ * stdout: the command output (JSON — --json is always passed)
10
+ */
11
+ const [component, tgzPath, checkFlag] = process.argv.slice(2);
12
+ const { upgradeComponent } = await import('../../cli/commands/component.js');
13
+ const args = [component, '--file', tgzPath, '--yes', '--json'];
14
+ if (checkFlag === '--check') args.push('--check');
15
+ await upgradeComponent(args);