brainctl 0.1.17 → 0.1.19

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (111) hide show
  1. package/dist/cli.d.ts +9 -11
  2. package/dist/cli.js +17 -22
  3. package/dist/commands/doctor.d.ts +1 -1
  4. package/dist/commands/mcp.js +2 -2
  5. package/dist/commands/profile.d.ts +7 -3
  6. package/dist/commands/profile.js +106 -16
  7. package/dist/commands/status.d.ts +1 -1
  8. package/dist/commands/status.js +7 -7
  9. package/dist/{mcp/server.d.ts → mcp-server.d.ts} +1 -1
  10. package/dist/{mcp/server.js → mcp-server.js} +56 -149
  11. package/dist/services/agent/agent-asset-installer.d.ts +3 -0
  12. package/dist/services/agent/agent-asset-installer.js +109 -0
  13. package/dist/services/agent/agent-availability-service.d.ts +11 -0
  14. package/dist/services/agent/agent-availability-service.js +32 -0
  15. package/dist/services/{agent-config-service.d.ts → agent/agent-config-service.d.ts} +6 -6
  16. package/dist/services/{agent-config-service.js → agent/agent-config-service.js} +6 -6
  17. package/dist/services/{credential-redaction-service.d.ts → credential/credential-redaction-service.d.ts} +2 -1
  18. package/dist/services/{credential-redaction-service.js → credential/credential-redaction-service.js} +9 -3
  19. package/dist/services/{credential-resolution-service.d.ts → credential/credential-resolution-service.d.ts} +1 -1
  20. package/dist/services/{doctor-service.d.ts → platform/doctor-service.d.ts} +3 -3
  21. package/dist/services/platform/doctor-service.js +23 -0
  22. package/dist/services/{mcp-preflight-service.d.ts → platform/mcp-preflight-service.d.ts} +2 -2
  23. package/dist/services/{mcp-preflight-service.js → platform/mcp-preflight-service.js} +1 -1
  24. package/dist/services/{runtime-detector.d.ts → platform/runtime-detector.d.ts} +1 -1
  25. package/dist/services/platform/status-service.d.ts +19 -0
  26. package/dist/services/platform/status-service.js +22 -0
  27. package/dist/services/{update-check-service.js → platform/update-check-service.js} +1 -1
  28. package/dist/services/plugin/plugin-install-bundle.d.ts +20 -0
  29. package/dist/services/plugin/plugin-install-bundle.js +80 -0
  30. package/dist/services/plugin/plugin-install-compatibility.d.ts +15 -0
  31. package/dist/services/plugin/plugin-install-compatibility.js +91 -0
  32. package/dist/services/plugin/plugin-install-fs.d.ts +27 -0
  33. package/dist/services/plugin/plugin-install-fs.js +65 -0
  34. package/dist/services/{plugin-install-service.d.ts → plugin/plugin-install-service.d.ts} +4 -18
  35. package/dist/services/{plugin-install-service.js → plugin/plugin-install-service.js} +7 -308
  36. package/dist/services/plugin/plugin-install-uninstall.d.ts +12 -0
  37. package/dist/services/plugin/plugin-install-uninstall.js +76 -0
  38. package/dist/services/{skill-paths.d.ts → plugin/skill-paths.d.ts} +1 -1
  39. package/dist/services/{skill-preflight-service.d.ts → plugin/skill-preflight-service.d.ts} +1 -1
  40. package/dist/services/{portable-mcp-classifier.d.ts → profile/portable-mcp-classifier.d.ts} +3 -3
  41. package/dist/services/{portable-mcp-classifier.js → profile/portable-mcp-classifier.js} +2 -2
  42. package/dist/services/{portable-profile-pack-service.d.ts → profile/portable-profile-pack-service.d.ts} +8 -2
  43. package/dist/services/{portable-profile-pack-service.js → profile/portable-profile-pack-service.js} +83 -9
  44. package/dist/services/profile/profile-apply-service.d.ts +34 -0
  45. package/dist/services/profile/profile-apply-service.js +102 -0
  46. package/dist/services/{profile-export-service.d.ts → profile/profile-export-service.d.ts} +7 -3
  47. package/dist/services/{profile-export-service.js → profile/profile-export-service.js} +3 -1
  48. package/dist/services/{profile-import-service.d.ts → profile/profile-import-service.d.ts} +1 -1
  49. package/dist/services/{profile-import-service.js → profile/profile-import-service.js} +85 -130
  50. package/dist/services/{profile-service.d.ts → profile/profile-service.d.ts} +3 -11
  51. package/dist/services/{profile-service.js → profile/profile-service.js} +58 -103
  52. package/dist/services/profile/profile-snapshot-service.d.ts +12 -0
  53. package/dist/services/profile/profile-snapshot-service.js +47 -0
  54. package/dist/types.d.ts +2 -57
  55. package/dist/ui/routes.d.ts +1 -3
  56. package/dist/ui/routes.js +79 -128
  57. package/dist/ui/server.d.ts +1 -1
  58. package/dist/web/assets/index-CGmTbSgk.js +63 -0
  59. package/dist/web/assets/index-EIVU5Woh.css +2 -0
  60. package/dist/web/index.html +2 -2
  61. package/package.json +1 -1
  62. package/dist/commands/init.d.ts +0 -3
  63. package/dist/commands/init.js +0 -27
  64. package/dist/commands/run.d.ts +0 -3
  65. package/dist/commands/run.js +0 -25
  66. package/dist/commands/sync.d.ts +0 -3
  67. package/dist/commands/sync.js +0 -31
  68. package/dist/config.d.ts +0 -14
  69. package/dist/config.js +0 -96
  70. package/dist/context/builder.d.ts +0 -6
  71. package/dist/context/builder.js +0 -13
  72. package/dist/context/memory.d.ts +0 -5
  73. package/dist/context/memory.js +0 -43
  74. package/dist/context/skills.d.ts +0 -2
  75. package/dist/context/skills.js +0 -8
  76. package/dist/executor/claude.d.ts +0 -12
  77. package/dist/executor/claude.js +0 -16
  78. package/dist/executor/codex.d.ts +0 -12
  79. package/dist/executor/codex.js +0 -16
  80. package/dist/executor/process.d.ts +0 -11
  81. package/dist/executor/process.js +0 -40
  82. package/dist/executor/resolver.d.ts +0 -13
  83. package/dist/executor/resolver.js +0 -60
  84. package/dist/executor/types.d.ts +0 -14
  85. package/dist/executor/types.js +0 -1
  86. package/dist/services/config-write-service.d.ts +0 -12
  87. package/dist/services/config-write-service.js +0 -70
  88. package/dist/services/doctor-service.js +0 -79
  89. package/dist/services/init-service.d.ts +0 -14
  90. package/dist/services/init-service.js +0 -88
  91. package/dist/services/memory-write-service.d.ts +0 -12
  92. package/dist/services/memory-write-service.js +0 -56
  93. package/dist/services/run-service.d.ts +0 -15
  94. package/dist/services/run-service.js +0 -94
  95. package/dist/services/status-service.d.ts +0 -17
  96. package/dist/services/status-service.js +0 -21
  97. package/dist/services/sync-service.d.ts +0 -15
  98. package/dist/services/sync-service.js +0 -69
  99. package/dist/ui/streaming.d.ts +0 -3
  100. package/dist/ui/streaming.js +0 -16
  101. package/dist/web/assets/index-Bbophmwh.css +0 -2
  102. package/dist/web/assets/index-DDG_ylui.js +0 -63
  103. /package/dist/{system/executables.d.ts → executables.d.ts} +0 -0
  104. /package/dist/{system/executables.js → executables.js} +0 -0
  105. /package/dist/services/{agent-converter-service.d.ts → agent/agent-converter-service.d.ts} +0 -0
  106. /package/dist/services/{agent-converter-service.js → agent/agent-converter-service.js} +0 -0
  107. /package/dist/services/{credential-resolution-service.js → credential/credential-resolution-service.js} +0 -0
  108. /package/dist/services/{runtime-detector.js → platform/runtime-detector.js} +0 -0
  109. /package/dist/services/{update-check-service.d.ts → platform/update-check-service.d.ts} +0 -0
  110. /package/dist/services/{skill-paths.js → plugin/skill-paths.js} +0 -0
  111. /package/dist/services/{skill-preflight-service.js → plugin/skill-preflight-service.js} +0 -0
