syntaur 0.1.0 → 0.1.2
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/README.md +116 -0
- package/dist/dashboard/server.js +74 -24
- package/dist/dashboard/server.js.map +1 -1
- package/dist/index.js +858 -160
- package/dist/index.js.map +1 -1
- package/package.json +18 -2
- package/plugin/.claude-plugin/plugin.json +1 -1
- package/plugin/agents/syntaur-expert.md +2 -2
- package/plugins/syntaur/.codex-plugin/plugin.json +1 -1
- package/plugins/syntaur/agents/syntaur-operator.md +1 -1
package/dist/index.js
CHANGED
|
@@ -83,6 +83,31 @@ var init_fs = __esm({
|
|
|
83
83
|
}
|
|
84
84
|
});
|
|
85
85
|
|
|
86
|
+
// src/templates/config.ts
|
|
87
|
+
function renderConfig(params) {
|
|
88
|
+
return `---
|
|
89
|
+
version: "1.0"
|
|
90
|
+
defaultMissionDir: ${params.defaultMissionDir}
|
|
91
|
+
agentDefaults:
|
|
92
|
+
trustLevel: medium
|
|
93
|
+
autoApprove: false
|
|
94
|
+
sync:
|
|
95
|
+
enabled: false
|
|
96
|
+
endpoint: null
|
|
97
|
+
interval: 300
|
|
98
|
+
---
|
|
99
|
+
|
|
100
|
+
# Syntaur Configuration
|
|
101
|
+
|
|
102
|
+
Global configuration for the Syntaur CLI.
|
|
103
|
+
`;
|
|
104
|
+
}
|
|
105
|
+
var init_config = __esm({
|
|
106
|
+
"src/templates/config.ts"() {
|
|
107
|
+
"use strict";
|
|
108
|
+
}
|
|
109
|
+
});
|
|
110
|
+
|
|
86
111
|
// src/dashboard/parser.ts
|
|
87
112
|
function extractFrontmatter(fileContent) {
|
|
88
113
|
const match = fileContent.match(/^---\n([\s\S]*?)\n---/);
|
|
@@ -469,6 +494,58 @@ function serializeStatusConfig(statuses) {
|
|
|
469
494
|
}
|
|
470
495
|
return lines.join("\n");
|
|
471
496
|
}
|
|
497
|
+
function serializeIntegrationConfig(integrations) {
|
|
498
|
+
const lines = [];
|
|
499
|
+
if (integrations.claudePluginDir) {
|
|
500
|
+
lines.push(` claudePluginDir: ${integrations.claudePluginDir}`);
|
|
501
|
+
}
|
|
502
|
+
if (integrations.codexPluginDir) {
|
|
503
|
+
lines.push(` codexPluginDir: ${integrations.codexPluginDir}`);
|
|
504
|
+
}
|
|
505
|
+
if (integrations.codexMarketplacePath) {
|
|
506
|
+
lines.push(` codexMarketplacePath: ${integrations.codexMarketplacePath}`);
|
|
507
|
+
}
|
|
508
|
+
if (lines.length === 0) {
|
|
509
|
+
return null;
|
|
510
|
+
}
|
|
511
|
+
return ["integrations:", ...lines].join("\n");
|
|
512
|
+
}
|
|
513
|
+
function stripTopLevelBlock(fmBlock, key) {
|
|
514
|
+
const blockStart = fmBlock.match(new RegExp(`^${key}:\\s*$`, "m"));
|
|
515
|
+
if (!blockStart) {
|
|
516
|
+
return fmBlock.replace(/\n+$/, "");
|
|
517
|
+
}
|
|
518
|
+
const startIdx = fmBlock.indexOf(blockStart[0]);
|
|
519
|
+
const before = fmBlock.slice(0, startIdx);
|
|
520
|
+
const after = fmBlock.slice(startIdx + blockStart[0].length);
|
|
521
|
+
const remaining = after.split("\n");
|
|
522
|
+
let endIdx = 0;
|
|
523
|
+
for (let i = 0; i < remaining.length; i++) {
|
|
524
|
+
const line = remaining[i];
|
|
525
|
+
if (line.trim() === "") {
|
|
526
|
+
endIdx = i + 1;
|
|
527
|
+
continue;
|
|
528
|
+
}
|
|
529
|
+
if (line.length > 0 && line[0] !== " ") {
|
|
530
|
+
break;
|
|
531
|
+
}
|
|
532
|
+
endIdx = i + 1;
|
|
533
|
+
}
|
|
534
|
+
return (before + remaining.slice(endIdx).join("\n")).replace(/\n+$/, "");
|
|
535
|
+
}
|
|
536
|
+
function parseOptionalAbsolutePath(value, fieldName) {
|
|
537
|
+
if (!value) {
|
|
538
|
+
return null;
|
|
539
|
+
}
|
|
540
|
+
const expanded = expandHome(String(value));
|
|
541
|
+
if (!isAbsolute(expanded)) {
|
|
542
|
+
console.warn(
|
|
543
|
+
`Warning: config.md ${fieldName} is not an absolute path ("${value}"), ignoring it`
|
|
544
|
+
);
|
|
545
|
+
return null;
|
|
546
|
+
}
|
|
547
|
+
return resolve4(expanded);
|
|
548
|
+
}
|
|
472
549
|
async function writeStatusConfig(statuses) {
|
|
473
550
|
const configPath = resolve4(syntaurRoot(), "config.md");
|
|
474
551
|
const statusBlock = serializeStatusConfig(statuses);
|
|
@@ -531,28 +608,42 @@ async function deleteStatusConfig() {
|
|
|
531
608
|
if (!fmMatch) return;
|
|
532
609
|
const fmBlock = fmMatch[2];
|
|
533
610
|
const afterFrontmatter = existing.slice(fmMatch[0].length);
|
|
534
|
-
const
|
|
535
|
-
if (!statusesStart) return;
|
|
536
|
-
const startIdx = fmBlock.indexOf(statusesStart[0]);
|
|
537
|
-
const before = fmBlock.slice(0, startIdx);
|
|
538
|
-
const after = fmBlock.slice(startIdx + statusesStart[0].length);
|
|
539
|
-
const remaining = after.split("\n");
|
|
540
|
-
let endIdx = 0;
|
|
541
|
-
for (let i = 0; i < remaining.length; i++) {
|
|
542
|
-
const line = remaining[i];
|
|
543
|
-
if (line.trim() === "") {
|
|
544
|
-
endIdx = i + 1;
|
|
545
|
-
continue;
|
|
546
|
-
}
|
|
547
|
-
if (line.length > 0 && line[0] !== " ") break;
|
|
548
|
-
endIdx = i + 1;
|
|
549
|
-
}
|
|
550
|
-
const cleanedFm = (before + remaining.slice(endIdx).join("\n")).replace(/\n+$/, "");
|
|
611
|
+
const cleanedFm = stripTopLevelBlock(fmBlock, "statuses");
|
|
551
612
|
const newContent = `---
|
|
552
613
|
${cleanedFm}
|
|
553
614
|
---${afterFrontmatter}`;
|
|
554
615
|
await writeFileForce(configPath, newContent);
|
|
555
616
|
}
|
|
617
|
+
async function updateIntegrationConfig(integrations) {
|
|
618
|
+
const configPath = resolve4(syntaurRoot(), "config.md");
|
|
619
|
+
const nextIntegrations = {
|
|
620
|
+
...(await readConfig()).integrations,
|
|
621
|
+
...integrations
|
|
622
|
+
};
|
|
623
|
+
const integrationBlock = serializeIntegrationConfig(nextIntegrations);
|
|
624
|
+
const existing = await fileExists(configPath) ? await readFile3(configPath, "utf-8") : renderConfig({ defaultMissionDir: defaultMissionDir() });
|
|
625
|
+
const fmMatch = existing.match(/^(---\n)([\s\S]*?)\n(---)/);
|
|
626
|
+
if (!fmMatch) {
|
|
627
|
+
const content = `---
|
|
628
|
+
version: "1.0"
|
|
629
|
+
defaultMissionDir: ${defaultMissionDir()}
|
|
630
|
+
${integrationBlock ?? ""}
|
|
631
|
+
---
|
|
632
|
+
${existing}`;
|
|
633
|
+
await writeFileForce(configPath, content.replace(/\n\n---/, "\n---"));
|
|
634
|
+
return;
|
|
635
|
+
}
|
|
636
|
+
const fmBlock = fmMatch[2];
|
|
637
|
+
const afterFrontmatter = existing.slice(fmMatch[0].length);
|
|
638
|
+
const cleanedFm = stripTopLevelBlock(fmBlock, "integrations");
|
|
639
|
+
const newFm = integrationBlock ? `${cleanedFm}
|
|
640
|
+
${integrationBlock}`.replace(/^\n+/, "") : cleanedFm;
|
|
641
|
+
const normalizedFm = newFm.replace(/\n+$/, "");
|
|
642
|
+
const newContent = `---
|
|
643
|
+
${normalizedFm}
|
|
644
|
+
---${afterFrontmatter}`;
|
|
645
|
+
await writeFileForce(configPath, newContent);
|
|
646
|
+
}
|
|
556
647
|
async function readConfig() {
|
|
557
648
|
const configPath = resolve4(syntaurRoot(), "config.md");
|
|
558
649
|
if (!await fileExists(configPath)) {
|
|
@@ -578,15 +669,30 @@ async function readConfig() {
|
|
|
578
669
|
trustLevel: fm["agentDefaults.trustLevel"] || DEFAULT_CONFIG.agentDefaults.trustLevel,
|
|
579
670
|
autoApprove: fm["agentDefaults.autoApprove"] === "true" || DEFAULT_CONFIG.agentDefaults.autoApprove
|
|
580
671
|
},
|
|
672
|
+
integrations: {
|
|
673
|
+
claudePluginDir: parseOptionalAbsolutePath(
|
|
674
|
+
fm["integrations.claudePluginDir"],
|
|
675
|
+
"integrations.claudePluginDir"
|
|
676
|
+
),
|
|
677
|
+
codexPluginDir: parseOptionalAbsolutePath(
|
|
678
|
+
fm["integrations.codexPluginDir"],
|
|
679
|
+
"integrations.codexPluginDir"
|
|
680
|
+
),
|
|
681
|
+
codexMarketplacePath: parseOptionalAbsolutePath(
|
|
682
|
+
fm["integrations.codexMarketplacePath"],
|
|
683
|
+
"integrations.codexMarketplacePath"
|
|
684
|
+
)
|
|
685
|
+
},
|
|
581
686
|
statuses: parseStatusConfig(content)
|
|
582
687
|
};
|
|
583
688
|
}
|
|
584
689
|
var DEFAULT_CONFIG;
|
|
585
|
-
var
|
|
690
|
+
var init_config2 = __esm({
|
|
586
691
|
"src/utils/config.ts"() {
|
|
587
692
|
"use strict";
|
|
588
693
|
init_paths();
|
|
589
694
|
init_fs();
|
|
695
|
+
init_config();
|
|
590
696
|
DEFAULT_CONFIG = {
|
|
591
697
|
version: "1.0",
|
|
592
698
|
defaultMissionDir: defaultMissionDir(),
|
|
@@ -594,6 +700,11 @@ var init_config = __esm({
|
|
|
594
700
|
trustLevel: "medium",
|
|
595
701
|
autoApprove: false
|
|
596
702
|
},
|
|
703
|
+
integrations: {
|
|
704
|
+
claudePluginDir: null,
|
|
705
|
+
codexPluginDir: null,
|
|
706
|
+
codexMarketplacePath: null
|
|
707
|
+
},
|
|
597
708
|
statuses: null
|
|
598
709
|
};
|
|
599
710
|
}
|
|
@@ -1222,13 +1333,13 @@ var init_help = __esm({
|
|
|
1222
1333
|
// --- Plugin & adapter setup (indices 13-16) ---
|
|
1223
1334
|
{
|
|
1224
1335
|
command: "syntaur install-plugin",
|
|
1225
|
-
description: "Install the Syntaur Claude Code plugin
|
|
1226
|
-
example: "syntaur install-plugin"
|
|
1336
|
+
description: "Install the Syntaur Claude Code plugin, detecting the local Claude marketplace when available and prompting for the target directory when interactive.",
|
|
1337
|
+
example: "syntaur install-plugin --target-dir ~/.claude/plugins/marketplaces/user-plugins/plugins/syntaur"
|
|
1227
1338
|
},
|
|
1228
1339
|
{
|
|
1229
1340
|
command: "syntaur install-codex-plugin",
|
|
1230
|
-
description: "Install the Syntaur Codex plugin and register its
|
|
1231
|
-
example: "syntaur install-codex-plugin"
|
|
1341
|
+
description: "Install the Syntaur Codex plugin and register its marketplace entry, prompting for both paths when interactive.",
|
|
1342
|
+
example: "syntaur install-codex-plugin --target-dir ~/plugins/syntaur --marketplace-path ~/.agents/plugins/marketplace.json"
|
|
1232
1343
|
},
|
|
1233
1344
|
{
|
|
1234
1345
|
command: "syntaur uninstall",
|
|
@@ -2684,7 +2795,7 @@ var init_api = __esm({
|
|
|
2684
2795
|
"use strict";
|
|
2685
2796
|
init_lifecycle();
|
|
2686
2797
|
init_fs();
|
|
2687
|
-
|
|
2798
|
+
init_config2();
|
|
2688
2799
|
init_parser();
|
|
2689
2800
|
init_help();
|
|
2690
2801
|
STALE_ASSIGNMENT_MS = 7 * 24 * 60 * 60 * 1e3;
|
|
@@ -3606,30 +3717,11 @@ import { Command as Command2 } from "commander";
|
|
|
3606
3717
|
// src/commands/init.ts
|
|
3607
3718
|
init_paths();
|
|
3608
3719
|
init_fs();
|
|
3720
|
+
init_config();
|
|
3609
3721
|
import { resolve as resolve3, dirname as dirname2 } from "path";
|
|
3610
3722
|
import { readdir as readdir2, readFile as readFile2 } from "fs/promises";
|
|
3611
3723
|
import { fileURLToPath } from "url";
|
|
3612
3724
|
|
|
3613
|
-
// src/templates/config.ts
|
|
3614
|
-
function renderConfig(params) {
|
|
3615
|
-
return `---
|
|
3616
|
-
version: "1.0"
|
|
3617
|
-
defaultMissionDir: ${params.defaultMissionDir}
|
|
3618
|
-
agentDefaults:
|
|
3619
|
-
trustLevel: medium
|
|
3620
|
-
autoApprove: false
|
|
3621
|
-
sync:
|
|
3622
|
-
enabled: false
|
|
3623
|
-
endpoint: null
|
|
3624
|
-
interval: 300
|
|
3625
|
-
---
|
|
3626
|
-
|
|
3627
|
-
# Syntaur Configuration
|
|
3628
|
-
|
|
3629
|
-
Global configuration for the Syntaur CLI.
|
|
3630
|
-
`;
|
|
3631
|
-
}
|
|
3632
|
-
|
|
3633
3725
|
// src/utils/playbooks.ts
|
|
3634
3726
|
init_fs();
|
|
3635
3727
|
init_parser();
|
|
@@ -3752,6 +3844,9 @@ function generateId() {
|
|
|
3752
3844
|
// src/commands/create-mission.ts
|
|
3753
3845
|
init_paths();
|
|
3754
3846
|
init_fs();
|
|
3847
|
+
init_config2();
|
|
3848
|
+
|
|
3849
|
+
// src/templates/index.ts
|
|
3755
3850
|
init_config();
|
|
3756
3851
|
|
|
3757
3852
|
// src/templates/manifest.ts
|
|
@@ -4527,7 +4622,7 @@ import { resolve as resolve6 } from "path";
|
|
|
4527
4622
|
init_timestamp();
|
|
4528
4623
|
init_paths();
|
|
4529
4624
|
init_fs();
|
|
4530
|
-
|
|
4625
|
+
init_config2();
|
|
4531
4626
|
async function createAssignmentCommand(title, options) {
|
|
4532
4627
|
if (!title.trim()) {
|
|
4533
4628
|
throw new Error("Assignment title cannot be empty.");
|
|
@@ -4676,7 +4771,7 @@ Use --slug to specify a different slug.`
|
|
|
4676
4771
|
}
|
|
4677
4772
|
|
|
4678
4773
|
// src/commands/dashboard.ts
|
|
4679
|
-
|
|
4774
|
+
init_config2();
|
|
4680
4775
|
import { spawn } from "child_process";
|
|
4681
4776
|
import { resolve as resolve18, dirname as dirname5 } from "path";
|
|
4682
4777
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
@@ -4836,7 +4931,7 @@ function createWatcher(options) {
|
|
|
4836
4931
|
|
|
4837
4932
|
// src/dashboard/server.ts
|
|
4838
4933
|
init_fs();
|
|
4839
|
-
|
|
4934
|
+
init_config2();
|
|
4840
4935
|
|
|
4841
4936
|
// src/dashboard/api-write.ts
|
|
4842
4937
|
init_lifecycle();
|
|
@@ -7212,7 +7307,7 @@ async function dashboardCommand(options) {
|
|
|
7212
7307
|
// src/commands/assign.ts
|
|
7213
7308
|
init_paths();
|
|
7214
7309
|
init_fs();
|
|
7215
|
-
|
|
7310
|
+
init_config2();
|
|
7216
7311
|
import { resolve as resolve19 } from "path";
|
|
7217
7312
|
init_lifecycle();
|
|
7218
7313
|
async function assignCommand(assignment, options) {
|
|
@@ -7251,7 +7346,7 @@ async function assignCommand(assignment, options) {
|
|
|
7251
7346
|
// src/commands/start.ts
|
|
7252
7347
|
init_paths();
|
|
7253
7348
|
init_fs();
|
|
7254
|
-
|
|
7349
|
+
init_config2();
|
|
7255
7350
|
import { resolve as resolve20 } from "path";
|
|
7256
7351
|
init_lifecycle();
|
|
7257
7352
|
async function startCommand(assignment, options) {
|
|
@@ -7294,7 +7389,7 @@ async function startCommand(assignment, options) {
|
|
|
7294
7389
|
// src/commands/complete.ts
|
|
7295
7390
|
init_paths();
|
|
7296
7391
|
init_fs();
|
|
7297
|
-
|
|
7392
|
+
init_config2();
|
|
7298
7393
|
import { resolve as resolve21 } from "path";
|
|
7299
7394
|
init_lifecycle();
|
|
7300
7395
|
async function completeCommand(assignment, options) {
|
|
@@ -7330,7 +7425,7 @@ async function completeCommand(assignment, options) {
|
|
|
7330
7425
|
// src/commands/block.ts
|
|
7331
7426
|
init_paths();
|
|
7332
7427
|
init_fs();
|
|
7333
|
-
|
|
7428
|
+
init_config2();
|
|
7334
7429
|
import { resolve as resolve22 } from "path";
|
|
7335
7430
|
init_lifecycle();
|
|
7336
7431
|
async function blockCommand(assignment, options) {
|
|
@@ -7368,7 +7463,7 @@ async function blockCommand(assignment, options) {
|
|
|
7368
7463
|
// src/commands/unblock.ts
|
|
7369
7464
|
init_paths();
|
|
7370
7465
|
init_fs();
|
|
7371
|
-
|
|
7466
|
+
init_config2();
|
|
7372
7467
|
import { resolve as resolve23 } from "path";
|
|
7373
7468
|
init_lifecycle();
|
|
7374
7469
|
async function unblockCommand(assignment, options) {
|
|
@@ -7404,7 +7499,7 @@ async function unblockCommand(assignment, options) {
|
|
|
7404
7499
|
// src/commands/review.ts
|
|
7405
7500
|
init_paths();
|
|
7406
7501
|
init_fs();
|
|
7407
|
-
|
|
7502
|
+
init_config2();
|
|
7408
7503
|
import { resolve as resolve24 } from "path";
|
|
7409
7504
|
init_lifecycle();
|
|
7410
7505
|
async function reviewCommand(assignment, options) {
|
|
@@ -7440,7 +7535,7 @@ async function reviewCommand(assignment, options) {
|
|
|
7440
7535
|
// src/commands/fail.ts
|
|
7441
7536
|
init_paths();
|
|
7442
7537
|
init_fs();
|
|
7443
|
-
|
|
7538
|
+
init_config2();
|
|
7444
7539
|
import { resolve as resolve25 } from "path";
|
|
7445
7540
|
init_lifecycle();
|
|
7446
7541
|
async function failCommand(assignment, options) {
|
|
@@ -7476,7 +7571,7 @@ async function failCommand(assignment, options) {
|
|
|
7476
7571
|
// src/commands/reopen.ts
|
|
7477
7572
|
init_paths();
|
|
7478
7573
|
init_fs();
|
|
7479
|
-
|
|
7574
|
+
init_config2();
|
|
7480
7575
|
import { resolve as resolve26 } from "path";
|
|
7481
7576
|
init_lifecycle();
|
|
7482
7577
|
async function reopenCommand(assignment, options) {
|
|
@@ -7509,10 +7604,15 @@ async function reopenCommand(assignment, options) {
|
|
|
7509
7604
|
console.log(result.message);
|
|
7510
7605
|
}
|
|
7511
7606
|
|
|
7607
|
+
// src/commands/install-plugin.ts
|
|
7608
|
+
init_config2();
|
|
7609
|
+
|
|
7512
7610
|
// src/utils/install.ts
|
|
7611
|
+
init_config2();
|
|
7513
7612
|
init_fs();
|
|
7514
7613
|
import {
|
|
7515
7614
|
cp,
|
|
7615
|
+
readdir as readdir8,
|
|
7516
7616
|
symlink,
|
|
7517
7617
|
lstat,
|
|
7518
7618
|
readFile as readFile12,
|
|
@@ -7523,7 +7623,7 @@ import {
|
|
|
7523
7623
|
} from "fs/promises";
|
|
7524
7624
|
import { existsSync } from "fs";
|
|
7525
7625
|
import { homedir as homedir3 } from "os";
|
|
7526
|
-
import { basename, dirname as dirname7, resolve as resolve28 } from "path";
|
|
7626
|
+
import { basename, dirname as dirname7, isAbsolute as isAbsolute2, relative as relative2, resolve as resolve28 } from "path";
|
|
7527
7627
|
|
|
7528
7628
|
// src/utils/package-root.ts
|
|
7529
7629
|
init_fs();
|
|
@@ -7555,10 +7655,22 @@ function getPluginRelativePath(pluginKind) {
|
|
|
7555
7655
|
function getPluginManifestRelativePath(pluginKind) {
|
|
7556
7656
|
return pluginKind === "claude" ? ".claude-plugin/plugin.json" : ".codex-plugin/plugin.json";
|
|
7557
7657
|
}
|
|
7558
|
-
function
|
|
7658
|
+
function getDefaultPluginTargetDir(pluginKind) {
|
|
7559
7659
|
const home = homedir3();
|
|
7560
7660
|
return pluginKind === "claude" ? resolve28(home, ".claude", "plugins", "syntaur") : resolve28(home, "plugins", "syntaur");
|
|
7561
7661
|
}
|
|
7662
|
+
function getDefaultMarketplacePath() {
|
|
7663
|
+
return resolve28(homedir3(), ".agents", "plugins", "marketplace.json");
|
|
7664
|
+
}
|
|
7665
|
+
function getClaudeMarketplacesRoot() {
|
|
7666
|
+
return resolve28(homedir3(), ".claude", "plugins", "marketplaces");
|
|
7667
|
+
}
|
|
7668
|
+
function getClaudeKnownMarketplacesPath() {
|
|
7669
|
+
return resolve28(homedir3(), ".claude", "plugins", "known_marketplaces.json");
|
|
7670
|
+
}
|
|
7671
|
+
function getClaudeInstalledPluginsPath() {
|
|
7672
|
+
return resolve28(homedir3(), ".claude", "plugins", "installed_plugins.json");
|
|
7673
|
+
}
|
|
7562
7674
|
function getInstallMarkerPath(targetDir) {
|
|
7563
7675
|
return resolve28(targetDir, INSTALL_MARKER_FILENAME);
|
|
7564
7676
|
}
|
|
@@ -7566,6 +7678,22 @@ async function readPackageManifest(packageRoot2) {
|
|
|
7566
7678
|
const raw = await readFile12(resolve28(packageRoot2, "package.json"), "utf-8");
|
|
7567
7679
|
return JSON.parse(raw);
|
|
7568
7680
|
}
|
|
7681
|
+
async function readJsonFileIfExists(pathValue) {
|
|
7682
|
+
if (!await fileExists(pathValue)) {
|
|
7683
|
+
return null;
|
|
7684
|
+
}
|
|
7685
|
+
try {
|
|
7686
|
+
const raw = await readFile12(pathValue, "utf-8");
|
|
7687
|
+
return JSON.parse(raw);
|
|
7688
|
+
} catch {
|
|
7689
|
+
return null;
|
|
7690
|
+
}
|
|
7691
|
+
}
|
|
7692
|
+
async function readClaudePluginManifest(pluginDir) {
|
|
7693
|
+
return await readJsonFileIfExists(
|
|
7694
|
+
resolve28(pluginDir, ".claude-plugin", "plugin.json")
|
|
7695
|
+
) ?? {};
|
|
7696
|
+
}
|
|
7569
7697
|
async function readPluginManifestName(targetDir, pluginKind) {
|
|
7570
7698
|
const manifestPath = resolve28(targetDir, getPluginManifestRelativePath(pluginKind));
|
|
7571
7699
|
if (!await fileExists(manifestPath)) {
|
|
@@ -7649,22 +7777,325 @@ async function removeInstallMarker(targetDir) {
|
|
|
7649
7777
|
});
|
|
7650
7778
|
}
|
|
7651
7779
|
}
|
|
7652
|
-
|
|
7780
|
+
function normalizeAbsoluteInstallPath(pathValue, label) {
|
|
7781
|
+
const expanded = expandHome(pathValue.trim());
|
|
7782
|
+
if (!isAbsolute2(expanded)) {
|
|
7783
|
+
throw new Error(`${label} must be an absolute path.`);
|
|
7784
|
+
}
|
|
7785
|
+
return resolve28(expanded);
|
|
7786
|
+
}
|
|
7787
|
+
async function resolvePluginPaths(pluginKind, targetDir) {
|
|
7653
7788
|
const packageRoot2 = await findPackageRoot(getPluginRelativePath(pluginKind));
|
|
7654
7789
|
return {
|
|
7655
7790
|
packageRoot: packageRoot2,
|
|
7656
7791
|
sourceDir: resolve28(packageRoot2, getPluginRelativePath(pluginKind)),
|
|
7657
|
-
targetDir:
|
|
7792
|
+
targetDir: targetDir ?? getDefaultPluginTargetDir(pluginKind)
|
|
7658
7793
|
};
|
|
7659
7794
|
}
|
|
7660
|
-
async function
|
|
7661
|
-
const
|
|
7662
|
-
const
|
|
7663
|
-
|
|
7795
|
+
async function readInstalledClaudeMarketplaceNames() {
|
|
7796
|
+
const parsed = await readJsonFileIfExists(getClaudeInstalledPluginsPath());
|
|
7797
|
+
const names = /* @__PURE__ */ new Set();
|
|
7798
|
+
for (const key of Object.keys(parsed?.plugins ?? {})) {
|
|
7799
|
+
const atIndex = key.lastIndexOf("@");
|
|
7800
|
+
if (atIndex > 0 && atIndex < key.length - 1) {
|
|
7801
|
+
names.add(key.slice(atIndex + 1));
|
|
7802
|
+
}
|
|
7803
|
+
}
|
|
7804
|
+
return names;
|
|
7805
|
+
}
|
|
7806
|
+
async function readKnownClaudeMarketplaceRecords() {
|
|
7807
|
+
const parsed = await readJsonFileIfExists(
|
|
7808
|
+
getClaudeKnownMarketplacesPath()
|
|
7809
|
+
);
|
|
7810
|
+
return new Map(Object.entries(parsed ?? {}));
|
|
7811
|
+
}
|
|
7812
|
+
async function readClaudeMarketplaceFile(manifestPath) {
|
|
7813
|
+
const parsed = await readJsonFileIfExists(manifestPath);
|
|
7814
|
+
if (!parsed || typeof parsed !== "object") {
|
|
7815
|
+
return { plugins: [] };
|
|
7816
|
+
}
|
|
7817
|
+
return {
|
|
7818
|
+
...parsed,
|
|
7819
|
+
plugins: Array.isArray(parsed.plugins) ? parsed.plugins.filter(
|
|
7820
|
+
(plugin) => typeof plugin === "object" && plugin !== null
|
|
7821
|
+
) : []
|
|
7822
|
+
};
|
|
7823
|
+
}
|
|
7824
|
+
async function writeClaudeMarketplaceFile(manifestPath, marketplace) {
|
|
7825
|
+
await ensureDir(dirname7(manifestPath));
|
|
7826
|
+
await writeFile4(manifestPath, `${JSON.stringify(marketplace, null, 2)}
|
|
7827
|
+
`, "utf-8");
|
|
7828
|
+
}
|
|
7829
|
+
function buildClaudeMarketplaceSourcePath(pluginTargetDir, marketplaceRootDir) {
|
|
7830
|
+
const relPath = relative2(marketplaceRootDir, pluginTargetDir).replaceAll("\\", "/");
|
|
7831
|
+
if (relPath === "") {
|
|
7832
|
+
return ".";
|
|
7833
|
+
}
|
|
7834
|
+
return relPath.startsWith(".") ? relPath : `./${relPath}`;
|
|
7835
|
+
}
|
|
7836
|
+
function normalizeClaudeAuthor(author) {
|
|
7837
|
+
if (!author) {
|
|
7838
|
+
return void 0;
|
|
7839
|
+
}
|
|
7840
|
+
if (typeof author === "string") {
|
|
7841
|
+
return { name: author };
|
|
7842
|
+
}
|
|
7843
|
+
return {
|
|
7844
|
+
name: author.name,
|
|
7845
|
+
email: author.email
|
|
7846
|
+
};
|
|
7847
|
+
}
|
|
7848
|
+
function buildClaudeMarketplaceEntry(pluginTargetDir, marketplaceRootDir, manifest) {
|
|
7849
|
+
return {
|
|
7850
|
+
name: manifest.name ?? "syntaur",
|
|
7851
|
+
description: manifest.description,
|
|
7852
|
+
version: manifest.version,
|
|
7853
|
+
author: normalizeClaudeAuthor(manifest.author),
|
|
7854
|
+
source: buildClaudeMarketplaceSourcePath(pluginTargetDir, marketplaceRootDir),
|
|
7855
|
+
category: "development"
|
|
7856
|
+
};
|
|
7857
|
+
}
|
|
7858
|
+
function scoreClaudeMarketplaceCandidate(candidate) {
|
|
7859
|
+
if (candidate.hasSyntaur) {
|
|
7860
|
+
return 100;
|
|
7861
|
+
}
|
|
7862
|
+
if (candidate.active && candidate.isUserPlugins) {
|
|
7863
|
+
return 90;
|
|
7864
|
+
}
|
|
7865
|
+
if (candidate.isUserPlugins) {
|
|
7866
|
+
return 80;
|
|
7867
|
+
}
|
|
7868
|
+
if (candidate.active && candidate.isDirectorySource) {
|
|
7869
|
+
return 70;
|
|
7870
|
+
}
|
|
7871
|
+
if (candidate.isDirectorySource) {
|
|
7872
|
+
return 60;
|
|
7873
|
+
}
|
|
7874
|
+
if (candidate.active) {
|
|
7875
|
+
return 50;
|
|
7876
|
+
}
|
|
7877
|
+
return 10;
|
|
7878
|
+
}
|
|
7879
|
+
async function listClaudeMarketplaceCandidates() {
|
|
7880
|
+
const rootDir = getClaudeMarketplacesRoot();
|
|
7881
|
+
if (!await fileExists(rootDir)) {
|
|
7882
|
+
return [];
|
|
7883
|
+
}
|
|
7884
|
+
const [knownMarketplaces, activeMarketplaceNames, entries] = await Promise.all([
|
|
7885
|
+
readKnownClaudeMarketplaceRecords(),
|
|
7886
|
+
readInstalledClaudeMarketplaceNames(),
|
|
7887
|
+
readdir8(rootDir, { withFileTypes: true })
|
|
7888
|
+
]);
|
|
7889
|
+
const candidates = [];
|
|
7890
|
+
const seen = /* @__PURE__ */ new Set();
|
|
7891
|
+
for (const entry of entries) {
|
|
7892
|
+
if (!entry.isDirectory()) {
|
|
7893
|
+
continue;
|
|
7894
|
+
}
|
|
7895
|
+
const candidateRoot = resolve28(rootDir, entry.name);
|
|
7896
|
+
const manifestPath = resolve28(candidateRoot, ".claude-plugin", "marketplace.json");
|
|
7897
|
+
if (!await fileExists(manifestPath)) {
|
|
7898
|
+
continue;
|
|
7899
|
+
}
|
|
7900
|
+
const parsed = await readClaudeMarketplaceFile(manifestPath);
|
|
7901
|
+
const candidateName = typeof parsed.name === "string" && parsed.name.trim() !== "" ? parsed.name : entry.name;
|
|
7902
|
+
const known = knownMarketplaces.get(candidateName);
|
|
7903
|
+
const hasSyntaur = parsed.plugins.some(
|
|
7904
|
+
(plugin) => plugin.name === "syntaur" && typeof plugin.source === "string"
|
|
7905
|
+
);
|
|
7906
|
+
const isUserPlugins = candidateName === "user-plugins" || entry.name === "user-plugins";
|
|
7907
|
+
candidates.push({
|
|
7908
|
+
name: candidateName,
|
|
7909
|
+
rootDir: candidateRoot,
|
|
7910
|
+
manifestPath,
|
|
7911
|
+
active: activeMarketplaceNames.has(candidateName),
|
|
7912
|
+
hasSyntaur,
|
|
7913
|
+
isUserPlugins,
|
|
7914
|
+
isDirectorySource: known?.source?.source === "directory" || isUserPlugins
|
|
7915
|
+
});
|
|
7916
|
+
seen.add(candidateRoot);
|
|
7917
|
+
}
|
|
7918
|
+
for (const [candidateName, known] of knownMarketplaces.entries()) {
|
|
7919
|
+
const installLocation = known.installLocation;
|
|
7920
|
+
if (!installLocation) {
|
|
7921
|
+
continue;
|
|
7922
|
+
}
|
|
7923
|
+
const candidateRoot = resolve28(expandHome(installLocation));
|
|
7924
|
+
if (seen.has(candidateRoot)) {
|
|
7925
|
+
continue;
|
|
7926
|
+
}
|
|
7927
|
+
const manifestPath = resolve28(candidateRoot, ".claude-plugin", "marketplace.json");
|
|
7928
|
+
if (!await fileExists(manifestPath)) {
|
|
7929
|
+
continue;
|
|
7930
|
+
}
|
|
7931
|
+
const parsed = await readClaudeMarketplaceFile(manifestPath);
|
|
7932
|
+
candidates.push({
|
|
7933
|
+
name: typeof parsed.name === "string" && parsed.name.trim() !== "" ? parsed.name : candidateName,
|
|
7934
|
+
rootDir: candidateRoot,
|
|
7935
|
+
manifestPath,
|
|
7936
|
+
active: activeMarketplaceNames.has(candidateName),
|
|
7937
|
+
hasSyntaur: parsed.plugins.some(
|
|
7938
|
+
(plugin) => plugin.name === "syntaur" && typeof plugin.source === "string"
|
|
7939
|
+
),
|
|
7940
|
+
isUserPlugins: candidateName === "user-plugins",
|
|
7941
|
+
isDirectorySource: known.source?.source === "directory" || candidateName === "user-plugins"
|
|
7942
|
+
});
|
|
7943
|
+
}
|
|
7944
|
+
return candidates.sort((a, b) => scoreClaudeMarketplaceCandidate(b) - scoreClaudeMarketplaceCandidate(a));
|
|
7945
|
+
}
|
|
7946
|
+
async function getPreferredClaudeMarketplace() {
|
|
7947
|
+
const candidate = (await listClaudeMarketplaceCandidates())[0];
|
|
7948
|
+
if (!candidate) {
|
|
7949
|
+
return null;
|
|
7950
|
+
}
|
|
7951
|
+
return {
|
|
7952
|
+
name: candidate.name,
|
|
7953
|
+
rootDir: candidate.rootDir,
|
|
7954
|
+
manifestPath: candidate.manifestPath,
|
|
7955
|
+
targetDir: resolve28(candidate.rootDir, "plugins", "syntaur")
|
|
7956
|
+
};
|
|
7957
|
+
}
|
|
7958
|
+
async function detectClaudeMarketplaceForTarget(targetDir) {
|
|
7959
|
+
const normalizedTargetDir = normalizeAbsoluteInstallPath(targetDir, "Claude plugin target");
|
|
7960
|
+
const pluginsDir = dirname7(normalizedTargetDir);
|
|
7961
|
+
if (basename(pluginsDir) !== "plugins") {
|
|
7962
|
+
return null;
|
|
7963
|
+
}
|
|
7964
|
+
const rootDir = dirname7(pluginsDir);
|
|
7965
|
+
const manifestPath = resolve28(rootDir, ".claude-plugin", "marketplace.json");
|
|
7966
|
+
if (!await fileExists(manifestPath)) {
|
|
7967
|
+
return null;
|
|
7968
|
+
}
|
|
7969
|
+
const marketplace = await readClaudeMarketplaceFile(manifestPath);
|
|
7970
|
+
const name = typeof marketplace.name === "string" && marketplace.name.trim() !== "" ? marketplace.name : basename(rootDir);
|
|
7971
|
+
return {
|
|
7972
|
+
name,
|
|
7973
|
+
rootDir,
|
|
7974
|
+
manifestPath,
|
|
7975
|
+
targetDir: normalizedTargetDir
|
|
7976
|
+
};
|
|
7977
|
+
}
|
|
7978
|
+
async function findManagedClaudeMarketplacePluginDir() {
|
|
7979
|
+
const marketplaces = await listClaudeMarketplaceCandidates();
|
|
7980
|
+
for (const marketplace of marketplaces) {
|
|
7981
|
+
const targetDir = resolve28(marketplace.rootDir, "plugins", "syntaur");
|
|
7982
|
+
const status = await getInstallStatus(targetDir, "claude");
|
|
7983
|
+
if (status.exists && status.managed) {
|
|
7984
|
+
return targetDir;
|
|
7985
|
+
}
|
|
7986
|
+
}
|
|
7987
|
+
return null;
|
|
7988
|
+
}
|
|
7989
|
+
async function ensureClaudeMarketplaceEntry(options) {
|
|
7990
|
+
const marketplaceRootDir = normalizeAbsoluteInstallPath(
|
|
7991
|
+
options.marketplaceRootDir,
|
|
7992
|
+
"Claude marketplace root"
|
|
7993
|
+
);
|
|
7994
|
+
const manifestPath = normalizeAbsoluteInstallPath(
|
|
7995
|
+
options.manifestPath,
|
|
7996
|
+
"Claude marketplace manifest"
|
|
7997
|
+
);
|
|
7998
|
+
const pluginTargetDir = normalizeAbsoluteInstallPath(
|
|
7999
|
+
options.pluginTargetDir,
|
|
8000
|
+
"Claude plugin target"
|
|
8001
|
+
);
|
|
8002
|
+
const marketplace = await readClaudeMarketplaceFile(manifestPath);
|
|
8003
|
+
const pluginManifest = await readClaudePluginManifest(pluginTargetDir);
|
|
8004
|
+
const entry = buildClaudeMarketplaceEntry(pluginTargetDir, marketplaceRootDir, pluginManifest);
|
|
8005
|
+
const expectedSource = entry.source;
|
|
8006
|
+
const existingIndex = marketplace.plugins.findIndex(
|
|
8007
|
+
(plugin) => plugin.name === entry.name && plugin.source === expectedSource
|
|
8008
|
+
);
|
|
8009
|
+
if (existingIndex >= 0) {
|
|
8010
|
+
const existing = marketplace.plugins[existingIndex];
|
|
8011
|
+
const changed = JSON.stringify(existing) !== JSON.stringify(entry);
|
|
8012
|
+
if (changed) {
|
|
8013
|
+
marketplace.plugins[existingIndex] = entry;
|
|
8014
|
+
await writeClaudeMarketplaceFile(manifestPath, marketplace);
|
|
8015
|
+
}
|
|
8016
|
+
return { manifestPath, changed };
|
|
8017
|
+
}
|
|
8018
|
+
const conflictingIndex = marketplace.plugins.findIndex((plugin) => plugin.name === entry.name);
|
|
8019
|
+
if (conflictingIndex >= 0) {
|
|
8020
|
+
const expectedExistingSource = options.expectedExistingPluginTargetDir ? buildClaudeMarketplaceSourcePath(
|
|
8021
|
+
normalizeAbsoluteInstallPath(
|
|
8022
|
+
options.expectedExistingPluginTargetDir,
|
|
8023
|
+
"Existing Claude plugin target"
|
|
8024
|
+
),
|
|
8025
|
+
marketplaceRootDir
|
|
8026
|
+
) : null;
|
|
8027
|
+
const existing = marketplace.plugins[conflictingIndex];
|
|
8028
|
+
if (expectedExistingSource && existing.source === expectedExistingSource) {
|
|
8029
|
+
marketplace.plugins[conflictingIndex] = entry;
|
|
8030
|
+
await writeClaudeMarketplaceFile(manifestPath, marketplace);
|
|
8031
|
+
return { manifestPath, changed: true };
|
|
8032
|
+
}
|
|
7664
8033
|
throw new Error(
|
|
7665
|
-
`
|
|
8034
|
+
`Marketplace entry "${entry.name}" already exists with different settings in ${manifestPath}. Remove it manually before installing the Claude plugin.`
|
|
7666
8035
|
);
|
|
7667
8036
|
}
|
|
8037
|
+
marketplace.plugins.push(entry);
|
|
8038
|
+
await writeClaudeMarketplaceFile(manifestPath, marketplace);
|
|
8039
|
+
return { manifestPath, changed: true };
|
|
8040
|
+
}
|
|
8041
|
+
async function removeClaudeMarketplaceEntry(options) {
|
|
8042
|
+
const manifestPath = normalizeAbsoluteInstallPath(
|
|
8043
|
+
options.manifestPath,
|
|
8044
|
+
"Claude marketplace manifest"
|
|
8045
|
+
);
|
|
8046
|
+
if (!await fileExists(manifestPath)) {
|
|
8047
|
+
return { manifestPath, removed: false };
|
|
8048
|
+
}
|
|
8049
|
+
const marketplaceRootDir = normalizeAbsoluteInstallPath(
|
|
8050
|
+
options.marketplaceRootDir,
|
|
8051
|
+
"Claude marketplace root"
|
|
8052
|
+
);
|
|
8053
|
+
const expectedSource = options.pluginTargetDir ? buildClaudeMarketplaceSourcePath(
|
|
8054
|
+
normalizeAbsoluteInstallPath(options.pluginTargetDir, "Claude plugin target"),
|
|
8055
|
+
marketplaceRootDir
|
|
8056
|
+
) : null;
|
|
8057
|
+
const marketplace = await readClaudeMarketplaceFile(manifestPath);
|
|
8058
|
+
const beforeCount = marketplace.plugins.length;
|
|
8059
|
+
marketplace.plugins = marketplace.plugins.filter((plugin) => {
|
|
8060
|
+
if (plugin.name !== "syntaur") {
|
|
8061
|
+
return true;
|
|
8062
|
+
}
|
|
8063
|
+
if (!expectedSource) {
|
|
8064
|
+
return false;
|
|
8065
|
+
}
|
|
8066
|
+
return plugin.source !== expectedSource;
|
|
8067
|
+
});
|
|
8068
|
+
if (marketplace.plugins.length === beforeCount) {
|
|
8069
|
+
return { manifestPath, removed: false };
|
|
8070
|
+
}
|
|
8071
|
+
await writeClaudeMarketplaceFile(manifestPath, marketplace);
|
|
8072
|
+
return { manifestPath, removed: true };
|
|
8073
|
+
}
|
|
8074
|
+
async function inspectInstallPath(pluginKind, targetDir) {
|
|
8075
|
+
const normalizedTarget = normalizeAbsoluteInstallPath(targetDir, `${getPluginDisplayName(pluginKind)} target`);
|
|
8076
|
+
const status = await getInstallStatus(normalizedTarget, pluginKind);
|
|
8077
|
+
return {
|
|
8078
|
+
exists: status.exists,
|
|
8079
|
+
managed: status.managed,
|
|
8080
|
+
installMode: status.installMode,
|
|
8081
|
+
targetDir: normalizedTarget
|
|
8082
|
+
};
|
|
8083
|
+
}
|
|
8084
|
+
async function installManagedPlugin(options) {
|
|
8085
|
+
const {
|
|
8086
|
+
pluginKind,
|
|
8087
|
+
force = false,
|
|
8088
|
+
link = false,
|
|
8089
|
+
targetDir = getDefaultPluginTargetDir(pluginKind)
|
|
8090
|
+
} = options;
|
|
8091
|
+
const normalizedTargetDir = normalizeAbsoluteInstallPath(
|
|
8092
|
+
targetDir,
|
|
8093
|
+
`${getPluginDisplayName(pluginKind)} target`
|
|
8094
|
+
);
|
|
8095
|
+
const paths = await resolvePluginPaths(pluginKind, normalizedTargetDir);
|
|
8096
|
+
if (!await fileExists(paths.sourceDir)) {
|
|
8097
|
+
throw new Error(`Plugin source directory not found at ${paths.sourceDir}.`);
|
|
8098
|
+
}
|
|
7668
8099
|
const desiredMode = link ? "link" : "copy";
|
|
7669
8100
|
const existing = await getInstallStatus(paths.targetDir, pluginKind);
|
|
7670
8101
|
if (existing.exists && !existing.managed) {
|
|
@@ -7695,12 +8126,19 @@ async function installManagedPlugin(options) {
|
|
|
7695
8126
|
changed: true
|
|
7696
8127
|
};
|
|
7697
8128
|
}
|
|
7698
|
-
function
|
|
8129
|
+
function buildMarketplaceSourcePath(pluginTargetDir, marketplacePath) {
|
|
8130
|
+
const relPath = relative2(dirname7(marketplacePath), pluginTargetDir).replaceAll("\\", "/");
|
|
8131
|
+
if (relPath === "") {
|
|
8132
|
+
return ".";
|
|
8133
|
+
}
|
|
8134
|
+
return relPath.startsWith(".") ? relPath : `./${relPath}`;
|
|
8135
|
+
}
|
|
8136
|
+
function buildSyntaurMarketplaceEntry(pluginTargetDir, marketplacePath) {
|
|
7699
8137
|
return {
|
|
7700
8138
|
name: "syntaur",
|
|
7701
8139
|
source: {
|
|
7702
8140
|
source: "local",
|
|
7703
|
-
path:
|
|
8141
|
+
path: buildMarketplaceSourcePath(pluginTargetDir, marketplacePath)
|
|
7704
8142
|
},
|
|
7705
8143
|
policy: {
|
|
7706
8144
|
installation: "AVAILABLE",
|
|
@@ -7709,11 +8147,7 @@ function buildSyntaurMarketplaceEntry() {
|
|
|
7709
8147
|
category: "Coding"
|
|
7710
8148
|
};
|
|
7711
8149
|
}
|
|
7712
|
-
function
|
|
7713
|
-
return resolve28(homedir3(), ".agents", "plugins", "marketplace.json");
|
|
7714
|
-
}
|
|
7715
|
-
async function readMarketplaceFile() {
|
|
7716
|
-
const marketplacePath = getMarketplacePath();
|
|
8150
|
+
async function readMarketplaceFile(marketplacePath) {
|
|
7717
8151
|
if (!await fileExists(marketplacePath)) {
|
|
7718
8152
|
return {
|
|
7719
8153
|
name: "local",
|
|
@@ -7729,50 +8163,90 @@ async function readMarketplaceFile() {
|
|
|
7729
8163
|
plugins: Array.isArray(parsed.plugins) ? parsed.plugins : []
|
|
7730
8164
|
};
|
|
7731
8165
|
}
|
|
7732
|
-
async function writeMarketplaceFile(marketplace) {
|
|
7733
|
-
const marketplacePath = getMarketplacePath();
|
|
8166
|
+
async function writeMarketplaceFile(marketplacePath, marketplace) {
|
|
7734
8167
|
await ensureDir(dirname7(marketplacePath));
|
|
7735
8168
|
await writeFile4(marketplacePath, `${JSON.stringify(marketplace, null, 2)}
|
|
7736
8169
|
`, "utf-8");
|
|
7737
8170
|
}
|
|
7738
|
-
async function
|
|
7739
|
-
const marketplace = await readMarketplaceFile();
|
|
7740
|
-
|
|
8171
|
+
async function hasAnySyntaurMarketplaceEntry(marketplacePath) {
|
|
8172
|
+
const marketplace = await readMarketplaceFile(marketplacePath);
|
|
8173
|
+
return marketplace.plugins.some(
|
|
8174
|
+
(plugin) => plugin.name === "syntaur" && plugin.source?.source === "local"
|
|
8175
|
+
);
|
|
8176
|
+
}
|
|
8177
|
+
async function hasSyntaurMarketplaceEntry(marketplacePath, pluginTargetDir) {
|
|
8178
|
+
const marketplace = await readMarketplaceFile(marketplacePath);
|
|
8179
|
+
const expectedPath = buildMarketplaceSourcePath(pluginTargetDir, marketplacePath);
|
|
8180
|
+
return marketplace.plugins.some(
|
|
8181
|
+
(plugin) => plugin.name === "syntaur" && plugin.source?.source === "local" && plugin.source?.path === expectedPath
|
|
8182
|
+
);
|
|
8183
|
+
}
|
|
8184
|
+
async function ensureMarketplaceEntry(options) {
|
|
8185
|
+
const marketplacePath = normalizeAbsoluteInstallPath(
|
|
8186
|
+
options.marketplacePath,
|
|
8187
|
+
"Codex marketplace path"
|
|
8188
|
+
);
|
|
8189
|
+
const pluginTargetDir = normalizeAbsoluteInstallPath(
|
|
8190
|
+
options.pluginTargetDir,
|
|
8191
|
+
"Codex plugin target"
|
|
8192
|
+
);
|
|
8193
|
+
const marketplace = await readMarketplaceFile(marketplacePath);
|
|
8194
|
+
const entry = buildSyntaurMarketplaceEntry(pluginTargetDir, marketplacePath);
|
|
7741
8195
|
const existingIndex = marketplace.plugins.findIndex(
|
|
7742
8196
|
(plugin) => plugin.name === entry.name && plugin.source?.source === entry.source.source && plugin.source?.path === entry.source.path
|
|
7743
8197
|
);
|
|
7744
8198
|
if (existingIndex >= 0) {
|
|
7745
|
-
return {
|
|
7746
|
-
marketplacePath: getMarketplacePath(),
|
|
7747
|
-
changed: false
|
|
7748
|
-
};
|
|
8199
|
+
return { marketplacePath, changed: false };
|
|
7749
8200
|
}
|
|
7750
8201
|
const conflictingIndex = marketplace.plugins.findIndex((plugin) => plugin.name === entry.name);
|
|
7751
8202
|
if (conflictingIndex >= 0) {
|
|
8203
|
+
const existing = marketplace.plugins[conflictingIndex];
|
|
8204
|
+
const expectedExistingPath = options.expectedExistingPluginTargetDir ? buildMarketplaceSourcePath(
|
|
8205
|
+
normalizeAbsoluteInstallPath(
|
|
8206
|
+
options.expectedExistingPluginTargetDir,
|
|
8207
|
+
"Existing Codex plugin target"
|
|
8208
|
+
),
|
|
8209
|
+
marketplacePath
|
|
8210
|
+
) : null;
|
|
8211
|
+
if (existing.source?.source === "local" && expectedExistingPath && existing.source?.path === expectedExistingPath) {
|
|
8212
|
+
marketplace.plugins[conflictingIndex] = entry;
|
|
8213
|
+
await writeMarketplaceFile(marketplacePath, marketplace);
|
|
8214
|
+
return { marketplacePath, changed: true };
|
|
8215
|
+
}
|
|
7752
8216
|
throw new Error(
|
|
7753
|
-
`Marketplace entry "${entry.name}" already exists with different settings. Remove it manually before installing the Codex plugin.`
|
|
8217
|
+
`Marketplace entry "${entry.name}" already exists with different settings in ${marketplacePath}. Remove it manually before installing the Codex plugin.`
|
|
7754
8218
|
);
|
|
7755
8219
|
}
|
|
7756
8220
|
marketplace.plugins.push(entry);
|
|
7757
|
-
await writeMarketplaceFile(marketplace);
|
|
7758
|
-
return {
|
|
7759
|
-
marketplacePath: getMarketplacePath(),
|
|
7760
|
-
changed: true
|
|
7761
|
-
};
|
|
8221
|
+
await writeMarketplaceFile(marketplacePath, marketplace);
|
|
8222
|
+
return { marketplacePath, changed: true };
|
|
7762
8223
|
}
|
|
7763
8224
|
function isDefaultMarketplaceShell(marketplace) {
|
|
7764
8225
|
return marketplace.name === "local" && (marketplace.interface?.displayName ?? "Local Plugins") === "Local Plugins";
|
|
7765
8226
|
}
|
|
7766
|
-
async function removeMarketplaceEntry() {
|
|
7767
|
-
const marketplacePath =
|
|
8227
|
+
async function removeMarketplaceEntry(options) {
|
|
8228
|
+
const marketplacePath = normalizeAbsoluteInstallPath(
|
|
8229
|
+
options.marketplacePath,
|
|
8230
|
+
"Codex marketplace path"
|
|
8231
|
+
);
|
|
7768
8232
|
if (!await fileExists(marketplacePath)) {
|
|
7769
8233
|
return { marketplacePath, removed: false };
|
|
7770
8234
|
}
|
|
7771
|
-
const
|
|
8235
|
+
const expectedSourcePath = options.expectedSourcePath ?? (options.pluginTargetDir ? buildMarketplaceSourcePath(
|
|
8236
|
+
normalizeAbsoluteInstallPath(options.pluginTargetDir, "Codex plugin target"),
|
|
8237
|
+
marketplacePath
|
|
8238
|
+
) : null);
|
|
8239
|
+
const marketplace = await readMarketplaceFile(marketplacePath);
|
|
7772
8240
|
const beforeCount = marketplace.plugins.length;
|
|
7773
|
-
marketplace.plugins = marketplace.plugins.filter(
|
|
7774
|
-
|
|
7775
|
-
|
|
8241
|
+
marketplace.plugins = marketplace.plugins.filter((plugin) => {
|
|
8242
|
+
if (plugin.name !== "syntaur" || plugin.source?.source !== "local") {
|
|
8243
|
+
return true;
|
|
8244
|
+
}
|
|
8245
|
+
if (!expectedSourcePath) {
|
|
8246
|
+
return false;
|
|
8247
|
+
}
|
|
8248
|
+
return plugin.source.path !== expectedSourcePath;
|
|
8249
|
+
});
|
|
7776
8250
|
if (marketplace.plugins.length === beforeCount) {
|
|
7777
8251
|
return { marketplacePath, removed: false };
|
|
7778
8252
|
}
|
|
@@ -7780,23 +8254,66 @@ async function removeMarketplaceEntry() {
|
|
|
7780
8254
|
await rm2(marketplacePath, { force: true });
|
|
7781
8255
|
return { marketplacePath, removed: true };
|
|
7782
8256
|
}
|
|
7783
|
-
await writeMarketplaceFile(marketplace);
|
|
8257
|
+
await writeMarketplaceFile(marketplacePath, marketplace);
|
|
7784
8258
|
return { marketplacePath, removed: true };
|
|
7785
8259
|
}
|
|
7786
|
-
async function uninstallManagedPlugin(pluginKind) {
|
|
7787
|
-
const
|
|
7788
|
-
|
|
8260
|
+
async function uninstallManagedPlugin(pluginKind, targetDir = getDefaultPluginTargetDir(pluginKind)) {
|
|
8261
|
+
const normalizedTarget = normalizeAbsoluteInstallPath(
|
|
8262
|
+
targetDir,
|
|
8263
|
+
`${getPluginDisplayName(pluginKind)} target`
|
|
8264
|
+
);
|
|
8265
|
+
const existing = await getInstallStatus(normalizedTarget, pluginKind);
|
|
7789
8266
|
if (!existing.exists) {
|
|
7790
|
-
return { removed: false, targetDir };
|
|
8267
|
+
return { removed: false, targetDir: normalizedTarget };
|
|
7791
8268
|
}
|
|
7792
8269
|
if (!existing.managed) {
|
|
7793
8270
|
throw new Error(
|
|
7794
|
-
`${
|
|
8271
|
+
`${normalizedTarget} exists but is not a Syntaur-managed install. Remove it manually if you want to replace it.`
|
|
7795
8272
|
);
|
|
7796
8273
|
}
|
|
7797
|
-
await removeInstallMarker(
|
|
7798
|
-
await rm2(
|
|
7799
|
-
return { removed: true, targetDir };
|
|
8274
|
+
await removeInstallMarker(normalizedTarget);
|
|
8275
|
+
await rm2(normalizedTarget, { recursive: true, force: true });
|
|
8276
|
+
return { removed: true, targetDir: normalizedTarget };
|
|
8277
|
+
}
|
|
8278
|
+
async function getConfiguredOrLegacyManagedPluginDir(pluginKind) {
|
|
8279
|
+
const config = await readConfig();
|
|
8280
|
+
const configuredPath = pluginKind === "claude" ? config.integrations.claudePluginDir : config.integrations.codexPluginDir;
|
|
8281
|
+
if (configuredPath) {
|
|
8282
|
+
return configuredPath;
|
|
8283
|
+
}
|
|
8284
|
+
const defaultTarget = getDefaultPluginTargetDir(pluginKind);
|
|
8285
|
+
const status = await getInstallStatus(defaultTarget, pluginKind);
|
|
8286
|
+
if (status.exists && status.managed) {
|
|
8287
|
+
return defaultTarget;
|
|
8288
|
+
}
|
|
8289
|
+
if (pluginKind === "claude") {
|
|
8290
|
+
return findManagedClaudeMarketplacePluginDir();
|
|
8291
|
+
}
|
|
8292
|
+
return null;
|
|
8293
|
+
}
|
|
8294
|
+
async function getConfiguredOrLegacyMarketplacePath() {
|
|
8295
|
+
const config = await readConfig();
|
|
8296
|
+
if (config.integrations.codexMarketplacePath) {
|
|
8297
|
+
return config.integrations.codexMarketplacePath;
|
|
8298
|
+
}
|
|
8299
|
+
const defaultMarketplacePath = getDefaultMarketplacePath();
|
|
8300
|
+
return await hasAnySyntaurMarketplaceEntry(defaultMarketplacePath) ? defaultMarketplacePath : null;
|
|
8301
|
+
}
|
|
8302
|
+
async function recommendPluginTargetDir(pluginKind) {
|
|
8303
|
+
const configuredOrManaged = await getConfiguredOrLegacyManagedPluginDir(pluginKind);
|
|
8304
|
+
if (pluginKind !== "claude") {
|
|
8305
|
+
return configuredOrManaged ?? getDefaultPluginTargetDir(pluginKind);
|
|
8306
|
+
}
|
|
8307
|
+
const preferredMarketplace = await getPreferredClaudeMarketplace();
|
|
8308
|
+
const legacyTarget = getDefaultPluginTargetDir("claude");
|
|
8309
|
+
if (configuredOrManaged) {
|
|
8310
|
+
return configuredOrManaged === legacyTarget && preferredMarketplace ? preferredMarketplace.targetDir : configuredOrManaged;
|
|
8311
|
+
}
|
|
8312
|
+
return preferredMarketplace?.targetDir ?? legacyTarget;
|
|
8313
|
+
}
|
|
8314
|
+
async function recommendMarketplacePath() {
|
|
8315
|
+
const configuredOrManaged = await getConfiguredOrLegacyMarketplacePath();
|
|
8316
|
+
return configuredOrManaged ?? getDefaultMarketplacePath();
|
|
7800
8317
|
}
|
|
7801
8318
|
async function isSyntaurDataInstalled() {
|
|
7802
8319
|
return fileExists(resolve28(syntaurRoot(), "config.md"));
|
|
@@ -7808,52 +8325,224 @@ async function removeSyntaurData() {
|
|
|
7808
8325
|
await rm2(syntaurRoot(), { recursive: true, force: true });
|
|
7809
8326
|
}
|
|
7810
8327
|
async function getConfiguredMissionDir() {
|
|
7811
|
-
|
|
7812
|
-
if (!await fileExists(configPath)) {
|
|
7813
|
-
return null;
|
|
7814
|
-
}
|
|
7815
|
-
const raw = await readFile12(configPath, "utf-8");
|
|
7816
|
-
const match = raw.match(/^defaultMissionDir:\s*(.+)$/m);
|
|
7817
|
-
if (!match) {
|
|
8328
|
+
if (!await fileExists(resolve28(syntaurRoot(), "config.md"))) {
|
|
7818
8329
|
return null;
|
|
7819
8330
|
}
|
|
7820
|
-
return
|
|
8331
|
+
return (await readConfig()).defaultMissionDir;
|
|
8332
|
+
}
|
|
8333
|
+
function getPluginDisplayName(pluginKind) {
|
|
8334
|
+
return pluginKind === "claude" ? "Claude Code plugin" : "Codex plugin";
|
|
7821
8335
|
}
|
|
7822
8336
|
function getPluginInstallCommand(pluginKind) {
|
|
7823
8337
|
return pluginKind === "claude" ? "syntaur install-plugin" : "syntaur install-codex-plugin";
|
|
7824
8338
|
}
|
|
7825
8339
|
|
|
8340
|
+
// src/utils/prompt.ts
|
|
8341
|
+
import { stdin as input, stdout as output } from "process";
|
|
8342
|
+
import { createInterface } from "readline/promises";
|
|
8343
|
+
function isInteractiveTerminal() {
|
|
8344
|
+
return Boolean(input.isTTY && output.isTTY);
|
|
8345
|
+
}
|
|
8346
|
+
async function confirmPrompt(question, defaultValue = false) {
|
|
8347
|
+
if (!isInteractiveTerminal()) {
|
|
8348
|
+
throw new Error("Interactive confirmation requires a TTY.");
|
|
8349
|
+
}
|
|
8350
|
+
const suffix = defaultValue ? " [Y/n] " : " [y/N] ";
|
|
8351
|
+
const rl = createInterface({ input, output });
|
|
8352
|
+
try {
|
|
8353
|
+
const answer = (await rl.question(`${question}${suffix}`)).trim().toLowerCase();
|
|
8354
|
+
if (answer === "") {
|
|
8355
|
+
return defaultValue;
|
|
8356
|
+
}
|
|
8357
|
+
return answer === "y" || answer === "yes";
|
|
8358
|
+
} finally {
|
|
8359
|
+
rl.close();
|
|
8360
|
+
}
|
|
8361
|
+
}
|
|
8362
|
+
async function textPrompt(question, defaultValue) {
|
|
8363
|
+
if (!isInteractiveTerminal()) {
|
|
8364
|
+
throw new Error("Interactive input requires a TTY.");
|
|
8365
|
+
}
|
|
8366
|
+
const suffix = defaultValue !== void 0 ? ` [${defaultValue}] ` : " ";
|
|
8367
|
+
const rl = createInterface({ input, output });
|
|
8368
|
+
try {
|
|
8369
|
+
const answer = (await rl.question(`${question}${suffix}`)).trim();
|
|
8370
|
+
if (answer === "" && defaultValue !== void 0) {
|
|
8371
|
+
return defaultValue;
|
|
8372
|
+
}
|
|
8373
|
+
return answer;
|
|
8374
|
+
} finally {
|
|
8375
|
+
rl.close();
|
|
8376
|
+
}
|
|
8377
|
+
}
|
|
8378
|
+
|
|
7826
8379
|
// src/commands/install-plugin.ts
|
|
8380
|
+
async function promptForInstallPath(question, recommendedPath) {
|
|
8381
|
+
while (true) {
|
|
8382
|
+
const answer = await textPrompt(question, recommendedPath);
|
|
8383
|
+
try {
|
|
8384
|
+
return normalizeAbsoluteInstallPath(answer, question);
|
|
8385
|
+
} catch (error) {
|
|
8386
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
8387
|
+
}
|
|
8388
|
+
}
|
|
8389
|
+
}
|
|
7827
8390
|
async function installPluginCommand(options) {
|
|
8391
|
+
const shouldPromptForTarget = Boolean(
|
|
8392
|
+
options.promptForTarget !== false && isInteractiveTerminal() && !options.targetDir
|
|
8393
|
+
);
|
|
8394
|
+
const recommendedTargetDir = await recommendPluginTargetDir("claude");
|
|
8395
|
+
const targetDir = options.targetDir ? normalizeAbsoluteInstallPath(options.targetDir, "Claude plugin target") : shouldPromptForTarget ? await promptForInstallPath("Claude plugin directory", recommendedTargetDir) : recommendedTargetDir;
|
|
8396
|
+
const previousTargetDir = await getConfiguredOrLegacyManagedPluginDir("claude");
|
|
8397
|
+
const migrating = Boolean(previousTargetDir && previousTargetDir !== targetDir);
|
|
8398
|
+
let previousInstall = previousTargetDir ? await inspectInstallPath("claude", previousTargetDir) : null;
|
|
8399
|
+
const previousMarketplace = previousTargetDir ? await detectClaudeMarketplaceForTarget(previousTargetDir) : null;
|
|
8400
|
+
const legacyTargetDir = getDefaultPluginTargetDir("claude");
|
|
8401
|
+
const legacyInstall = targetDir !== legacyTargetDir ? await inspectInstallPath("claude", legacyTargetDir) : null;
|
|
8402
|
+
if (migrating && previousInstall?.exists && !previousInstall.managed) {
|
|
8403
|
+
throw new Error(
|
|
8404
|
+
`${previousTargetDir} exists but is not a Syntaur-managed install. Remove it manually before changing the Claude plugin location.`
|
|
8405
|
+
);
|
|
8406
|
+
}
|
|
8407
|
+
if (targetDir !== legacyTargetDir && legacyInstall?.exists && !legacyInstall.managed && (!previousTargetDir || previousTargetDir !== legacyTargetDir)) {
|
|
8408
|
+
console.warn(
|
|
8409
|
+
`Warning: ${legacyTargetDir} already exists and is not a Syntaur-managed install. Syntaur will use ${targetDir} instead.`
|
|
8410
|
+
);
|
|
8411
|
+
}
|
|
8412
|
+
if (migrating && previousInstall?.exists && previousInstall.managed && isInteractiveTerminal()) {
|
|
8413
|
+
const confirmed = await confirmPrompt(
|
|
8414
|
+
`Move the Claude Code plugin from ${previousTargetDir} to ${targetDir} and remove the old install?`,
|
|
8415
|
+
true
|
|
8416
|
+
);
|
|
8417
|
+
if (!confirmed) {
|
|
8418
|
+
throw new Error("Install cancelled.");
|
|
8419
|
+
}
|
|
8420
|
+
}
|
|
7828
8421
|
const result = await installManagedPlugin({
|
|
7829
8422
|
pluginKind: "claude",
|
|
7830
8423
|
force: options.force,
|
|
7831
|
-
link: options.link
|
|
8424
|
+
link: options.link,
|
|
8425
|
+
targetDir
|
|
7832
8426
|
});
|
|
7833
|
-
|
|
8427
|
+
const currentMarketplace = await detectClaudeMarketplaceForTarget(result.targetDir);
|
|
8428
|
+
if (currentMarketplace) {
|
|
8429
|
+
await ensureClaudeMarketplaceEntry({
|
|
8430
|
+
marketplaceRootDir: currentMarketplace.rootDir,
|
|
8431
|
+
manifestPath: currentMarketplace.manifestPath,
|
|
8432
|
+
pluginTargetDir: result.targetDir,
|
|
8433
|
+
expectedExistingPluginTargetDir: previousMarketplace && previousMarketplace.manifestPath === currentMarketplace.manifestPath ? previousTargetDir : null
|
|
8434
|
+
});
|
|
8435
|
+
}
|
|
8436
|
+
await updateIntegrationConfig({ claudePluginDir: result.targetDir });
|
|
8437
|
+
if (previousMarketplace && previousTargetDir && (!currentMarketplace || currentMarketplace.manifestPath !== previousMarketplace.manifestPath)) {
|
|
8438
|
+
const removedMarketplaceEntry = await removeClaudeMarketplaceEntry({
|
|
8439
|
+
manifestPath: previousMarketplace.manifestPath,
|
|
8440
|
+
marketplaceRootDir: previousMarketplace.rootDir,
|
|
8441
|
+
pluginTargetDir: previousTargetDir
|
|
8442
|
+
});
|
|
8443
|
+
if (removedMarketplaceEntry.removed) {
|
|
8444
|
+
console.log(`Removed previous Claude marketplace entry from ${removedMarketplaceEntry.manifestPath}`);
|
|
8445
|
+
}
|
|
8446
|
+
}
|
|
8447
|
+
if (migrating && previousInstall?.exists && previousInstall.managed && previousTargetDir) {
|
|
8448
|
+
const removed = await uninstallManagedPlugin("claude", previousTargetDir);
|
|
8449
|
+
if (removed.removed) {
|
|
8450
|
+
console.log(`Removed previous Claude Code plugin from ${removed.targetDir}`);
|
|
8451
|
+
}
|
|
8452
|
+
previousInstall = null;
|
|
8453
|
+
}
|
|
8454
|
+
console.log("Installed Syntaur plugin:");
|
|
7834
8455
|
console.log(` target: ${result.targetDir}`);
|
|
7835
8456
|
console.log(` source: ${result.sourceDir}`);
|
|
7836
8457
|
console.log(` mode: ${result.mode}`);
|
|
7837
|
-
|
|
7838
|
-
|
|
7839
|
-
|
|
7840
|
-
console.log(
|
|
7841
|
-
console.log(
|
|
8458
|
+
if (currentMarketplace) {
|
|
8459
|
+
console.log(` marketplace: ${currentMarketplace.manifestPath}`);
|
|
8460
|
+
}
|
|
8461
|
+
console.log("\nThe plugin is now available in Claude Code.");
|
|
8462
|
+
console.log(" Skills: /grab-assignment, /plan-assignment, /complete-assignment");
|
|
8463
|
+
console.log(" Background: syntaur-protocol (auto-invoked)");
|
|
8464
|
+
console.log(" Hook: write boundary enforcement (PreToolUse)");
|
|
7842
8465
|
}
|
|
7843
8466
|
|
|
7844
8467
|
// src/commands/install-codex-plugin.ts
|
|
8468
|
+
init_config2();
|
|
8469
|
+
async function promptForInstallPath2(question, recommendedPath) {
|
|
8470
|
+
while (true) {
|
|
8471
|
+
const answer = await textPrompt(question, recommendedPath);
|
|
8472
|
+
try {
|
|
8473
|
+
return normalizeAbsoluteInstallPath(answer, question);
|
|
8474
|
+
} catch (error) {
|
|
8475
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
8476
|
+
}
|
|
8477
|
+
}
|
|
8478
|
+
}
|
|
7845
8479
|
async function installCodexPluginCommand(options) {
|
|
8480
|
+
const promptForTarget = Boolean(
|
|
8481
|
+
options.promptForTarget !== false && isInteractiveTerminal() && (!options.targetDir || !options.marketplacePath)
|
|
8482
|
+
);
|
|
8483
|
+
const recommendedTargetDir = await recommendPluginTargetDir("codex");
|
|
8484
|
+
const recommendedMarketplacePath = await recommendMarketplacePath();
|
|
8485
|
+
const targetDir = options.targetDir ? normalizeAbsoluteInstallPath(options.targetDir, "Codex plugin target") : promptForTarget ? await promptForInstallPath2("Codex plugin directory", recommendedTargetDir) : recommendedTargetDir;
|
|
8486
|
+
const marketplacePath = options.marketplacePath ? normalizeAbsoluteInstallPath(options.marketplacePath, "Codex marketplace path") : promptForTarget ? await promptForInstallPath2("Codex marketplace file path", recommendedMarketplacePath) : recommendedMarketplacePath;
|
|
8487
|
+
const previousTargetDir = await getConfiguredOrLegacyManagedPluginDir("codex");
|
|
8488
|
+
const previousMarketplacePath = await getConfiguredOrLegacyMarketplacePath();
|
|
8489
|
+
const pluginPathChanged = Boolean(previousTargetDir && previousTargetDir !== targetDir);
|
|
8490
|
+
const marketplacePathChanged = Boolean(
|
|
8491
|
+
previousMarketplacePath && previousMarketplacePath !== marketplacePath
|
|
8492
|
+
);
|
|
8493
|
+
const previousInstall = previousTargetDir ? await inspectInstallPath("codex", previousTargetDir) : null;
|
|
8494
|
+
const previousMarketplaceEntryExists = Boolean(
|
|
8495
|
+
previousMarketplacePath && previousTargetDir && await hasSyntaurMarketplaceEntry(previousMarketplacePath, previousTargetDir)
|
|
8496
|
+
);
|
|
8497
|
+
if (pluginPathChanged && previousInstall?.exists && !previousInstall.managed) {
|
|
8498
|
+
throw new Error(
|
|
8499
|
+
`${previousTargetDir} exists but is not a Syntaur-managed install. Remove it manually before changing the Codex plugin location.`
|
|
8500
|
+
);
|
|
8501
|
+
}
|
|
8502
|
+
if ((pluginPathChanged || marketplacePathChanged) && (previousInstall?.exists || previousMarketplaceEntryExists) && isInteractiveTerminal()) {
|
|
8503
|
+
const confirmed = await confirmPrompt(
|
|
8504
|
+
`Move the Codex integration to ${targetDir} and ${marketplacePath} and remove the previous Syntaur-managed integration?`,
|
|
8505
|
+
true
|
|
8506
|
+
);
|
|
8507
|
+
if (!confirmed) {
|
|
8508
|
+
throw new Error("Install cancelled.");
|
|
8509
|
+
}
|
|
8510
|
+
}
|
|
7846
8511
|
const result = await installManagedPlugin({
|
|
7847
8512
|
pluginKind: "codex",
|
|
7848
8513
|
force: options.force,
|
|
7849
|
-
link: options.link
|
|
8514
|
+
link: options.link,
|
|
8515
|
+
targetDir
|
|
8516
|
+
});
|
|
8517
|
+
const marketplace = await ensureMarketplaceEntry({
|
|
8518
|
+
marketplacePath,
|
|
8519
|
+
pluginTargetDir: result.targetDir,
|
|
8520
|
+
expectedExistingPluginTargetDir: previousMarketplacePath === marketplacePath ? previousTargetDir : null
|
|
8521
|
+
});
|
|
8522
|
+
await updateIntegrationConfig({
|
|
8523
|
+
codexPluginDir: result.targetDir,
|
|
8524
|
+
codexMarketplacePath: marketplace.marketplacePath
|
|
7850
8525
|
});
|
|
7851
|
-
|
|
8526
|
+
if (pluginPathChanged && previousInstall?.exists && previousInstall.managed && previousTargetDir) {
|
|
8527
|
+
const removed = await uninstallManagedPlugin("codex", previousTargetDir);
|
|
8528
|
+
if (removed.removed) {
|
|
8529
|
+
console.log(`Removed previous Codex plugin from ${removed.targetDir}`);
|
|
8530
|
+
}
|
|
8531
|
+
}
|
|
8532
|
+
if (previousMarketplacePath && previousTargetDir && previousMarketplacePath !== marketplace.marketplacePath) {
|
|
8533
|
+
const removedMarketplace = await removeMarketplaceEntry({
|
|
8534
|
+
marketplacePath: previousMarketplacePath,
|
|
8535
|
+
pluginTargetDir: previousTargetDir
|
|
8536
|
+
});
|
|
8537
|
+
if (removedMarketplace.removed) {
|
|
8538
|
+
console.log(`Removed previous Codex marketplace entry from ${removedMarketplace.marketplacePath}`);
|
|
8539
|
+
}
|
|
8540
|
+
}
|
|
7852
8541
|
console.log("Installed Syntaur Codex plugin:");
|
|
7853
8542
|
console.log(` target: ${result.targetDir}`);
|
|
7854
8543
|
console.log(` source: ${result.sourceDir}`);
|
|
7855
8544
|
console.log(` mode: ${result.mode}`);
|
|
7856
|
-
console.log(` marketplace: ${marketplace.
|
|
8545
|
+
console.log(` marketplace: ${marketplace.marketplacePath}`);
|
|
7857
8546
|
console.log("\nThe plugin is now available to Codex.");
|
|
7858
8547
|
console.log(
|
|
7859
8548
|
" Skills: syntaur-protocol, create-mission, create-assignment, grab-assignment, plan-assignment, complete-assignment, track-session"
|
|
@@ -7862,36 +8551,13 @@ async function installCodexPluginCommand(options) {
|
|
|
7862
8551
|
console.log(" Hooks: write boundary enforcement, session cleanup");
|
|
7863
8552
|
}
|
|
7864
8553
|
|
|
7865
|
-
// src/utils/prompt.ts
|
|
7866
|
-
import { stdin as input, stdout as output } from "process";
|
|
7867
|
-
import { createInterface } from "readline/promises";
|
|
7868
|
-
function isInteractiveTerminal() {
|
|
7869
|
-
return Boolean(input.isTTY && output.isTTY);
|
|
7870
|
-
}
|
|
7871
|
-
async function confirmPrompt(question, defaultValue = false) {
|
|
7872
|
-
if (!isInteractiveTerminal()) {
|
|
7873
|
-
throw new Error("Interactive confirmation requires a TTY.");
|
|
7874
|
-
}
|
|
7875
|
-
const suffix = defaultValue ? " [Y/n] " : " [y/N] ";
|
|
7876
|
-
const rl = createInterface({ input, output });
|
|
7877
|
-
try {
|
|
7878
|
-
const answer = (await rl.question(`${question}${suffix}`)).trim().toLowerCase();
|
|
7879
|
-
if (answer === "") {
|
|
7880
|
-
return defaultValue;
|
|
7881
|
-
}
|
|
7882
|
-
return answer === "y" || answer === "yes";
|
|
7883
|
-
} finally {
|
|
7884
|
-
rl.close();
|
|
7885
|
-
}
|
|
7886
|
-
}
|
|
7887
|
-
|
|
7888
8554
|
// src/commands/setup.ts
|
|
7889
8555
|
function printNonInteractiveSetupHelp() {
|
|
7890
8556
|
console.error("Syntaur setup needs confirmation for optional steps when no TTY is available.");
|
|
7891
8557
|
console.error("Run one of these commands instead:");
|
|
7892
8558
|
console.error(" npx syntaur@latest setup --yes");
|
|
7893
|
-
console.error(
|
|
7894
|
-
console.error(
|
|
8559
|
+
console.error(" npx syntaur@latest setup --yes --claude");
|
|
8560
|
+
console.error(" npx syntaur@latest setup --yes --codex");
|
|
7895
8561
|
console.error(` npx syntaur@latest setup --yes --dashboard`);
|
|
7896
8562
|
}
|
|
7897
8563
|
async function setupCommand(options) {
|
|
@@ -7921,12 +8587,19 @@ async function setupCommand(options) {
|
|
|
7921
8587
|
}
|
|
7922
8588
|
}
|
|
7923
8589
|
if (installClaude) {
|
|
7924
|
-
await installPluginCommand({
|
|
8590
|
+
await installPluginCommand({
|
|
8591
|
+
targetDir: options.claudeDir,
|
|
8592
|
+
promptForTarget: !options.yes
|
|
8593
|
+
});
|
|
7925
8594
|
} else if (!interactive && !options.yes && !initialized) {
|
|
7926
8595
|
console.log(`Skip Claude plugin for now. Install later with: ${getPluginInstallCommand("claude")}`);
|
|
7927
8596
|
}
|
|
7928
8597
|
if (installCodex) {
|
|
7929
|
-
await installCodexPluginCommand({
|
|
8598
|
+
await installCodexPluginCommand({
|
|
8599
|
+
targetDir: options.codexDir,
|
|
8600
|
+
marketplacePath: options.codexMarketplacePath,
|
|
8601
|
+
promptForTarget: !options.yes
|
|
8602
|
+
});
|
|
7930
8603
|
} else if (!interactive && !options.yes && !initialized) {
|
|
7931
8604
|
console.log(`Skip Codex plugin for now. Install later with: ${getPluginInstallCommand("codex")}`);
|
|
7932
8605
|
}
|
|
@@ -7984,19 +8657,44 @@ async function uninstallCommand(options) {
|
|
|
7984
8657
|
}
|
|
7985
8658
|
}
|
|
7986
8659
|
if (targets.claude) {
|
|
7987
|
-
const
|
|
8660
|
+
const claudeTargetDir = await getConfiguredOrLegacyManagedPluginDir("claude");
|
|
8661
|
+
const claudeMarketplace = claudeTargetDir ? await detectClaudeMarketplaceForTarget(claudeTargetDir) : null;
|
|
8662
|
+
const result = await uninstallManagedPlugin(
|
|
8663
|
+
"claude",
|
|
8664
|
+
claudeTargetDir ?? void 0
|
|
8665
|
+
);
|
|
7988
8666
|
console.log(
|
|
7989
8667
|
result.removed ? `Removed Claude Code plugin from ${result.targetDir}` : `Claude Code plugin not installed at ${result.targetDir}`
|
|
7990
8668
|
);
|
|
8669
|
+
if (claudeMarketplace) {
|
|
8670
|
+
const removedMarketplaceEntry = await removeClaudeMarketplaceEntry({
|
|
8671
|
+
manifestPath: claudeMarketplace.manifestPath,
|
|
8672
|
+
marketplaceRootDir: claudeMarketplace.rootDir,
|
|
8673
|
+
pluginTargetDir: claudeTargetDir ?? void 0
|
|
8674
|
+
});
|
|
8675
|
+
if (removedMarketplaceEntry.removed) {
|
|
8676
|
+
console.log(`Removed Claude marketplace entry from ${removedMarketplaceEntry.manifestPath}`);
|
|
8677
|
+
}
|
|
8678
|
+
}
|
|
7991
8679
|
}
|
|
7992
8680
|
if (targets.codex) {
|
|
7993
|
-
const
|
|
8681
|
+
const codexTargetDir = await getConfiguredOrLegacyManagedPluginDir("codex");
|
|
8682
|
+
const marketplacePath = await getConfiguredOrLegacyMarketplacePath();
|
|
8683
|
+
const result = await uninstallManagedPlugin(
|
|
8684
|
+
"codex",
|
|
8685
|
+
codexTargetDir ?? void 0
|
|
8686
|
+
);
|
|
7994
8687
|
console.log(
|
|
7995
8688
|
result.removed ? `Removed Codex plugin from ${result.targetDir}` : `Codex plugin not installed at ${result.targetDir}`
|
|
7996
8689
|
);
|
|
7997
|
-
|
|
7998
|
-
|
|
7999
|
-
|
|
8690
|
+
if (marketplacePath) {
|
|
8691
|
+
const marketplace = await removeMarketplaceEntry({
|
|
8692
|
+
marketplacePath,
|
|
8693
|
+
pluginTargetDir: codexTargetDir ?? void 0
|
|
8694
|
+
});
|
|
8695
|
+
if (marketplace.removed) {
|
|
8696
|
+
console.log(`Removed Codex marketplace entry from ${marketplace.marketplacePath}`);
|
|
8697
|
+
}
|
|
8000
8698
|
}
|
|
8001
8699
|
}
|
|
8002
8700
|
if (targets.data) {
|
|
@@ -8017,7 +8715,7 @@ async function uninstallCommand(options) {
|
|
|
8017
8715
|
// src/commands/setup-adapter.ts
|
|
8018
8716
|
init_paths();
|
|
8019
8717
|
init_fs();
|
|
8020
|
-
|
|
8718
|
+
init_config2();
|
|
8021
8719
|
import { resolve as resolve30 } from "path";
|
|
8022
8720
|
var SUPPORTED_FRAMEWORKS = ["cursor", "codex", "opencode"];
|
|
8023
8721
|
async function setupAdapterCommand(framework, options) {
|
|
@@ -8116,7 +8814,7 @@ async function setupAdapterCommand(framework, options) {
|
|
|
8116
8814
|
// src/commands/track-session.ts
|
|
8117
8815
|
init_paths();
|
|
8118
8816
|
init_fs();
|
|
8119
|
-
|
|
8817
|
+
init_config2();
|
|
8120
8818
|
import { resolve as resolve31 } from "path";
|
|
8121
8819
|
import { randomUUID as randomUUID3 } from "crypto";
|
|
8122
8820
|
async function trackSessionCommand(options) {
|
|
@@ -8153,7 +8851,7 @@ async function trackSessionCommand(options) {
|
|
|
8153
8851
|
}
|
|
8154
8852
|
|
|
8155
8853
|
// src/commands/browse.ts
|
|
8156
|
-
|
|
8854
|
+
init_config2();
|
|
8157
8855
|
async function browseCommand(options) {
|
|
8158
8856
|
const config = await readConfig();
|
|
8159
8857
|
const missionsDir = config.defaultMissionDir;
|
|
@@ -8215,7 +8913,7 @@ Use --slug to specify a different slug.`
|
|
|
8215
8913
|
init_paths();
|
|
8216
8914
|
init_fs();
|
|
8217
8915
|
init_parser();
|
|
8218
|
-
import { readdir as
|
|
8916
|
+
import { readdir as readdir9, readFile as readFile13 } from "fs/promises";
|
|
8219
8917
|
import { resolve as resolve34 } from "path";
|
|
8220
8918
|
async function listPlaybooksCommand() {
|
|
8221
8919
|
const dir = playbooksDir();
|
|
@@ -8223,7 +8921,7 @@ async function listPlaybooksCommand() {
|
|
|
8223
8921
|
console.log('No playbooks directory found. Run "syntaur init" first.');
|
|
8224
8922
|
return;
|
|
8225
8923
|
}
|
|
8226
|
-
const entries = await
|
|
8924
|
+
const entries = await readdir9(dir, { withFileTypes: true });
|
|
8227
8925
|
const mdFiles = entries.filter((e) => e.isFile() && e.name.endsWith(".md") && !e.name.startsWith("_") && e.name !== "manifest.md");
|
|
8228
8926
|
if (mdFiles.length === 0) {
|
|
8229
8927
|
console.log('No playbooks found. Create one with "syntaur create-playbook <name>".');
|
|
@@ -8785,7 +9483,7 @@ program.command("reopen").description("Reopen a completed or failed assignment")
|
|
|
8785
9483
|
process.exit(1);
|
|
8786
9484
|
}
|
|
8787
9485
|
});
|
|
8788
|
-
program.command("setup").description("Initialize Syntaur and optionally install plugins or launch the dashboard").option("--yes", "Skip interactive prompts and perform only the requested flags").option("--claude", "Install the Claude Code plugin").option("--codex", "Install the Codex plugin").option("--dashboard", "Launch the dashboard after setup").action(async (options) => {
|
|
9486
|
+
program.command("setup").description("Initialize Syntaur and optionally install plugins or launch the dashboard").option("--yes", "Skip interactive prompts and perform only the requested flags").option("--claude", "Install the Claude Code plugin").option("--codex", "Install the Codex plugin").option("--claude-dir <path>", "Install the Claude Code plugin at a specific path").option("--codex-dir <path>", "Install the Codex plugin at a specific path").option("--codex-marketplace-path <path>", "Write the Codex marketplace entry to a specific file").option("--dashboard", "Launch the dashboard after setup").action(async (options) => {
|
|
8789
9487
|
try {
|
|
8790
9488
|
await setupCommand(options);
|
|
8791
9489
|
} catch (error) {
|
|
@@ -8796,9 +9494,9 @@ program.command("setup").description("Initialize Syntaur and optionally install
|
|
|
8796
9494
|
process.exit(1);
|
|
8797
9495
|
}
|
|
8798
9496
|
});
|
|
8799
|
-
program.command("install-plugin").description("Install the Syntaur Claude Code plugin").option("--force", "Overwrite an existing Syntaur-managed install").option("--link", "Use a symlink instead of copying files (repo-local dev only)").action(async (options) => {
|
|
9497
|
+
program.command("install-plugin").description("Install the Syntaur Claude Code plugin").option("--force", "Overwrite an existing Syntaur-managed install").option("--target-dir <path>", "Install the plugin at a specific directory").option("--link", "Use a symlink instead of copying files (repo-local dev only)").action(async (options) => {
|
|
8800
9498
|
try {
|
|
8801
|
-
await installPluginCommand(options);
|
|
9499
|
+
await installPluginCommand({ ...options, promptForTarget: true });
|
|
8802
9500
|
} catch (error) {
|
|
8803
9501
|
console.error(
|
|
8804
9502
|
"Error:",
|
|
@@ -8807,9 +9505,9 @@ program.command("install-plugin").description("Install the Syntaur Claude Code p
|
|
|
8807
9505
|
process.exit(1);
|
|
8808
9506
|
}
|
|
8809
9507
|
});
|
|
8810
|
-
program.command("install-codex-plugin").description("Install the Syntaur Codex plugin and
|
|
9508
|
+
program.command("install-codex-plugin").description("Install the Syntaur Codex plugin and marketplace entry").option("--force", "Overwrite an existing Syntaur-managed install").option("--target-dir <path>", "Install the plugin at a specific directory").option("--marketplace-path <path>", "Write the marketplace entry to a specific file").option("--link", "Use a symlink instead of copying files (repo-local dev only)").action(async (options) => {
|
|
8811
9509
|
try {
|
|
8812
|
-
await installCodexPluginCommand(options);
|
|
9510
|
+
await installCodexPluginCommand({ ...options, promptForTarget: true });
|
|
8813
9511
|
} catch (error) {
|
|
8814
9512
|
console.error(
|
|
8815
9513
|
"Error:",
|