@phnx-labs/agents-cli 1.20.52 → 1.20.53
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/CHANGELOG.md +57 -2
- package/README.md +12 -0
- package/dist/bin/agents +0 -0
- package/dist/commands/cloud.d.ts +3 -0
- package/dist/commands/cloud.js +2 -1
- package/dist/commands/exec.js +65 -1
- package/dist/commands/feed.d.ts +29 -0
- package/dist/commands/feed.js +237 -32
- package/dist/commands/memory.d.ts +9 -0
- package/dist/commands/memory.js +164 -0
- package/dist/commands/message.d.ts +11 -6
- package/dist/commands/message.js +140 -5
- package/dist/commands/routines.js +12 -0
- package/dist/commands/secrets-migrate.d.ts +2 -1
- package/dist/commands/secrets-migrate.js +88 -13
- package/dist/commands/secrets.js +4 -1
- package/dist/commands/sessions.js +10 -1
- package/dist/commands/worktree.js +4 -2
- package/dist/index.js +16 -21
- package/dist/lib/agents.js +249 -17
- package/dist/lib/answer-router.d.ts +75 -0
- package/dist/lib/answer-router.js +149 -0
- package/dist/lib/ask-classifier.d.ts +71 -0
- package/dist/lib/ask-classifier.js +197 -0
- package/dist/lib/cloud/antigravity.d.ts +0 -2
- package/dist/lib/cloud/antigravity.js +2 -17
- package/dist/lib/cloud/codex.js +3 -18
- package/dist/lib/cloud/rush.js +3 -15
- package/dist/lib/cloud/stream.js +2 -0
- package/dist/lib/cloud/types.d.ts +21 -0
- package/dist/lib/cloud/types.js +81 -0
- package/dist/lib/crabbox/cli.d.ts +1 -1
- package/dist/lib/crabbox/cli.js +12 -2
- package/dist/lib/crabbox/lease.d.ts +13 -0
- package/dist/lib/crabbox/lease.js +11 -2
- package/dist/lib/crabbox/progress.d.ts +62 -0
- package/dist/lib/crabbox/progress.js +129 -0
- package/dist/lib/events.js +4 -1
- package/dist/lib/exec.js +19 -1
- package/dist/lib/feed-outcome.d.ts +101 -0
- package/dist/lib/feed-outcome.js +244 -0
- package/dist/lib/feed-policy.d.ts +30 -0
- package/dist/lib/feed-policy.js +133 -0
- package/dist/lib/feed.d.ts +127 -3
- package/dist/lib/feed.js +416 -40
- package/dist/lib/git.d.ts +17 -1
- package/dist/lib/git.js +20 -1
- package/dist/lib/hooks.js +522 -12
- package/dist/lib/hosts/passthrough.d.ts +3 -3
- package/dist/lib/hosts/passthrough.js +3 -4
- package/dist/lib/mailbox-gc.d.ts +22 -0
- package/dist/lib/mailbox-gc.js +161 -0
- package/dist/lib/mailbox.d.ts +26 -2
- package/dist/lib/mailbox.js +80 -5
- package/dist/lib/mcp.js +82 -0
- package/dist/lib/memory.d.ts +55 -0
- package/dist/lib/memory.js +274 -0
- package/dist/lib/notify.d.ts +16 -0
- package/dist/lib/notify.js +61 -0
- package/dist/lib/operator.d.ts +26 -0
- package/dist/lib/operator.js +107 -0
- package/dist/lib/plugins.d.ts +35 -0
- package/dist/lib/plugins.js +217 -0
- package/dist/lib/remote-agents-json.d.ts +14 -0
- package/dist/lib/remote-agents-json.js +94 -0
- package/dist/lib/resources/mcp.js +44 -0
- package/dist/lib/resources/memory.d.ts +15 -0
- package/dist/lib/resources/memory.js +46 -0
- package/dist/lib/resources/types.d.ts +2 -2
- package/dist/lib/runner.d.ts +43 -0
- package/dist/lib/runner.js +323 -74
- package/dist/lib/sandbox.js +6 -0
- package/dist/lib/secrets/bundles.js +38 -13
- package/dist/lib/secrets/icloud-import.d.ts +12 -3
- package/dist/lib/secrets/icloud-import.js +37 -7
- package/dist/lib/secrets/index.d.ts +106 -2
- package/dist/lib/secrets/index.js +603 -28
- package/dist/lib/session/active.d.ts +18 -0
- package/dist/lib/session/active.js +47 -17
- package/dist/lib/session/db.d.ts +9 -1
- package/dist/lib/session/db.js +18 -3
- package/dist/lib/session/discover.d.ts +13 -0
- package/dist/lib/session/discover.js +31 -0
- package/dist/lib/session/parse.d.ts +8 -0
- package/dist/lib/session/parse.js +42 -21
- package/dist/lib/session/remote-active.js +8 -89
- package/dist/lib/session/state.d.ts +11 -0
- package/dist/lib/session/state.js +37 -0
- package/dist/lib/session/tail.d.ts +23 -4
- package/dist/lib/session/tail.js +34 -16
- package/dist/lib/session/throughput.d.ts +30 -0
- package/dist/lib/session/throughput.js +86 -0
- package/dist/lib/shim-heal.d.ts +12 -3
- package/dist/lib/shim-heal.js +12 -6
- package/dist/lib/staleness/detectors/subagents.js +57 -3
- package/dist/lib/staleness/writers/hooks.js +7 -3
- package/dist/lib/staleness/writers/subagents.js +37 -6
- package/dist/lib/startup/command-registry.d.ts +1 -0
- package/dist/lib/startup/command-registry.js +2 -0
- package/dist/lib/subagents.d.ts +52 -0
- package/dist/lib/subagents.js +315 -12
- package/dist/lib/teams/worktree.d.ts +8 -0
- package/dist/lib/teams/worktree.js +8 -0
- package/dist/lib/types.d.ts +10 -2
- package/dist/lib/versions.js +38 -48
- package/package.json +4 -3
- package/scripts/postinstall.js +61 -1
package/dist/lib/subagents.js
CHANGED
|
@@ -262,6 +262,153 @@ export function transformSubagentForDroid(subagentDir) {
|
|
|
262
262
|
}
|
|
263
263
|
return result;
|
|
264
264
|
}
|
|
265
|
+
/**
|
|
266
|
+
* Transform a subagent into an OpenCode agent markdown file.
|
|
267
|
+
*
|
|
268
|
+
* OpenCode loads agents from ~/.config/opencode/agents/*.md (global) with
|
|
269
|
+
* YAML frontmatter (description required; mode: subagent for invocable
|
|
270
|
+
* subagents) and a prompt body. File stem becomes the agent name.
|
|
271
|
+
* https://opencode.ai/docs/agents/
|
|
272
|
+
*/
|
|
273
|
+
export function transformSubagentForOpenCode(subagentDir) {
|
|
274
|
+
const agentMd = path.join(subagentDir, 'AGENT.md');
|
|
275
|
+
const frontmatter = parseSubagentFrontmatter(agentMd);
|
|
276
|
+
const body = getSubagentBody(agentMd);
|
|
277
|
+
if (!frontmatter) {
|
|
278
|
+
throw new Error(`Invalid AGENT.md in ${subagentDir}`);
|
|
279
|
+
}
|
|
280
|
+
const fm = {
|
|
281
|
+
description: frontmatter.description,
|
|
282
|
+
mode: 'subagent',
|
|
283
|
+
};
|
|
284
|
+
if (frontmatter.model)
|
|
285
|
+
fm.model = frontmatter.model;
|
|
286
|
+
let systemPrompt = body.trim();
|
|
287
|
+
const files = fs.readdirSync(subagentDir)
|
|
288
|
+
.filter(f => f.endsWith('.md') && f !== 'AGENT.md')
|
|
289
|
+
.sort();
|
|
290
|
+
for (const file of files) {
|
|
291
|
+
const content = fs.readFileSync(path.join(subagentDir, file), 'utf-8').trim();
|
|
292
|
+
const sectionName = file.replace('.md', '');
|
|
293
|
+
const title = sectionName.charAt(0).toUpperCase() + sectionName.slice(1).toLowerCase();
|
|
294
|
+
systemPrompt += `\n\n## ${title}\n\n${content}`;
|
|
295
|
+
}
|
|
296
|
+
return `---\n${yaml.stringify(fm).trim()}\n---\n\n${systemPrompt}\n`;
|
|
297
|
+
}
|
|
298
|
+
/** Managed parent agent file for Kimi (underscore prefix avoids clobbering a user subagent named agents-cli). */
|
|
299
|
+
export const KIMI_SUBAGENTS_PARENT_FILE = '_agents-cli.yaml';
|
|
300
|
+
/**
|
|
301
|
+
* Transform a subagent into Kimi Code agent files.
|
|
302
|
+
*
|
|
303
|
+
* Kimi's schema (agentspec) only accepts `system_prompt_path` (path relative to
|
|
304
|
+
* the agent YAML) — there is no inline `system_prompt` key. We write:
|
|
305
|
+
* ~/.kimi-code/agents/<name>.yaml — agent YAML
|
|
306
|
+
* ~/.kimi-code/agents/<name>.system.md — system prompt body
|
|
307
|
+
* plus a managed parent `_agents-cli.yaml` that lists them under agent.subagents
|
|
308
|
+
* for `kimi --agent-file …/_agents-cli.yaml`.
|
|
309
|
+
* https://moonshotai.github.io/kimi-cli/en/customization/agents.html
|
|
310
|
+
*/
|
|
311
|
+
export function transformSubagentForKimi(subagentDir, fileBaseName) {
|
|
312
|
+
const agentMd = path.join(subagentDir, 'AGENT.md');
|
|
313
|
+
const frontmatter = parseSubagentFrontmatter(agentMd);
|
|
314
|
+
const body = getSubagentBody(agentMd);
|
|
315
|
+
if (!frontmatter) {
|
|
316
|
+
throw new Error(`Invalid AGENT.md in ${subagentDir}`);
|
|
317
|
+
}
|
|
318
|
+
let systemPrompt = body.trim();
|
|
319
|
+
const files = fs.readdirSync(subagentDir)
|
|
320
|
+
.filter(f => f.endsWith('.md') && f !== 'AGENT.md')
|
|
321
|
+
.sort();
|
|
322
|
+
for (const file of files) {
|
|
323
|
+
const content = fs.readFileSync(path.join(subagentDir, file), 'utf-8').trim();
|
|
324
|
+
const sectionName = file.replace('.md', '');
|
|
325
|
+
const title = sectionName.charAt(0).toUpperCase() + sectionName.slice(1).toLowerCase();
|
|
326
|
+
systemPrompt += `\n\n## ${title}\n\n${content}`;
|
|
327
|
+
}
|
|
328
|
+
const systemPromptFileName = `${fileBaseName}.system.md`;
|
|
329
|
+
const doc = {
|
|
330
|
+
version: 1,
|
|
331
|
+
agent: {
|
|
332
|
+
extend: 'default',
|
|
333
|
+
name: frontmatter.name,
|
|
334
|
+
description: frontmatter.description,
|
|
335
|
+
system_prompt_path: `./${systemPromptFileName}`,
|
|
336
|
+
},
|
|
337
|
+
};
|
|
338
|
+
if (frontmatter.model) {
|
|
339
|
+
doc.agent.model = frontmatter.model;
|
|
340
|
+
}
|
|
341
|
+
return {
|
|
342
|
+
yaml: yaml.stringify(doc),
|
|
343
|
+
systemPrompt,
|
|
344
|
+
systemPromptFileName,
|
|
345
|
+
};
|
|
346
|
+
}
|
|
347
|
+
/**
|
|
348
|
+
* Build the managed parent agent YAML that declares all installed subagents
|
|
349
|
+
* so `kimi --agent-file ~/.kimi-code/agents/_agents-cli.yaml` can launch them.
|
|
350
|
+
*/
|
|
351
|
+
export function buildKimiSubagentsParentYaml(entries) {
|
|
352
|
+
const subagents = {};
|
|
353
|
+
for (const e of entries) {
|
|
354
|
+
subagents[e.name] = { path: e.relativePath, description: e.description };
|
|
355
|
+
}
|
|
356
|
+
return yaml.stringify({
|
|
357
|
+
version: 1,
|
|
358
|
+
agent: {
|
|
359
|
+
extend: 'default',
|
|
360
|
+
name: 'agents-cli',
|
|
361
|
+
description: 'Managed parent agent listing agents-cli synced subagents',
|
|
362
|
+
subagents,
|
|
363
|
+
},
|
|
364
|
+
});
|
|
365
|
+
}
|
|
366
|
+
/** Write a Kimi subagent YAML + sibling system-prompt md into agentsDir. */
|
|
367
|
+
export function writeKimiSubagentFiles(agentsDir, subagentDir, name) {
|
|
368
|
+
const { yaml: yml, systemPrompt, systemPromptFileName } = transformSubagentForKimi(subagentDir, name);
|
|
369
|
+
fs.mkdirSync(agentsDir, { recursive: true });
|
|
370
|
+
fs.writeFileSync(safeJoin(agentsDir, `${name}.yaml`), yml);
|
|
371
|
+
fs.writeFileSync(safeJoin(agentsDir, systemPromptFileName), systemPrompt);
|
|
372
|
+
}
|
|
373
|
+
/**
|
|
374
|
+
* Transform a subagent into a Codex custom-agent TOML file.
|
|
375
|
+
*
|
|
376
|
+
* Codex loads standalone TOML under ~/.codex/agents/*.toml. Required fields:
|
|
377
|
+
* name, description, developer_instructions. Optional model maps from our
|
|
378
|
+
* frontmatter when present.
|
|
379
|
+
* https://developers.openai.com/codex/subagents (custom agents section)
|
|
380
|
+
*/
|
|
381
|
+
export function transformSubagentForCodex(subagentDir) {
|
|
382
|
+
const agentMd = path.join(subagentDir, 'AGENT.md');
|
|
383
|
+
const frontmatter = parseSubagentFrontmatter(agentMd);
|
|
384
|
+
const body = getSubagentBody(agentMd);
|
|
385
|
+
if (!frontmatter) {
|
|
386
|
+
throw new Error(`Invalid AGENT.md in ${subagentDir}`);
|
|
387
|
+
}
|
|
388
|
+
// Append other .md files into the developer_instructions body.
|
|
389
|
+
let instructions = body.trim();
|
|
390
|
+
const files = fs.readdirSync(subagentDir)
|
|
391
|
+
.filter(f => f.endsWith('.md') && f !== 'AGENT.md')
|
|
392
|
+
.sort();
|
|
393
|
+
for (const file of files) {
|
|
394
|
+
const content = fs.readFileSync(path.join(subagentDir, file), 'utf-8').trim();
|
|
395
|
+
const sectionName = file.replace('.md', '');
|
|
396
|
+
const title = sectionName.charAt(0).toUpperCase() + sectionName.slice(1).toLowerCase();
|
|
397
|
+
instructions += `\n\n## ${title}\n\n${content}`;
|
|
398
|
+
}
|
|
399
|
+
// Escape TOML multi-line string (""") content — only """ needs escaping.
|
|
400
|
+
const safeInstructions = instructions.replace(/"""/g, '\\"""');
|
|
401
|
+
const safeName = frontmatter.name.replace(/\\/g, '\\\\').replace(/"/g, '\\"');
|
|
402
|
+
const safeDesc = frontmatter.description.replace(/\\/g, '\\\\').replace(/"/g, '\\"');
|
|
403
|
+
let toml = `name = "${safeName}"\n`;
|
|
404
|
+
toml += `description = "${safeDesc}"\n`;
|
|
405
|
+
if (frontmatter.model) {
|
|
406
|
+
const safeModel = String(frontmatter.model).replace(/\\/g, '\\\\').replace(/"/g, '\\"');
|
|
407
|
+
toml += `model = "${safeModel}"\n`;
|
|
408
|
+
}
|
|
409
|
+
toml += `developer_instructions = """\n${safeInstructions}\n"""\n`;
|
|
410
|
+
return toml;
|
|
411
|
+
}
|
|
265
412
|
/**
|
|
266
413
|
* Sync a subagent to an OpenClaw workspace
|
|
267
414
|
* Copies full directory, renames AGENT.md to AGENTS.md
|
|
@@ -291,9 +438,10 @@ export function syncSubagentToOpenclaw(subagentDir, targetDir) {
|
|
|
291
438
|
* Install a subagent to a specific agent's home
|
|
292
439
|
*/
|
|
293
440
|
export function installSubagentToAgent(subagentDir, subagentName, agent, agentHome) {
|
|
294
|
-
if (agent === 'claude') {
|
|
295
|
-
// Claude: flatten to single .md
|
|
296
|
-
|
|
441
|
+
if (agent === 'claude' || agent === 'grok') {
|
|
442
|
+
// Claude / Grok: flatten to single .md under ~/.claude/agents or ~/.grok/agents
|
|
443
|
+
// Grok discovers user agent defs from ~/.grok/agents/*.md (same shape as Claude).
|
|
444
|
+
const agentsDir = path.join(agentHome, agent === 'grok' ? '.grok' : '.claude', 'agents');
|
|
297
445
|
if (!fs.existsSync(agentsDir)) {
|
|
298
446
|
fs.mkdirSync(agentsDir, { recursive: true });
|
|
299
447
|
}
|
|
@@ -306,6 +454,44 @@ export function installSubagentToAgent(subagentDir, subagentName, agent, agentHo
|
|
|
306
454
|
return { success: false, error: String(err) };
|
|
307
455
|
}
|
|
308
456
|
}
|
|
457
|
+
else if (agent === 'codex') {
|
|
458
|
+
// Codex: standalone TOML under ~/.codex/agents/<name>.toml
|
|
459
|
+
const agentsDir = path.join(agentHome, '.codex', 'agents');
|
|
460
|
+
if (!fs.existsSync(agentsDir)) {
|
|
461
|
+
fs.mkdirSync(agentsDir, { recursive: true });
|
|
462
|
+
}
|
|
463
|
+
try {
|
|
464
|
+
const toml = transformSubagentForCodex(subagentDir);
|
|
465
|
+
fs.writeFileSync(safeJoin(agentsDir, `${subagentName}.toml`), toml);
|
|
466
|
+
return { success: true };
|
|
467
|
+
}
|
|
468
|
+
catch (err) {
|
|
469
|
+
return { success: false, error: String(err) };
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
else if (agent === 'kimi') {
|
|
473
|
+
// Kimi: YAML + sibling system-prompt md under ~/.kimi-code/agents/
|
|
474
|
+
try {
|
|
475
|
+
writeKimiSubagentFiles(path.join(agentHome, '.kimi-code', 'agents'), subagentDir, subagentName);
|
|
476
|
+
return { success: true };
|
|
477
|
+
}
|
|
478
|
+
catch (err) {
|
|
479
|
+
return { success: false, error: String(err) };
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
else if (agent === 'opencode') {
|
|
483
|
+
// OpenCode: markdown agents under ~/.config/opencode/agents/
|
|
484
|
+
const agentsDir = path.join(agentHome, '.config', 'opencode', 'agents');
|
|
485
|
+
if (!fs.existsSync(agentsDir))
|
|
486
|
+
fs.mkdirSync(agentsDir, { recursive: true });
|
|
487
|
+
try {
|
|
488
|
+
fs.writeFileSync(safeJoin(agentsDir, `${subagentName}.md`), transformSubagentForOpenCode(subagentDir));
|
|
489
|
+
return { success: true };
|
|
490
|
+
}
|
|
491
|
+
catch (err) {
|
|
492
|
+
return { success: false, error: String(err) };
|
|
493
|
+
}
|
|
494
|
+
}
|
|
309
495
|
else if (agent === 'openclaw') {
|
|
310
496
|
// OpenClaw: copy full directory
|
|
311
497
|
const targetDir = safeJoin(path.join(agentHome, '.openclaw'), subagentName);
|
|
@@ -321,13 +507,37 @@ export function installSubagentToAgent(subagentDir, subagentName, agent, agentHo
|
|
|
321
507
|
*/
|
|
322
508
|
export function removeSubagentFromAgent(subagentName, agent, agentHome) {
|
|
323
509
|
try {
|
|
324
|
-
if (agent === 'claude') {
|
|
325
|
-
const
|
|
510
|
+
if (agent === 'claude' || agent === 'grok') {
|
|
511
|
+
const agentsRoot = agent === 'grok' ? '.grok' : '.claude';
|
|
512
|
+
const targetPath = safeJoin(path.join(agentHome, agentsRoot, 'agents'), `${subagentName}.md`);
|
|
513
|
+
if (fs.existsSync(targetPath)) {
|
|
514
|
+
fs.unlinkSync(targetPath);
|
|
515
|
+
}
|
|
516
|
+
return { success: true };
|
|
517
|
+
}
|
|
518
|
+
else if (agent === 'codex') {
|
|
519
|
+
const targetPath = safeJoin(path.join(agentHome, '.codex', 'agents'), `${subagentName}.toml`);
|
|
326
520
|
if (fs.existsSync(targetPath)) {
|
|
327
521
|
fs.unlinkSync(targetPath);
|
|
328
522
|
}
|
|
329
523
|
return { success: true };
|
|
330
524
|
}
|
|
525
|
+
else if (agent === 'kimi') {
|
|
526
|
+
const agentsDir = path.join(agentHome, '.kimi-code', 'agents');
|
|
527
|
+
const targetPath = safeJoin(agentsDir, `${subagentName}.yaml`);
|
|
528
|
+
const promptPath = safeJoin(agentsDir, `${subagentName}.system.md`);
|
|
529
|
+
if (fs.existsSync(targetPath))
|
|
530
|
+
fs.unlinkSync(targetPath);
|
|
531
|
+
if (fs.existsSync(promptPath))
|
|
532
|
+
fs.unlinkSync(promptPath);
|
|
533
|
+
return { success: true };
|
|
534
|
+
}
|
|
535
|
+
else if (agent === 'opencode') {
|
|
536
|
+
const targetPath = safeJoin(path.join(agentHome, '.config', 'opencode', 'agents'), `${subagentName}.md`);
|
|
537
|
+
if (fs.existsSync(targetPath))
|
|
538
|
+
fs.unlinkSync(targetPath);
|
|
539
|
+
return { success: true };
|
|
540
|
+
}
|
|
331
541
|
else if (agent === 'openclaw') {
|
|
332
542
|
const targetDir = safeJoin(path.join(agentHome, '.openclaw'), subagentName);
|
|
333
543
|
if (fs.existsSync(targetDir)) {
|
|
@@ -373,13 +583,14 @@ export function subagentContentMatches(installedDir, sourceDir) {
|
|
|
373
583
|
/**
|
|
374
584
|
* List subagents installed to a specific agent's home
|
|
375
585
|
* Claude: scans ~/.claude/agents/{name}.md
|
|
586
|
+
* Kimi: scans ~/.kimi-code/agents/{name}.yaml (+ sibling .system.md)
|
|
376
587
|
* OpenClaw: scans ~/.openclaw/{name}/AGENTS.md
|
|
377
588
|
*/
|
|
378
589
|
export function listSubagentsForAgent(agentId, home) {
|
|
379
590
|
const subagents = [];
|
|
380
|
-
if (agentId === 'claude') {
|
|
381
|
-
// Claude: flat .md files in agents/
|
|
382
|
-
const agentsDir = path.join(home, '.claude', 'agents');
|
|
591
|
+
if (agentId === 'claude' || agentId === 'grok') {
|
|
592
|
+
// Claude / Grok: flat .md files in agents/
|
|
593
|
+
const agentsDir = path.join(home, agentId === 'grok' ? '.grok' : '.claude', 'agents');
|
|
383
594
|
if (!fs.existsSync(agentsDir))
|
|
384
595
|
return subagents;
|
|
385
596
|
for (const file of fs.readdirSync(agentsDir)) {
|
|
@@ -400,6 +611,51 @@ export function listSubagentsForAgent(agentId, home) {
|
|
|
400
611
|
});
|
|
401
612
|
}
|
|
402
613
|
}
|
|
614
|
+
else if (agentId === 'kimi') {
|
|
615
|
+
// Kimi: flat YAML under .kimi-code/agents/ (exclude managed parent _*.yaml)
|
|
616
|
+
const agentsDir = path.join(home, '.kimi-code', 'agents');
|
|
617
|
+
if (!fs.existsSync(agentsDir))
|
|
618
|
+
return subagents;
|
|
619
|
+
for (const file of fs.readdirSync(agentsDir)) {
|
|
620
|
+
if (!file.endsWith('.yaml') || file.startsWith('_'))
|
|
621
|
+
continue;
|
|
622
|
+
const filePath = path.join(agentsDir, file);
|
|
623
|
+
if (!fs.statSync(filePath).isFile())
|
|
624
|
+
continue;
|
|
625
|
+
const name = file.replace(/\.yaml$/, '');
|
|
626
|
+
let description = '';
|
|
627
|
+
try {
|
|
628
|
+
const parsed = yaml.parse(fs.readFileSync(filePath, 'utf-8'));
|
|
629
|
+
description = parsed?.agent?.description ?? '';
|
|
630
|
+
}
|
|
631
|
+
catch { /* leave description empty */ }
|
|
632
|
+
const files = [file];
|
|
633
|
+
const promptFile = `${name}.system.md`;
|
|
634
|
+
if (fs.existsSync(path.join(agentsDir, promptFile)))
|
|
635
|
+
files.push(promptFile);
|
|
636
|
+
subagents.push({
|
|
637
|
+
name,
|
|
638
|
+
path: filePath,
|
|
639
|
+
files,
|
|
640
|
+
frontmatter: { name, description },
|
|
641
|
+
});
|
|
642
|
+
}
|
|
643
|
+
}
|
|
644
|
+
else if (agentId === 'opencode') {
|
|
645
|
+
const agentsDir = path.join(home, '.config', 'opencode', 'agents');
|
|
646
|
+
if (!fs.existsSync(agentsDir))
|
|
647
|
+
return subagents;
|
|
648
|
+
for (const file of fs.readdirSync(agentsDir)) {
|
|
649
|
+
if (!file.endsWith('.md'))
|
|
650
|
+
continue;
|
|
651
|
+
const filePath = path.join(agentsDir, file);
|
|
652
|
+
if (!fs.statSync(filePath).isFile())
|
|
653
|
+
continue;
|
|
654
|
+
const name = file.replace(/\.md$/, '');
|
|
655
|
+
const frontmatter = parseSubagentFrontmatter(filePath) ?? { name, description: '' };
|
|
656
|
+
subagents.push({ name, path: filePath, files: [file], frontmatter });
|
|
657
|
+
}
|
|
658
|
+
}
|
|
403
659
|
else if (agentId === 'openclaw') {
|
|
404
660
|
// OpenClaw: directories with AGENTS.md
|
|
405
661
|
const openclawDir = path.join(home, '.openclaw');
|
|
@@ -462,8 +718,8 @@ export function diffVersionSubagents(agent, version) {
|
|
|
462
718
|
}
|
|
463
719
|
}
|
|
464
720
|
// Check what's installed
|
|
465
|
-
if (agent === 'claude') {
|
|
466
|
-
const agentsDir = path.join(versionHome, '.claude', 'agents');
|
|
721
|
+
if (agent === 'claude' || agent === 'grok') {
|
|
722
|
+
const agentsDir = path.join(versionHome, agent === 'grok' ? '.grok' : '.claude', 'agents');
|
|
467
723
|
if (fs.existsSync(agentsDir)) {
|
|
468
724
|
for (const file of fs.readdirSync(agentsDir)) {
|
|
469
725
|
if (!file.endsWith('.md'))
|
|
@@ -475,6 +731,31 @@ export function diffVersionSubagents(agent, version) {
|
|
|
475
731
|
}
|
|
476
732
|
}
|
|
477
733
|
}
|
|
734
|
+
else if (agent === 'kimi') {
|
|
735
|
+
const agentsDir = path.join(versionHome, '.kimi-code', 'agents');
|
|
736
|
+
if (fs.existsSync(agentsDir)) {
|
|
737
|
+
for (const file of fs.readdirSync(agentsDir)) {
|
|
738
|
+
if (!file.endsWith('.yaml') || file.startsWith('_'))
|
|
739
|
+
continue;
|
|
740
|
+
const name = path.basename(file, '.yaml');
|
|
741
|
+
if (!discovered.has(name)) {
|
|
742
|
+
orphans.push(name);
|
|
743
|
+
}
|
|
744
|
+
}
|
|
745
|
+
}
|
|
746
|
+
}
|
|
747
|
+
else if (agent === 'opencode') {
|
|
748
|
+
const agentsDir = path.join(versionHome, '.config', 'opencode', 'agents');
|
|
749
|
+
if (fs.existsSync(agentsDir)) {
|
|
750
|
+
for (const file of fs.readdirSync(agentsDir)) {
|
|
751
|
+
if (!file.endsWith('.md'))
|
|
752
|
+
continue;
|
|
753
|
+
const name = path.basename(file, '.md');
|
|
754
|
+
if (!discovered.has(name))
|
|
755
|
+
orphans.push(name);
|
|
756
|
+
}
|
|
757
|
+
}
|
|
758
|
+
}
|
|
478
759
|
else if (agent === 'openclaw') {
|
|
479
760
|
const openclawDir = path.join(versionHome, '.openclaw');
|
|
480
761
|
if (fs.existsSync(openclawDir)) {
|
|
@@ -516,8 +797,30 @@ export function removeSubagentFromVersion(agent, version, subagentName) {
|
|
|
516
797
|
const stamp = new Date().toISOString().replace(/[:.]/g, '-');
|
|
517
798
|
const trashDir = path.join(getTrashSubagentsDir(), agent, version, subagentName);
|
|
518
799
|
try {
|
|
519
|
-
if (agent === 'claude') {
|
|
520
|
-
const
|
|
800
|
+
if (agent === 'claude' || agent === 'grok') {
|
|
801
|
+
const agentsRoot = agent === 'grok' ? '.grok' : '.claude';
|
|
802
|
+
const targetPath = path.join(versionHome, agentsRoot, 'agents', `${subagentName}.md`);
|
|
803
|
+
if (fs.existsSync(targetPath)) {
|
|
804
|
+
fs.mkdirSync(trashDir, { recursive: true, mode: 0o700 });
|
|
805
|
+
fs.renameSync(targetPath, path.join(trashDir, `${subagentName}.md.${stamp}`));
|
|
806
|
+
}
|
|
807
|
+
}
|
|
808
|
+
else if (agent === 'kimi') {
|
|
809
|
+
const agentsDir = path.join(versionHome, '.kimi-code', 'agents');
|
|
810
|
+
const yamlPath = path.join(agentsDir, `${subagentName}.yaml`);
|
|
811
|
+
const promptPath = path.join(agentsDir, `${subagentName}.system.md`);
|
|
812
|
+
if (fs.existsSync(yamlPath) || fs.existsSync(promptPath)) {
|
|
813
|
+
fs.mkdirSync(trashDir, { recursive: true, mode: 0o700 });
|
|
814
|
+
if (fs.existsSync(yamlPath)) {
|
|
815
|
+
fs.renameSync(yamlPath, path.join(trashDir, `${subagentName}.yaml.${stamp}`));
|
|
816
|
+
}
|
|
817
|
+
if (fs.existsSync(promptPath)) {
|
|
818
|
+
fs.renameSync(promptPath, path.join(trashDir, `${subagentName}.system.md.${stamp}`));
|
|
819
|
+
}
|
|
820
|
+
}
|
|
821
|
+
}
|
|
822
|
+
else if (agent === 'opencode') {
|
|
823
|
+
const targetPath = path.join(versionHome, '.config', 'opencode', 'agents', `${subagentName}.md`);
|
|
521
824
|
if (fs.existsSync(targetPath)) {
|
|
522
825
|
fs.mkdirSync(trashDir, { recursive: true, mode: 0o700 });
|
|
523
826
|
fs.renameSync(targetPath, path.join(trashDir, `${subagentName}.md.${stamp}`));
|
|
@@ -1,3 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Check if `dir` is inside a git repository (**async, worktree-correct**).
|
|
3
|
+
*
|
|
4
|
+
* Shells out to `git rev-parse --git-dir`, so it returns true from any
|
|
5
|
+
* subdirectory and for linked worktrees. Distinct from the synchronous,
|
|
6
|
+
* root-only `isGitRepo` in `lib/git.ts` (a `.git`-existence check): different
|
|
7
|
+
* semantics, so the two are intentionally **not** merged.
|
|
8
|
+
*/
|
|
1
9
|
export declare function isGitRepo(dir: string): Promise<boolean>;
|
|
2
10
|
export declare function getGitRoot(dir: string): Promise<string>;
|
|
3
11
|
/**
|
|
@@ -11,6 +11,14 @@ import * as path from 'path';
|
|
|
11
11
|
import { safeJoin } from '../paths.js';
|
|
12
12
|
const execFileAsync = promisify(execFile);
|
|
13
13
|
const WORKTREE_NAME_RE = /^[A-Za-z0-9_-]+$/;
|
|
14
|
+
/**
|
|
15
|
+
* Check if `dir` is inside a git repository (**async, worktree-correct**).
|
|
16
|
+
*
|
|
17
|
+
* Shells out to `git rev-parse --git-dir`, so it returns true from any
|
|
18
|
+
* subdirectory and for linked worktrees. Distinct from the synchronous,
|
|
19
|
+
* root-only `isGitRepo` in `lib/git.ts` (a `.git`-existence check): different
|
|
20
|
+
* semantics, so the two are intentionally **not** merged.
|
|
21
|
+
*/
|
|
14
22
|
export async function isGitRepo(dir) {
|
|
15
23
|
try {
|
|
16
24
|
await execFileAsync('git', ['rev-parse', '--git-dir'], { cwd: dir });
|
package/dist/lib/types.d.ts
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
*/
|
|
8
8
|
import type { CloudProviderId } from './cloud/types.js';
|
|
9
9
|
/** Unique identifier for a supported AI coding agent. */
|
|
10
|
-
export type AgentId = 'claude' | 'codex' | 'gemini' | 'cursor' | 'opencode' | 'openclaw' | 'copilot' | 'amp' | 'kiro' | 'goose' | 'antigravity' | 'grok' | 'kimi' | 'droid';
|
|
10
|
+
export type AgentId = 'claude' | 'codex' | 'gemini' | 'cursor' | 'opencode' | 'openclaw' | 'copilot' | 'amp' | 'kiro' | 'goose' | 'antigravity' | 'grok' | 'kimi' | 'droid' | 'hermes' | 'forge';
|
|
11
11
|
/** How `agents run <agent>` chooses an installed version when none is pinned. */
|
|
12
12
|
export type RunStrategy = 'pinned' | 'available' | 'balanced';
|
|
13
13
|
/** Per-agent run strategy config. */
|
|
@@ -148,6 +148,12 @@ export interface AgentConfig {
|
|
|
148
148
|
subagents: Capability;
|
|
149
149
|
rules: RulesCapability;
|
|
150
150
|
workflows: Capability;
|
|
151
|
+
/**
|
|
152
|
+
* Portable knowledge-store memory (`agents memory` / ~/.agents/memory/).
|
|
153
|
+
* Distinct from `rules` (instructions). When true, sync fans facts into
|
|
154
|
+
* the agent version home (see memoryTargetDir).
|
|
155
|
+
*/
|
|
156
|
+
memory: Capability;
|
|
151
157
|
/**
|
|
152
158
|
* Permission modes this agent natively supports. Modes outside this set
|
|
153
159
|
* are gated by buildExecCommand: `auto` silently degrades to `edit`,
|
|
@@ -177,7 +183,7 @@ export type RulesCapability = false | {
|
|
|
177
183
|
file: string;
|
|
178
184
|
};
|
|
179
185
|
/** Names of every gateable capability on AgentConfig. */
|
|
180
|
-
export type CapabilityName = 'hooks' | 'mcp' | 'mcpHttp' | 'mcpHeaders' | 'allowlist' | 'skills' | 'commands' | 'plugins' | 'subagents' | 'rules' | 'workflows';
|
|
186
|
+
export type CapabilityName = 'hooks' | 'mcp' | 'mcpHttp' | 'mcpHeaders' | 'allowlist' | 'skills' | 'commands' | 'plugins' | 'subagents' | 'rules' | 'workflows' | 'memory';
|
|
181
187
|
/**
|
|
182
188
|
* Permission modes controlling agent autonomy.
|
|
183
189
|
* plan read-only investigation; no writes, no shell side-effects
|
|
@@ -516,6 +522,8 @@ export interface DiscoveredPlugin {
|
|
|
516
522
|
commands: string[];
|
|
517
523
|
/** Subagent .md files in the plugin's agents/ directory (names without extension). */
|
|
518
524
|
agentDefs: string[];
|
|
525
|
+
/** Memory fact basenames from the plugin's memory/ directory (without .md). */
|
|
526
|
+
memory: string[];
|
|
519
527
|
/** Executable files in the plugin's bin/ directory. */
|
|
520
528
|
bin: string[];
|
|
521
529
|
/** MCP server names parsed from .mcp.json. */
|
package/dist/lib/versions.js
CHANGED
|
@@ -42,6 +42,7 @@ import { emit } from './events.js';
|
|
|
42
42
|
import { safeJoin } from './paths.js';
|
|
43
43
|
import { readSkillSourceCommandMarker, shouldInstallCommandAsSkill } from './command-skills.js';
|
|
44
44
|
import { getWriter, getDetector } from './staleness/registry.js';
|
|
45
|
+
import { syncMemoryToVersionHome } from './memory.js';
|
|
45
46
|
/** Promisified exec for running shell commands. */
|
|
46
47
|
const execAsync = promisify(exec);
|
|
47
48
|
const execFileAsync = promisify(execFile);
|
|
@@ -1127,19 +1128,28 @@ export async function installVersion(agent, version, onProgress, opts) {
|
|
|
1127
1128
|
emit('version.install', { agent, version: installedVersion });
|
|
1128
1129
|
return { success: true, installedVersion };
|
|
1129
1130
|
}
|
|
1130
|
-
// Resolve the `oldest`
|
|
1131
|
-
// of the install path treats
|
|
1132
|
-
// keeps
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1131
|
+
// Resolve the `latest`/`oldest` aliases to a concrete npm version up front so
|
|
1132
|
+
// the rest of the install path treats them as an ordinary pinned install.
|
|
1133
|
+
// This is what keeps concurrent installs safe: resolving `latest` only AFTER
|
|
1134
|
+
// npm finished meant the install ran in a shared, well-known
|
|
1135
|
+
// `versions/<agent>/latest/` scratch dir that was renamed to the real version
|
|
1136
|
+
// at the end — so a concurrent `agents view` reconcile
|
|
1137
|
+
// (reconcileStaleLatestForAgent) or a second `latest` install could rename
|
|
1138
|
+
// that dir out from under npm mid-extraction and corrupt the install (the
|
|
1139
|
+
// seeded package.json and half-extracted node_modules would vanish, yielding
|
|
1140
|
+
// ENOENT). A concrete dir per version has no shared name to race on.
|
|
1141
|
+
if (version === 'latest' || version === 'oldest') {
|
|
1142
|
+
const resolved = version === 'latest'
|
|
1143
|
+
? await getLatestNpmVersion(agent)
|
|
1144
|
+
: await getOldestNpmVersion(agent);
|
|
1145
|
+
if (!resolved) {
|
|
1136
1146
|
return {
|
|
1137
1147
|
success: false,
|
|
1138
1148
|
installedVersion: version,
|
|
1139
|
-
error: `Could not resolve the
|
|
1149
|
+
error: `Could not resolve the ${version} published version for ${agentConfig.name} from npm.`,
|
|
1140
1150
|
};
|
|
1141
1151
|
}
|
|
1142
|
-
version =
|
|
1152
|
+
version = resolved;
|
|
1143
1153
|
}
|
|
1144
1154
|
ensureAgentsDir();
|
|
1145
1155
|
const versionDir = getVersionDir(agent, version);
|
|
@@ -1161,12 +1171,11 @@ export async function installVersion(agent, version, onProgress, opts) {
|
|
|
1161
1171
|
private: true,
|
|
1162
1172
|
};
|
|
1163
1173
|
fs.writeFileSync(path.join(versionDir, 'package.json'), JSON.stringify(packageJson, null, 2));
|
|
1164
|
-
// Install the package
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
// (which VERSION_RE permits to start with `-`) is never passed as a standalone npm CLI flag.
|
|
1174
|
+
// Install the package. `version` is always concrete here (`latest`/`oldest`
|
|
1175
|
+
// were resolved above), so the spec is always pinned. The `@` prefix is
|
|
1176
|
+
// load-bearing: it ensures `version` (which VERSION_RE permits to start with
|
|
1177
|
+
// `-`) is never passed as a standalone npm CLI flag.
|
|
1178
|
+
const packageSpec = `${agentConfig.npmPackage}@${version}`;
|
|
1170
1179
|
try {
|
|
1171
1180
|
// Check npm is available
|
|
1172
1181
|
const winShell = process.platform === 'win32';
|
|
@@ -1181,37 +1190,12 @@ export async function installVersion(agent, version, onProgress, opts) {
|
|
|
1181
1190
|
};
|
|
1182
1191
|
}
|
|
1183
1192
|
onProgress?.(`Installing ${packageSpec}...`);
|
|
1184
|
-
|
|
1185
|
-
//
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
try {
|
|
1191
|
-
const installedPkgPath = path.join(versionDir, 'node_modules', agentConfig.npmPackage, 'package.json');
|
|
1192
|
-
if (fs.existsSync(installedPkgPath)) {
|
|
1193
|
-
const installedPkg = JSON.parse(fs.readFileSync(installedPkgPath, 'utf-8'));
|
|
1194
|
-
installedVersion = installedPkg.version;
|
|
1195
|
-
// Rename the directory to the actual version
|
|
1196
|
-
if (installedVersion !== 'latest') {
|
|
1197
|
-
const actualVersionDir = getVersionDir(agent, installedVersion);
|
|
1198
|
-
if (!fs.existsSync(actualVersionDir)) {
|
|
1199
|
-
fs.renameSync(versionDir, actualVersionDir);
|
|
1200
|
-
}
|
|
1201
|
-
else {
|
|
1202
|
-
// Already exists — drop the 'latest' install artifacts but keep
|
|
1203
|
-
// `home/` (may contain conversation history from sessions that
|
|
1204
|
-
// ran while the user was on `latest`).
|
|
1205
|
-
removeInstallArtifacts(versionDir);
|
|
1206
|
-
}
|
|
1207
|
-
}
|
|
1208
|
-
}
|
|
1209
|
-
}
|
|
1210
|
-
catch (e) {
|
|
1211
|
-
// Failed to determine version - this shouldn't happen
|
|
1212
|
-
throw new Error(`Failed to determine installed version: ${e.message}`);
|
|
1213
|
-
}
|
|
1214
|
-
}
|
|
1193
|
+
await execFileAsync('npm', ['install', packageSpec, '--ignore-scripts'], { cwd: versionDir, shell: winShell });
|
|
1194
|
+
// `version` is concrete (the `latest`/`oldest` aliases were resolved up
|
|
1195
|
+
// front), so the package installed directly into its final versioned dir —
|
|
1196
|
+
// no post-install rename, and no shared `latest/` dir for a concurrent
|
|
1197
|
+
// process to move out from under us.
|
|
1198
|
+
const installedVersion = version;
|
|
1215
1199
|
// Create versioned alias (e.g., claude@2.0.65)
|
|
1216
1200
|
createVersionedAlias(agent, installedVersion);
|
|
1217
1201
|
// Claude reads its global config from CLAUDE_CONFIG_DIR/.claude.json —
|
|
@@ -1241,9 +1225,9 @@ export async function installVersion(agent, version, onProgress, opts) {
|
|
|
1241
1225
|
// binary (postinstall failed, or a platform with no published native dep)
|
|
1242
1226
|
// fails there with the correct message rather than throwing here.
|
|
1243
1227
|
if (agentConfig.npmPackage) {
|
|
1244
|
-
//
|
|
1245
|
-
//
|
|
1246
|
-
const pkgRoot = path.join(
|
|
1228
|
+
// `installedVersion === version`, so this is exactly `versionDir` — the
|
|
1229
|
+
// install landed in its final dir with no rename to chase.
|
|
1230
|
+
const pkgRoot = path.join(versionDir, 'node_modules', agentConfig.npmPackage);
|
|
1247
1231
|
try {
|
|
1248
1232
|
const pkg = JSON.parse(fs.readFileSync(path.join(pkgRoot, 'package.json'), 'utf-8'));
|
|
1249
1233
|
const postinstall = pkg?.scripts?.postinstall;
|
|
@@ -2335,6 +2319,12 @@ export function syncResourcesToVersion(agent, version, selection, options = {})
|
|
|
2335
2319
|
const r = workflowsWriter.write({ version, versionHome, selection: workflowsToSync, cwd });
|
|
2336
2320
|
result.workflows.push(...r.synced);
|
|
2337
2321
|
}
|
|
2322
|
+
// Knowledge memory (RUSH-1330) — distinct from selection.memory which still
|
|
2323
|
+
// means the composed *rules* file. Always fan out ~/.agents/memory/ facts
|
|
2324
|
+
// into capable agent version homes on every full or partial sync.
|
|
2325
|
+
if (supports(agent, 'memory', version).ok) {
|
|
2326
|
+
syncMemoryToVersionHome(agent, versionHome, cwd);
|
|
2327
|
+
}
|
|
2338
2328
|
// Write manifest after a full sync (no user-passed selection) so the next
|
|
2339
2329
|
// launch can skip the slow path. Pattern-derived selections still count as
|
|
2340
2330
|
// "full" — the agents.yaml patterns describe the intended scope, not a
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@phnx-labs/agents-cli",
|
|
3
|
-
"version": "1.20.
|
|
3
|
+
"version": "1.20.53",
|
|
4
4
|
"description": "One CLI for all your AI coding agents - versions, config, cloud dispatch, sessions, and teams (now with first-class Grok Build CLI support)",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -24,6 +24,7 @@
|
|
|
24
24
|
"dist/**/*.js",
|
|
25
25
|
"dist/**/*.d.ts",
|
|
26
26
|
"dist/**/*.json",
|
|
27
|
+
"dist/bin/**",
|
|
27
28
|
"dist/lib/secrets/Agents CLI.app/**",
|
|
28
29
|
"dist/lib/menubar/MenubarHelper.app/**",
|
|
29
30
|
"scripts/postinstall.js",
|
|
@@ -44,10 +45,10 @@
|
|
|
44
45
|
"url": "https://github.com/phnx-labs/agents-cli/issues"
|
|
45
46
|
},
|
|
46
47
|
"scripts": {
|
|
47
|
-
"build": "tsc && rm -rf 'dist/lib/secrets/AgentsKeychain.app' 'dist/lib/secrets/Agents CLI.app' 'dist/lib/menubar/MenubarHelper.app' && ([ -d 'bin/Agents CLI.app' ] && cp -R 'bin/Agents CLI.app' 'dist/lib/secrets/Agents CLI.app' || true) && ([ -d 'bin/MenubarHelper.app' ] && mkdir -p 'dist/lib/menubar' && cp -R 'bin/MenubarHelper.app' 'dist/lib/menubar/MenubarHelper.app' || true)",
|
|
48
|
+
"build": "tsc && rm -rf 'dist/lib/secrets/AgentsKeychain.app' 'dist/lib/secrets/Agents CLI.app' 'dist/lib/menubar/MenubarHelper.app' && ([ -d 'bin/Agents CLI.app' ] && cp -R 'bin/Agents CLI.app' 'dist/lib/secrets/Agents CLI.app' || true) && ([ -d 'bin/MenubarHelper.app' ] && mkdir -p 'dist/lib/menubar' && cp -R 'bin/MenubarHelper.app' 'dist/lib/menubar/MenubarHelper.app' || true) && ([ -f 'bin/agents-macos' ] && mkdir -p dist/bin && cp -f 'bin/agents-macos' 'dist/bin/agents' && chmod 755 'dist/bin/agents' || true)",
|
|
48
49
|
"build:bin": "scripts/build-bin.sh",
|
|
49
50
|
"prepare": "npm run build",
|
|
50
|
-
"prepack": "cp ../../README.md README.md && scripts/verify-keychain-helper.sh && scripts/verify-menubar-helper.sh",
|
|
51
|
+
"prepack": "cp ../../README.md README.md && scripts/verify-keychain-helper.sh && scripts/verify-menubar-helper.sh && scripts/verify-cli-binary.sh",
|
|
51
52
|
"postinstall": "node scripts/postinstall.js",
|
|
52
53
|
"dev": "tsx src/index.ts",
|
|
53
54
|
"start": "node dist/index.js",
|