@@ -0,0 +1,65 @@
1
+ import { cp, mkdir, rm, writeFile } from 'node:fs/promises';
2
+ import path from 'node:path';
3
+ import { claudeAgentMdToCodexToml, claudeCommandMdToCodexSkill, codexAgentTomlToClaudeMd, } from '../agent/agent-converter-service.js';
4
+ import { getAgentFilePath, getCommandFilePath, getSkillDir } from './skill-paths.js';
5
+ export async function defaultInstallAgent(options) {
6
+ const targetPath = getAgentFilePath(options.targetAgent, options.agent.name);
7
+ await mkdir(path.dirname(targetPath), { recursive: true });
8
+ let output;
9
+ if (options.targetAgent === 'claude') {
10
+ output = options.agent.sourceFormat === 'claude-md'
11
+ ? options.agent.content
12
+ : codexAgentTomlToClaudeMd(options.agent.content);
13
+ }
14
+ else if (options.targetAgent === 'codex') {
15
+ output = options.agent.sourceFormat === 'codex-toml'
16
+ ? options.agent.content
17
+ : claudeAgentMdToCodexToml(options.agent.content);
18
+ }
19
+ else {
20
+ throw new Error(`Agent install is not supported for ${options.targetAgent}`);
21
+ }
22
+ await writeFile(targetPath, output, 'utf8');
23
+ }
24
+ export async function defaultInstallCommand(options) {
25
+ if (options.targetAgent === 'claude') {
26
+ const targetPath = getCommandFilePath('claude', options.command.name);
27
+ await mkdir(path.dirname(targetPath), { recursive: true });
28
+ await writeFile(targetPath, options.command.content, 'utf8');
29
+ return;
30
+ }
31
+ if (options.targetAgent === 'codex') {
32
+ const skillDir = getSkillDir('codex', options.command.name);
33
+ await mkdir(skillDir, { recursive: true });
34
+ const { skillMarkdown } = claudeCommandMdToCodexSkill(options.command.content);
35
+ await writeFile(path.join(skillDir, 'SKILL.md'), skillMarkdown, 'utf8');
36
+ return;
37
+ }
38
+ throw new Error(`Command install is not supported for ${options.targetAgent}`);
39
+ }
40
+ export async function defaultRemoveAgentFile(options) {
41
+ const targetPath = getAgentFilePath(options.targetAgent, options.agentName);
42
+ await rm(targetPath, { force: true });
43
+ }
44
+ export async function defaultRemoveCommandFile(options) {
45
+ if (options.targetAgent === 'claude') {
46
+ const targetPath = getCommandFilePath('claude', options.commandName);
47
+ await rm(targetPath, { force: true });
48
+ return;
49
+ }
50
+ if (options.targetAgent === 'codex') {
51
+ const skillDir = getSkillDir('codex', options.commandName);
52
+ await rm(skillDir, { recursive: true, force: true });
53
+ return;
54
+ }
55
+ }
56
+ export async function defaultCopySkillDirectory(options) {
57
+ const sourceDir = path.join(options.sourceInstallPath, 'skills', options.skillName);
58
+ const targetDir = getSkillDir(options.targetAgent, options.skillName);
59
+ await mkdir(path.dirname(targetDir), { recursive: true });
60
+ await cp(sourceDir, targetDir, { recursive: true });
61
+ }
62
+ export async function defaultRemoveSkillDirectory(options) {
63
+ const targetDir = getSkillDir(options.targetAgent, options.skillName);
64
+ await rm(targetDir, { recursive: true, force: true });
65
+ }
@@ -1,19 +1,12 @@
1
- import type { AgentName } from '../types.js';
2
- import type { AgentLiveConfig, AgentMcpEntry, AgentSkillEntry } from './agent-config-service.js';
1
+ import type { AgentName } from '../../types.js';
2
+ import type { AgentLiveConfig, AgentMcpEntry, AgentSkillEntry } from '../agent/agent-config-service.js';
3
+ import { type PluginBundle, type PluginBundleAgent, type PluginBundleCommand } from './plugin-install-bundle.js';
4
+ export type { PluginBundleAgent, PluginBundleCommand };
3
5
  export interface PluginInstallCheck {
4
6
  label: string;
5
7
  status: 'ok' | 'warn' | 'error';
6
8
  message: string;
7
9
  }
8
- export interface PluginBundleAgent {
9
- name: string;
10
- sourceFormat: 'claude-md' | 'codex-toml';
11
- content: string;
12
- }
13
- export interface PluginBundleCommand {
14
- name: string;
15
- content: string;
16
- }
17
10
  export interface PluginInstallPlan {
18
11
  ok: boolean;
19
12
  checks: PluginInstallCheck[];
@@ -66,12 +59,6 @@ export interface PluginInstallService {
66
59
  plugin: AgentSkillEntry;
67
60
  }): Promise<PluginUninstallResult>;
68
61
  }
69
- interface PluginBundle {
70
- skills: string[];
71
- mcps: Record<string, AgentMcpEntry>;
72
- agents: PluginBundleAgent[];
73
- commands: PluginBundleCommand[];
74
- }
75
62
  interface PluginInstallDependencies {
76
63
  readInstalledPluginBundle?: (installPath: string) => Promise<PluginBundle>;
77
64
  readTargetState?: (options: {
@@ -132,4 +119,3 @@ interface PluginInstallDependencies {
132
119
  }) => Promise<void>;
133
120
  }
134
121
  export declare function createPluginInstallService(dependencies?: PluginInstallDependencies): PluginInstallService;
135
- export {};
@@ -1,14 +1,11 @@
1
- import { spawn } from 'node:child_process';
2
- import { copyFile, cp, mkdir, readdir, readFile, rename, rm, stat, writeFile } from 'node:fs/promises';
3
- import { homedir } from 'node:os';
4
- import path from 'node:path';
5
- import { ValidationError } from '../errors.js';
6
- import { formatTimestamp } from './sync/agent-writer.js';
7
- import { stripPluginSection } from './sync/codex-writer.js';
8
- import { createAgentConfigService } from './agent-config-service.js';
9
- import { claudeAgentMdToCodexToml, claudeCommandMdToCodexSkill, codexAgentTomlToClaudeMd, } from './agent-converter-service.js';
1
+ import { ValidationError } from '../../errors.js';
2
+ import { createAgentConfigService } from '../agent/agent-config-service.js';
10
3
  import { getAgentFilePath, getCommandFilePath, getSkillDir } from './skill-paths.js';
11
- import { removeManagedPluginInstall, writeManagedPluginInstall, } from './sync/managed-plugin-registry.js';
4
+ import { removeManagedPluginInstall, writeManagedPluginInstall, } from '../sync/managed-plugin-registry.js';
5
+ import { defaultCopySkillDirectory, defaultInstallAgent, defaultInstallCommand, defaultRemoveAgentFile, defaultRemoveCommandFile, defaultRemoveSkillDirectory, } from './plugin-install-fs.js';
6
+ import { detectIncompatibleArtifacts, formatCompatibilityWarnings, pathExists, } from './plugin-install-compatibility.js';
7
+ import { defaultReadInstalledPluginBundle, isAgentInstallableOnTarget, isCommandInstallableOnTarget, } from './plugin-install-bundle.js';
8
+ import { defaultUninstallClaudePlugin, defaultUninstallCodexPlugin, isUnmanagedClaudePlugin, isUnmanagedCodexPlugin, } from './plugin-install-uninstall.js';
12
9
  export function createPluginInstallService(dependencies = {}) {
13
10
  const agentConfigService = createAgentConfigService();
14
11
  const readInstalledPluginBundle = dependencies.readInstalledPluginBundle ?? defaultReadInstalledPluginBundle;
@@ -301,301 +298,3 @@ export function createPluginInstallService(dependencies = {}) {
301
298
  },
302
299
  };
303
300
  }
304
- function isUnmanagedCodexPlugin(targetAgent, plugin) {
305
- return (!plugin.managed &&
306
- targetAgent === 'codex' &&
307
- typeof plugin.installPath === 'string' &&
308
- typeof plugin.source === 'string' &&
309
- plugin.source.length > 0);
310
- }
311
- function isUnmanagedClaudePlugin(targetAgent, plugin) {
312
- return (!plugin.managed &&
313
- targetAgent === 'claude' &&
314
- typeof plugin.installPath === 'string' &&
315
- typeof plugin.source === 'string' &&
316
- plugin.source.length > 0);
317
- }
318
- async function defaultUninstallClaudePlugin(options) {
319
- // Delegate to `claude plugin uninstall` so a running Claude Code session
320
- // drops the plugin from its in-memory state (direct fs mutation gets
321
- // resurrected by live sessions that still have the plugin loaded).
322
- await new Promise((resolve, reject) => {
323
- const child = spawn('claude', ['plugin', 'uninstall', options.pluginKey, '--scope', 'user'], { stdio: ['ignore', 'pipe', 'pipe'] });
324
- let stderr = '';
325
- let stdout = '';
326
- child.stdout?.on('data', (chunk) => {
327
- stdout += chunk.toString();
328
- });
329
- child.stderr?.on('data', (chunk) => {
330
- stderr += chunk.toString();
331
- });
332
- child.on('error', (error) => {
333
- reject(new ValidationError(`Failed to invoke \`claude\` CLI: ${error.message}. Is Claude Code installed on PATH?`));
334
- });
335
- child.on('exit', (code) => {
336
- if (code === 0) {
337
- resolve();
338
- return;
339
- }
340
- const detail = (stderr || stdout).trim();
341
- reject(new ValidationError(`\`claude plugin uninstall ${options.pluginKey}\` exited ${code}${detail ? `: ${detail}` : ''}`));
342
- });
343
- });
344
- }
345
- async function defaultUninstallCodexPlugin(options) {
346
- const home = homedir();
347
- const cacheRoot = path.join(home, '.codex', 'plugins', 'cache');
348
- const resolvedInstall = path.resolve(options.installPath);
349
- if (!resolvedInstall.startsWith(cacheRoot + path.sep)) {
350
- throw new ValidationError(`Refusing to remove Codex plugin files outside ${cacheRoot}: ${resolvedInstall}`);
351
- }
352
- const pluginRoot = path.dirname(resolvedInstall);
353
- const configPath = path.join(home, '.codex', 'config.toml');
354
- let existing = '';
355
- try {
356
- existing = await readFile(configPath, 'utf8');
357
- }
358
- catch {
359
- existing = '';
360
- }
361
- if (existing.length > 0) {
362
- const next = stripPluginSection(existing, options.pluginKey);
363
- if (next !== existing) {
364
- const backupPath = `${configPath}.bak.${formatTimestamp()}`;
365
- await copyFile(configPath, backupPath);
366
- const tmpPath = `${configPath}.tmp.${Date.now()}`;
367
- await writeFile(tmpPath, next, 'utf8');
368
- await rename(tmpPath, configPath);
369
- }
370
- }
371
- await rm(pluginRoot, { recursive: true, force: true });
372
- }
373
- async function defaultReadInstalledPluginBundle(installPath) {
374
- const skillsDir = path.join(installPath, 'skills');
375
- let skills = [];
376
- try {
377
- const { readdir } = await import('node:fs/promises');
378
- const entries = await readdir(skillsDir, { withFileTypes: true });
379
- skills = entries
380
- .filter((entry) => !entry.name.startsWith('.') && entry.isDirectory())
381
- .map((entry) => entry.name)
382
- .sort((left, right) => left.localeCompare(right));
383
- }
384
- catch {
385
- skills = [];
386
- }
387
- let mcps = {};
388
- try {
389
- const mcpSource = await readFile(path.join(installPath, '.mcp.json'), 'utf8');
390
- const parsed = JSON.parse(mcpSource);
391
- mcps = Object.fromEntries(Object.entries(parsed)
392
- .filter(([, value]) => typeof value?.command === 'string')
393
- .map(([key, value]) => [
394
- key,
395
- {
396
- command: String(value.command),
397
- args: Array.isArray(value.args) ? value.args.map(String) : undefined,
398
- env: value.env && typeof value.env === 'object' && !Array.isArray(value.env)
399
- ? Object.fromEntries(Object.entries(value.env).map(([envKey, envValue]) => [
400
- envKey,
401
- String(envValue),
402
- ]))
403
- : undefined,
404
- },
405
- ]));
406
- }
407
- catch {
408
- mcps = {};
409
- }
410
- const agents = [];
411
- try {
412
- const entries = await readdir(path.join(installPath, 'agents'), { withFileTypes: true });
413
- for (const entry of entries) {
414
- if (!entry.isFile())
415
- continue;
416
- if (entry.name.endsWith('.md')) {
417
- const content = await readFile(path.join(installPath, 'agents', entry.name), 'utf8');
418
- agents.push({ name: entry.name.replace(/\.md$/, ''), sourceFormat: 'claude-md', content });
419
- }
420
- else if (entry.name.endsWith('.toml')) {
421
- const content = await readFile(path.join(installPath, 'agents', entry.name), 'utf8');
422
- agents.push({ name: entry.name.replace(/\.toml$/, ''), sourceFormat: 'codex-toml', content });
423
- }
424
- }
425
- agents.sort((left, right) => left.name.localeCompare(right.name));
426
- }
427
- catch {
428
- // no agents dir
429
- }
430
- const commands = [];
431
- try {
432
- const entries = await readdir(path.join(installPath, 'commands'), { withFileTypes: true });
433
- for (const entry of entries) {
434
- if (!entry.isFile() || !entry.name.endsWith('.md'))
435
- continue;
436
- const content = await readFile(path.join(installPath, 'commands', entry.name), 'utf8');
437
- commands.push({ name: entry.name.replace(/\.md$/, ''), content });
438
- }
439
- commands.sort((left, right) => left.name.localeCompare(right.name));
440
- }
441
- catch {
442
- // no commands dir
443
- }
444
- return { skills, mcps, agents, commands };
445
- }
446
- function isAgentInstallableOnTarget(target) {
447
- return target === 'claude' || target === 'codex';
448
- }
449
- function isCommandInstallableOnTarget(target) {
450
- return target === 'claude' || target === 'codex';
451
- }
452
- async function defaultInstallAgent(options) {
453
- const targetPath = getAgentFilePath(options.targetAgent, options.agent.name);
454
- await mkdir(path.dirname(targetPath), { recursive: true });
455
- let output;
456
- if (options.targetAgent === 'claude') {
457
- output = options.agent.sourceFormat === 'claude-md'
458
- ? options.agent.content
459
- : codexAgentTomlToClaudeMd(options.agent.content);
460
- }
461
- else if (options.targetAgent === 'codex') {
462
- output = options.agent.sourceFormat === 'codex-toml'
463
- ? options.agent.content
464
- : claudeAgentMdToCodexToml(options.agent.content);
465
- }
466
- else {
467
- throw new Error(`Agent install is not supported for ${options.targetAgent}`);
468
- }
469
- await writeFile(targetPath, output, 'utf8');
470
- }
471
- async function defaultInstallCommand(options) {
472
- if (options.targetAgent === 'claude') {
473
- const targetPath = getCommandFilePath('claude', options.command.name);
474
- await mkdir(path.dirname(targetPath), { recursive: true });
475
- await writeFile(targetPath, options.command.content, 'utf8');
476
- return;
477
- }
478
- if (options.targetAgent === 'codex') {
479
- const skillDir = getSkillDir('codex', options.command.name);
480
- await mkdir(skillDir, { recursive: true });
481
- const { skillMarkdown } = claudeCommandMdToCodexSkill(options.command.content);
482
- await writeFile(path.join(skillDir, 'SKILL.md'), skillMarkdown, 'utf8');
483
- return;
484
- }
485
- throw new Error(`Command install is not supported for ${options.targetAgent}`);
486
- }
487
- async function defaultRemoveAgentFile(options) {
488
- const targetPath = getAgentFilePath(options.targetAgent, options.agentName);
489
- await rm(targetPath, { force: true });
490
- }
491
- async function defaultRemoveCommandFile(options) {
492
- if (options.targetAgent === 'claude') {
493
- const targetPath = getCommandFilePath('claude', options.commandName);
494
- await rm(targetPath, { force: true });
495
- return;
496
- }
497
- if (options.targetAgent === 'codex') {
498
- const skillDir = getSkillDir('codex', options.commandName);
499
- await rm(skillDir, { recursive: true, force: true });
500
- return;
501
- }
502
- }
503
- async function defaultCopySkillDirectory(options) {
504
- const sourceDir = path.join(options.sourceInstallPath, 'skills', options.skillName);
505
- const targetDir = getSkillDir(options.targetAgent, options.skillName);
506
- await mkdir(path.dirname(targetDir), { recursive: true });
507
- await cp(sourceDir, targetDir, { recursive: true });
508
- }
509
- async function defaultRemoveSkillDirectory(options) {
510
- const targetDir = getSkillDir(options.targetAgent, options.skillName);
511
- await rm(targetDir, { recursive: true, force: true });
512
- }
513
- async function detectIncompatibleArtifacts(installPath) {
514
- const [hasAppConnector, hasHooks, hasCommands, codexAgentSkills, claudeAgents] = await Promise.all([
515
- pathExists(path.join(installPath, '.app.json')),
516
- pathExists(path.join(installPath, 'hooks')),
517
- pathExists(path.join(installPath, 'commands')),
518
- listCodexAgentSkills(installPath),
519
- listClaudeAgentFiles(installPath),
520
- ]);
521
- return { hasAppConnector, hasHooks, hasCommands, codexAgentSkills, claudeAgents };
522
- }
523
- async function pathExists(target) {
524
- try {
525
- await stat(target);
526
- return true;
527
- }
528
- catch {
529
- return false;
530
- }
531
- }
532
- async function listCodexAgentSkills(installPath) {
533
- const skillsDir = path.join(installPath, 'skills');
534
- try {
535
- const entries = await readdir(skillsDir, { withFileTypes: true });
536
- const matches = [];
537
- for (const entry of entries) {
538
- if (!entry.isDirectory() || entry.name.startsWith('.'))
539
- continue;
540
- if (await pathExists(path.join(skillsDir, entry.name, 'agents'))) {
541
- matches.push(entry.name);
542
- }
543
- }
544
- return matches.sort((left, right) => left.localeCompare(right));
545
- }
546
- catch {
547
- return [];
548
- }
549
- }
550
- async function listClaudeAgentFiles(installPath) {
551
- const agentsDir = path.join(installPath, 'agents');
552
- try {
553
- const entries = await readdir(agentsDir, { withFileTypes: true });
554
- return entries
555
- .filter((entry) => entry.isFile() && entry.name.endsWith('.md'))
556
- .map((entry) => entry.name.replace(/\.md$/, ''))
557
- .sort((left, right) => left.localeCompare(right));
558
- }
559
- catch {
560
- return [];
561
- }
562
- }
563
- function formatCompatibilityWarnings(artifacts, context) {
564
- const warnings = [];
565
- if (artifacts.hasAppConnector && context.targetAgent !== 'codex') {
566
- warnings.push({
567
- label: 'App connector',
568
- status: 'warn',
569
- message: `Plugin ships a Codex app connector (.app.json) that will NOT transfer. Skill instructions will copy over but the backing integration will not work on ${context.targetAgent}.`,
570
- });
571
- }
572
- if (artifacts.codexAgentSkills.length > 0 && context.targetAgent !== 'codex') {
573
- warnings.push({
574
- label: 'Codex agent YAML',
575
- status: 'warn',
576
- message: `Skills ${artifacts.codexAgentSkills.join(', ')} include Codex-specific agent YAML that will not transfer to ${context.targetAgent}.`,
577
- });
578
- }
579
- if (artifacts.hasHooks && context.targetAgent !== 'claude') {
580
- warnings.push({
581
- label: 'Claude hooks',
582
- status: 'warn',
583
- message: `Plugin ships session hooks that only work on Claude and will NOT transfer to ${context.targetAgent}.`,
584
- });
585
- }
586
- if (context.targetAgent === 'gemini' && artifacts.claudeAgents.length > 0) {
587
- warnings.push({
588
- label: 'Subagents',
589
- status: 'warn',
590
- message: `Plugin ships subagent definitions (${artifacts.claudeAgents.join(', ')}) that cannot be converted to ${context.targetAgent}.`,
591
- });
592
- }
593
- if (context.targetAgent === 'gemini' && artifacts.hasCommands) {
594
- warnings.push({
595
- label: 'Slash commands',
596
- status: 'warn',
597
- message: `Plugin ships slash commands that cannot be converted to ${context.targetAgent}.`,
598
- });
599
- }
600
- return warnings;
601
- }
@@ -0,0 +1,12 @@
1
+ import type { AgentName } from '../../types.js';
2
+ import type { AgentSkillEntry } from '../agent/agent-config-service.js';
3
+ export declare function isUnmanagedCodexPlugin(targetAgent: AgentName, plugin: AgentSkillEntry): boolean;
4
+ export declare function isUnmanagedClaudePlugin(targetAgent: AgentName, plugin: AgentSkillEntry): boolean;
5
+ export declare function defaultUninstallClaudePlugin(options: {
6
+ pluginKey: string;
7
+ installPath: string;
8
+ }): Promise<void>;
9
+ export declare function defaultUninstallCodexPlugin(options: {
10
+ pluginKey: string;
11
+ installPath: string;
12
+ }): Promise<void>;
@@ -0,0 +1,76 @@
1
+ import { spawn } from 'node:child_process';
2
+ import { copyFile, readFile, rename, rm, writeFile } from 'node:fs/promises';
3
+ import { homedir } from 'node:os';
4
+ import path from 'node:path';
5
+ import { ValidationError } from '../../errors.js';
6
+ import { formatTimestamp } from '../sync/agent-writer.js';
7
+ import { stripPluginSection } from '../sync/codex-writer.js';
8
+ export function isUnmanagedCodexPlugin(targetAgent, plugin) {
9
+ return (!plugin.managed &&
10
+ targetAgent === 'codex' &&
11
+ typeof plugin.installPath === 'string' &&
12
+ typeof plugin.source === 'string' &&
13
+ plugin.source.length > 0);
14
+ }
15
+ export function isUnmanagedClaudePlugin(targetAgent, plugin) {
16
+ return (!plugin.managed &&
17
+ targetAgent === 'claude' &&
18
+ typeof plugin.installPath === 'string' &&
19
+ typeof plugin.source === 'string' &&
20
+ plugin.source.length > 0);
21
+ }
22
+ export async function defaultUninstallClaudePlugin(options) {
23
+ // Delegate to `claude plugin uninstall` so a running Claude Code session
24
+ // drops the plugin from its in-memory state (direct fs mutation gets
25
+ // resurrected by live sessions that still have the plugin loaded).
26
+ await new Promise((resolve, reject) => {
27
+ const child = spawn('claude', ['plugin', 'uninstall', options.pluginKey, '--scope', 'user'], { stdio: ['ignore', 'pipe', 'pipe'] });
28
+ let stderr = '';
29
+ let stdout = '';
30
+ child.stdout?.on('data', (chunk) => {
31
+ stdout += chunk.toString();
32
+ });
33
+ child.stderr?.on('data', (chunk) => {
34
+ stderr += chunk.toString();
35
+ });
36
+ child.on('error', (error) => {
37
+ reject(new ValidationError(`Failed to invoke \`claude\` CLI: ${error.message}. Is Claude Code installed on PATH?`));
38
+ });
39
+ child.on('exit', (code) => {
40
+ if (code === 0) {
41
+ resolve();
42
+ return;
43
+ }
44
+ const detail = (stderr || stdout).trim();
45
+ reject(new ValidationError(`\`claude plugin uninstall ${options.pluginKey}\` exited ${code}${detail ? `: ${detail}` : ''}`));
46
+ });
47
+ });
48
+ }
49
+ export async function defaultUninstallCodexPlugin(options) {
50
+ const home = homedir();
51
+ const cacheRoot = path.join(home, '.codex', 'plugins', 'cache');
52
+ const resolvedInstall = path.resolve(options.installPath);
53
+ if (!resolvedInstall.startsWith(cacheRoot + path.sep)) {
54
+ throw new ValidationError(`Refusing to remove Codex plugin files outside ${cacheRoot}: ${resolvedInstall}`);
55
+ }
56
+ const pluginRoot = path.dirname(resolvedInstall);
57
+ const configPath = path.join(home, '.codex', 'config.toml');
58
+ let existing = '';
59
+ try {
60
+ existing = await readFile(configPath, 'utf8');
61
+ }
62
+ catch {
63
+ existing = '';
64
+ }
65
+ if (existing.length > 0) {
66
+ const next = stripPluginSection(existing, options.pluginKey);
67
+ if (next !== existing) {
68
+ const backupPath = `${configPath}.bak.${formatTimestamp()}`;
69
+ await copyFile(configPath, backupPath);
70
+ const tmpPath = `${configPath}.tmp.${Date.now()}`;
71
+ await writeFile(tmpPath, next, 'utf8');
72
+ await rename(tmpPath, configPath);
73
+ }
74
+ }
75
+ await rm(pluginRoot, { recursive: true, force: true });
76
+ }
@@ -1,4 +1,4 @@
1
- import type { AgentName } from '../types.js';
1
+ import type { AgentName } from '../../types.js';
2
2
  export declare function getSkillDir(agent: AgentName, skillName: string): string;
3
3
  export declare function getAgentFilePath(agent: AgentName, agentName: string): string;
4
4
  export declare function getCommandFilePath(agent: AgentName, commandName: string): string;
@@ -1,4 +1,4 @@
1
- import type { AgentName } from '../types.js';
1
+ import type { AgentName } from '../../types.js';
2
2
  export interface SkillPreflightCheck {
3
3
  label: string;
4
4
  status: 'ok' | 'warn' | 'error';
@@ -1,6 +1,6 @@
1
- import { ValidationError } from '../errors.js';
2
- import type { LocalBundledMcpServerConfig, LocalNpmMcpServerConfig, RemoteMcpServerConfig } from '../types.js';
3
- import type { AgentMcpEntry, PortableRemoteMcpMetadata } from './agent-config-service.js';
1
+ import { ValidationError } from '../../errors.js';
2
+ import type { LocalBundledMcpServerConfig, LocalNpmMcpServerConfig, RemoteMcpServerConfig } from '../../types.js';
3
+ import type { AgentMcpEntry, PortableRemoteMcpMetadata } from '../agent/agent-config-service.js';
4
4
  export type PortableMcpClassification = LocalNpmMcpServerConfig | LocalBundledMcpServerConfig | RemoteMcpServerConfig;
5
5
  export declare class PortableMcpClassificationError extends ValidationError {
6
6
  }
@@ -1,6 +1,6 @@
1
1
  import path from 'node:path';
2
- import { ValidationError } from '../errors.js';
3
- import { detectMcpRuntime, extractEntrypoint } from './runtime-detector.js';
2
+ import { ValidationError } from '../../errors.js';
3
+ import { detectMcpRuntime, extractEntrypoint } from '../platform/runtime-detector.js';
4
4
  const NPX_LIKE_COMMANDS = new Set(['npx', 'uvx']);
5
5
  export class PortableMcpClassificationError extends ValidationError {
6
6
  }
@@ -1,5 +1,5 @@
1
- import type { AgentName } from '../types.js';
2
- import { type AgentConfigService } from './agent-config-service.js';
1
+ import type { AgentName } from '../../types.js';
2
+ import { type AgentConfigService } from '../agent/agent-config-service.js';
3
3
  import { type ProfileService } from './profile-service.js';
4
4
  export type PortablePackSource = {
5
5
  source: 'profile';
@@ -9,13 +9,19 @@ export type PortablePackSource = {
9
9
  agent: AgentName;
10
10
  cwd: string;
11
11
  };
12
+ export type PortablePackFormat = 'tarball' | 'folder';
13
+ export type PortableCredentialsMode = 'redact' | 'keep';
12
14
  export interface PortableProfilePackService {
13
15
  execute(options: {
14
16
  cwd?: string;
15
17
  source: PortablePackSource;
16
18
  outputPath?: string;
19
+ format?: PortablePackFormat;
20
+ credentialsMode?: PortableCredentialsMode;
17
21
  }): Promise<{
18
22
  archivePath: string;
23
+ format: PortablePackFormat;
24
+ warnings: string[];
19
25
  }>;
20
26
  }
21
27
  interface PortableProfilePackServiceDependencies {