@izantech/lineup-cli 2.1.0 → 2.2.0
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/dist/{chunk-RQBQVCEG.js → chunk-G7CR3AA7.js} +296 -67
- package/dist/cli.js +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/package.json +1 -1
- package/schemas/json/host-adapter.schema.json +9 -3
- package/schemas/json/state.schema.json +3 -0
- package/schemas/yaml/tactic.schema.json +14 -2
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// src/cli.ts
|
|
2
2
|
import { readFileSync as readFileSync6 } from "fs";
|
|
3
|
-
import
|
|
3
|
+
import path9 from "path";
|
|
4
4
|
import process2 from "process";
|
|
5
5
|
import { Command } from "commander";
|
|
6
6
|
|
|
@@ -15,7 +15,8 @@ function printTableLine(text) {
|
|
|
15
15
|
}
|
|
16
16
|
|
|
17
17
|
// src/lib/operations.ts
|
|
18
|
-
import { rmSync as
|
|
18
|
+
import { rmSync as rmSync4 } from "fs";
|
|
19
|
+
import os2 from "os";
|
|
19
20
|
|
|
20
21
|
// src/lib/errors.ts
|
|
21
22
|
var CliError = class extends Error {
|
|
@@ -45,54 +46,114 @@ var LINEUP_PLUGIN_NAME = "lineup";
|
|
|
45
46
|
var CLAUDE_LEGACY_PLUGIN = "lineup@izantech";
|
|
46
47
|
var CLAUDE_LOCAL_MARKETPLACE_NAME = "lineup-local";
|
|
47
48
|
var CLAUDE_LOCAL_PLUGIN = `${LINEUP_PLUGIN_NAME}@${CLAUDE_LOCAL_MARKETPLACE_NAME}`;
|
|
48
|
-
var SUPPORTED_HOSTS = ["claude", "codex"];
|
|
49
|
+
var SUPPORTED_HOSTS = ["claude", "codex", "opencode"];
|
|
49
50
|
var CODEX_SKILL_DIRS = [
|
|
50
51
|
"lineup-kick-off",
|
|
51
52
|
"lineup-configure",
|
|
52
53
|
"lineup-explain",
|
|
53
|
-
"lineup-playbook"
|
|
54
|
+
"lineup-playbook",
|
|
55
|
+
"lineup-digest"
|
|
54
56
|
];
|
|
55
57
|
var CODEX_REQUIRED_FILES = [
|
|
56
58
|
".agents/skills/lineup-kick-off/SKILL.md",
|
|
57
59
|
".agents/skills/lineup-kick-off/INIT.md",
|
|
60
|
+
".agents/skills/lineup-kick-off/STAGES-1-3.md",
|
|
61
|
+
".agents/skills/lineup-kick-off/STAGES-4-5.md",
|
|
62
|
+
".agents/skills/lineup-kick-off/STAGES-6-7.md",
|
|
58
63
|
".agents/skills/lineup-configure/SKILL.md",
|
|
59
64
|
".agents/skills/lineup-explain/SKILL.md",
|
|
60
|
-
".agents/skills/lineup-playbook/SKILL.md"
|
|
65
|
+
".agents/skills/lineup-playbook/SKILL.md",
|
|
66
|
+
".agents/skills/lineup-digest/SKILL.md"
|
|
67
|
+
];
|
|
68
|
+
var OPENCODE_SKILL_DIRS = [
|
|
69
|
+
"lineup-kick-off",
|
|
70
|
+
"lineup-configure",
|
|
71
|
+
"lineup-explain",
|
|
72
|
+
"lineup-playbook",
|
|
73
|
+
"lineup-digest"
|
|
74
|
+
];
|
|
75
|
+
var OPENCODE_REQUIRED_FILES = [
|
|
76
|
+
".opencode/skills/lineup-kick-off/SKILL.md",
|
|
77
|
+
".opencode/skills/lineup-kick-off/INIT.md",
|
|
78
|
+
".opencode/skills/lineup-kick-off/STAGES-1-3.md",
|
|
79
|
+
".opencode/skills/lineup-kick-off/STAGES-4-5.md",
|
|
80
|
+
".opencode/skills/lineup-kick-off/STAGES-6-7.md",
|
|
81
|
+
".opencode/skills/lineup-configure/SKILL.md",
|
|
82
|
+
".opencode/skills/lineup-explain/SKILL.md",
|
|
83
|
+
".opencode/skills/lineup-playbook/SKILL.md",
|
|
84
|
+
".opencode/skills/lineup-digest/SKILL.md"
|
|
61
85
|
];
|
|
62
86
|
var HOST_TEMPLATE_SPECS = [
|
|
63
87
|
{
|
|
64
88
|
source: ".lineup-core/skills/kick-off/core.md",
|
|
65
89
|
targetFor: {
|
|
66
90
|
claude: "skills/{{SKILL_NAME_KICKOFF}}/SKILL.md",
|
|
67
|
-
codex: ".agents/skills/{{SKILL_NAME_KICKOFF}}/SKILL.md"
|
|
91
|
+
codex: ".agents/skills/{{SKILL_NAME_KICKOFF}}/SKILL.md",
|
|
92
|
+
opencode: ".opencode/skills/{{SKILL_NAME_KICKOFF}}/SKILL.md"
|
|
68
93
|
}
|
|
69
94
|
},
|
|
70
95
|
{
|
|
71
96
|
source: ".lineup-core/skills/kick-off/init.core.md",
|
|
72
97
|
targetFor: {
|
|
73
98
|
claude: "skills/{{SKILL_NAME_KICKOFF}}/INIT.md",
|
|
74
|
-
codex: ".agents/skills/{{SKILL_NAME_KICKOFF}}/INIT.md"
|
|
99
|
+
codex: ".agents/skills/{{SKILL_NAME_KICKOFF}}/INIT.md",
|
|
100
|
+
opencode: ".opencode/skills/{{SKILL_NAME_KICKOFF}}/INIT.md"
|
|
101
|
+
}
|
|
102
|
+
},
|
|
103
|
+
{
|
|
104
|
+
source: ".lineup-core/skills/kick-off/stages-1-3.core.md",
|
|
105
|
+
targetFor: {
|
|
106
|
+
claude: "skills/{{SKILL_NAME_KICKOFF}}/STAGES-1-3.md",
|
|
107
|
+
codex: ".agents/skills/{{SKILL_NAME_KICKOFF}}/STAGES-1-3.md",
|
|
108
|
+
opencode: ".opencode/skills/{{SKILL_NAME_KICKOFF}}/STAGES-1-3.md"
|
|
109
|
+
}
|
|
110
|
+
},
|
|
111
|
+
{
|
|
112
|
+
source: ".lineup-core/skills/kick-off/stages-4-5.core.md",
|
|
113
|
+
targetFor: {
|
|
114
|
+
claude: "skills/{{SKILL_NAME_KICKOFF}}/STAGES-4-5.md",
|
|
115
|
+
codex: ".agents/skills/{{SKILL_NAME_KICKOFF}}/STAGES-4-5.md",
|
|
116
|
+
opencode: ".opencode/skills/{{SKILL_NAME_KICKOFF}}/STAGES-4-5.md"
|
|
117
|
+
}
|
|
118
|
+
},
|
|
119
|
+
{
|
|
120
|
+
source: ".lineup-core/skills/kick-off/stages-6-7.core.md",
|
|
121
|
+
targetFor: {
|
|
122
|
+
claude: "skills/{{SKILL_NAME_KICKOFF}}/STAGES-6-7.md",
|
|
123
|
+
codex: ".agents/skills/{{SKILL_NAME_KICKOFF}}/STAGES-6-7.md",
|
|
124
|
+
opencode: ".opencode/skills/{{SKILL_NAME_KICKOFF}}/STAGES-6-7.md"
|
|
75
125
|
}
|
|
76
126
|
},
|
|
77
127
|
{
|
|
78
128
|
source: ".lineup-core/skills/configure/core.md",
|
|
79
129
|
targetFor: {
|
|
80
130
|
claude: "skills/{{SKILL_NAME_CONFIGURE}}/SKILL.md",
|
|
81
|
-
codex: ".agents/skills/{{SKILL_NAME_CONFIGURE}}/SKILL.md"
|
|
131
|
+
codex: ".agents/skills/{{SKILL_NAME_CONFIGURE}}/SKILL.md",
|
|
132
|
+
opencode: ".opencode/skills/{{SKILL_NAME_CONFIGURE}}/SKILL.md"
|
|
82
133
|
}
|
|
83
134
|
},
|
|
84
135
|
{
|
|
85
136
|
source: ".lineup-core/skills/explain/core.md",
|
|
86
137
|
targetFor: {
|
|
87
138
|
claude: "skills/{{SKILL_NAME_EXPLAIN}}/SKILL.md",
|
|
88
|
-
codex: ".agents/skills/{{SKILL_NAME_EXPLAIN}}/SKILL.md"
|
|
139
|
+
codex: ".agents/skills/{{SKILL_NAME_EXPLAIN}}/SKILL.md",
|
|
140
|
+
opencode: ".opencode/skills/{{SKILL_NAME_EXPLAIN}}/SKILL.md"
|
|
89
141
|
}
|
|
90
142
|
},
|
|
91
143
|
{
|
|
92
144
|
source: ".lineup-core/skills/playbook/core.md",
|
|
93
145
|
targetFor: {
|
|
94
146
|
claude: "skills/{{SKILL_NAME_PLAYBOOK}}/SKILL.md",
|
|
95
|
-
codex: ".agents/skills/{{SKILL_NAME_PLAYBOOK}}/SKILL.md"
|
|
147
|
+
codex: ".agents/skills/{{SKILL_NAME_PLAYBOOK}}/SKILL.md",
|
|
148
|
+
opencode: ".opencode/skills/{{SKILL_NAME_PLAYBOOK}}/SKILL.md"
|
|
149
|
+
}
|
|
150
|
+
},
|
|
151
|
+
{
|
|
152
|
+
source: ".lineup-core/skills/digest/core.md",
|
|
153
|
+
targetFor: {
|
|
154
|
+
claude: "skills/{{SKILL_NAME_DIGEST}}/SKILL.md",
|
|
155
|
+
codex: ".agents/skills/{{SKILL_NAME_DIGEST}}/SKILL.md",
|
|
156
|
+
opencode: ".opencode/skills/{{SKILL_NAME_DIGEST}}/SKILL.md"
|
|
96
157
|
}
|
|
97
158
|
}
|
|
98
159
|
];
|
|
@@ -153,6 +214,12 @@ function claudeHostRoot(homeDir = os.homedir()) {
|
|
|
153
214
|
function codexHostRoot(homeDir = os.homedir()) {
|
|
154
215
|
return path.join(lineupHome(homeDir), "hosts", "codex");
|
|
155
216
|
}
|
|
217
|
+
function opencodeGlobalSkillsDir(homeDir = os.homedir()) {
|
|
218
|
+
return path.join(homeDir, ".config", "opencode", "skills");
|
|
219
|
+
}
|
|
220
|
+
function opencodeHostRoot(homeDir = os.homedir()) {
|
|
221
|
+
return path.join(lineupHome(homeDir), "hosts", "opencode");
|
|
222
|
+
}
|
|
156
223
|
function claudeManagedPluginDir(version, homeDir = os.homedir()) {
|
|
157
224
|
return path.join(claudeMarketplaceRoot(homeDir), "plugins", "lineup", version);
|
|
158
225
|
}
|
|
@@ -171,6 +238,9 @@ function purgeTargets(hosts, homeDir = os.homedir()) {
|
|
|
171
238
|
targets.push(path.join(homeDir, ".codex", "lineup", "agents"));
|
|
172
239
|
targets.push(path.join(homeDir, ".codex", "lineup", "memory"));
|
|
173
240
|
}
|
|
241
|
+
if (hosts.includes("opencode")) {
|
|
242
|
+
targets.push(path.join(homeDir, ".config", "opencode", "lineup"));
|
|
243
|
+
}
|
|
174
244
|
return targets;
|
|
175
245
|
}
|
|
176
246
|
|
|
@@ -361,7 +431,7 @@ function writeGeneratedFiles(files, outputRoot) {
|
|
|
361
431
|
}
|
|
362
432
|
}
|
|
363
433
|
function prepareClaudePluginSkeleton(sourceRoot, outputRoot) {
|
|
364
|
-
const copyPaths = [".claude-plugin", "agents", "tactics", "templates"
|
|
434
|
+
const copyPaths = [".claude-plugin", "agents", "tactics", "templates"];
|
|
365
435
|
for (const entry of copyPaths) {
|
|
366
436
|
const from = path3.join(sourceRoot, entry);
|
|
367
437
|
const to = path3.join(outputRoot, entry);
|
|
@@ -435,6 +505,13 @@ function parseInstallPresence(output2) {
|
|
|
435
505
|
legacyInstalled: new RegExp(CLAUDE_LEGACY_PLUGIN, "i").test(output2)
|
|
436
506
|
};
|
|
437
507
|
}
|
|
508
|
+
function checkPluginOnDisk() {
|
|
509
|
+
const marketplaceManifest = path4.join(claudeMarketplaceRoot(), ".claude-plugin", "marketplace.json");
|
|
510
|
+
if (existsSync3(marketplaceManifest)) {
|
|
511
|
+
return { installed: true, source: "cli-managed" };
|
|
512
|
+
}
|
|
513
|
+
return { installed: false, source: null };
|
|
514
|
+
}
|
|
438
515
|
function writeMarketplace(root, pluginSource, version) {
|
|
439
516
|
const dotClaude = path4.join(root, ".claude-plugin");
|
|
440
517
|
mkdirSync2(dotClaude, { recursive: true });
|
|
@@ -479,13 +556,14 @@ async function statusClaude() {
|
|
|
479
556
|
try {
|
|
480
557
|
const result = await runCommand("claude", ["plugin", "list"]);
|
|
481
558
|
if (result.code !== 0) {
|
|
559
|
+
const disk = checkPluginOnDisk();
|
|
482
560
|
return {
|
|
483
561
|
host: "claude",
|
|
484
|
-
installed:
|
|
562
|
+
installed: disk.installed,
|
|
485
563
|
version: null,
|
|
486
|
-
source:
|
|
564
|
+
source: disk.source,
|
|
487
565
|
last_action: null,
|
|
488
|
-
error: result.stderr.trim() || "Failed to run claude plugin list"
|
|
566
|
+
error: disk.installed ? "claude plugin list unavailable; status detected from filesystem" : result.stderr.trim() || "Failed to run claude plugin list"
|
|
489
567
|
};
|
|
490
568
|
}
|
|
491
569
|
const output2 = `${result.stdout}
|
|
@@ -641,6 +719,92 @@ function statusCodex(global = true) {
|
|
|
641
719
|
};
|
|
642
720
|
}
|
|
643
721
|
|
|
722
|
+
// src/lib/host-opencode.ts
|
|
723
|
+
import { cpSync as cpSync3, existsSync as existsSync5, mkdirSync as mkdirSync4, renameSync as renameSync2, rmSync as rmSync2 } from "fs";
|
|
724
|
+
import path6 from "path";
|
|
725
|
+
function ensureOpencodeGenerated(sourceRoot, homeDir) {
|
|
726
|
+
const outputRoot = opencodeHostRoot(homeDir);
|
|
727
|
+
const files = generateHostFiles(sourceRoot, "opencode");
|
|
728
|
+
writeGeneratedFiles(files, outputRoot);
|
|
729
|
+
return path6.join(outputRoot, ".opencode", "skills");
|
|
730
|
+
}
|
|
731
|
+
function requiredAbsolutePaths2(baseDir) {
|
|
732
|
+
return OPENCODE_REQUIRED_FILES.map((relative) => path6.join(baseDir, ...relative.replace(/^\.opencode\/skills\//, "").split("/")));
|
|
733
|
+
}
|
|
734
|
+
function validateOpencodeSkillsDir(skillsDir) {
|
|
735
|
+
const missing = requiredAbsolutePaths2(skillsDir).filter((item) => !existsSync5(item));
|
|
736
|
+
if (missing.length > 0) {
|
|
737
|
+
throw new CliError([`OpenCode skills directory is missing required files in ${skillsDir}:`, ...missing.map((item) => `- ${item}`)].join("\n"), {
|
|
738
|
+
code: "opencode_skill_validation_failed"
|
|
739
|
+
});
|
|
740
|
+
}
|
|
741
|
+
}
|
|
742
|
+
function replaceDirectoryAtomic2(sourceDir, targetDir) {
|
|
743
|
+
const parentDir = path6.dirname(targetDir);
|
|
744
|
+
const nonce = `${Date.now()}-${process.pid}`;
|
|
745
|
+
const tempDir = path6.join(parentDir, `.${path6.basename(targetDir)}.tmp-${nonce}`);
|
|
746
|
+
const backupDir = path6.join(parentDir, `.${path6.basename(targetDir)}.bak-${nonce}`);
|
|
747
|
+
mkdirSync4(parentDir, { recursive: true });
|
|
748
|
+
cpSync3(sourceDir, tempDir, { recursive: true });
|
|
749
|
+
let movedTarget = false;
|
|
750
|
+
try {
|
|
751
|
+
if (existsSync5(targetDir)) {
|
|
752
|
+
renameSync2(targetDir, backupDir);
|
|
753
|
+
movedTarget = true;
|
|
754
|
+
}
|
|
755
|
+
renameSync2(tempDir, targetDir);
|
|
756
|
+
if (movedTarget && existsSync5(backupDir)) {
|
|
757
|
+
rmSync2(backupDir, { recursive: true, force: true });
|
|
758
|
+
}
|
|
759
|
+
} catch (error) {
|
|
760
|
+
if (existsSync5(tempDir)) {
|
|
761
|
+
rmSync2(tempDir, { recursive: true, force: true });
|
|
762
|
+
}
|
|
763
|
+
if (movedTarget && !existsSync5(targetDir) && existsSync5(backupDir)) {
|
|
764
|
+
renameSync2(backupDir, targetDir);
|
|
765
|
+
}
|
|
766
|
+
throw error;
|
|
767
|
+
}
|
|
768
|
+
}
|
|
769
|
+
function installOpencode(sourceRoot, homeDir) {
|
|
770
|
+
const sourceSkills = ensureOpencodeGenerated(sourceRoot, homeDir);
|
|
771
|
+
validateOpencodeSkillsDir(sourceSkills);
|
|
772
|
+
const destinationRoot = opencodeGlobalSkillsDir(homeDir);
|
|
773
|
+
mkdirSync4(destinationRoot, { recursive: true });
|
|
774
|
+
for (const dirName of OPENCODE_SKILL_DIRS) {
|
|
775
|
+
const from = path6.join(sourceSkills, dirName);
|
|
776
|
+
const to = path6.join(destinationRoot, dirName);
|
|
777
|
+
replaceDirectoryAtomic2(from, to);
|
|
778
|
+
}
|
|
779
|
+
validateOpencodeSkillsDir(destinationRoot);
|
|
780
|
+
return {
|
|
781
|
+
skills_dir: destinationRoot,
|
|
782
|
+
files_verified: OPENCODE_REQUIRED_FILES.length
|
|
783
|
+
};
|
|
784
|
+
}
|
|
785
|
+
function uninstallOpencode(homeDir) {
|
|
786
|
+
const root = opencodeGlobalSkillsDir(homeDir);
|
|
787
|
+
for (const dirName of OPENCODE_SKILL_DIRS) {
|
|
788
|
+
const target = path6.join(root, dirName);
|
|
789
|
+
if (existsSync5(target)) {
|
|
790
|
+
rmSync2(target, { recursive: true, force: true });
|
|
791
|
+
}
|
|
792
|
+
}
|
|
793
|
+
return { skills_dir: root };
|
|
794
|
+
}
|
|
795
|
+
function statusOpencode(homeDir) {
|
|
796
|
+
const root = opencodeGlobalSkillsDir(homeDir);
|
|
797
|
+
const missing = requiredAbsolutePaths2(root).filter((item) => !existsSync5(item));
|
|
798
|
+
return {
|
|
799
|
+
host: "opencode",
|
|
800
|
+
installed: missing.length === 0,
|
|
801
|
+
version: null,
|
|
802
|
+
source: null,
|
|
803
|
+
last_action: null,
|
|
804
|
+
...missing.length > 0 ? { error: `Missing ${missing.length} required files.` } : {}
|
|
805
|
+
};
|
|
806
|
+
}
|
|
807
|
+
|
|
644
808
|
// src/lib/prompts.ts
|
|
645
809
|
import readline from "readline/promises";
|
|
646
810
|
import { stdin as input, stdout as output } from "process";
|
|
@@ -669,9 +833,10 @@ async function promptHostSelection() {
|
|
|
669
833
|
output.write("Select host(s):\n");
|
|
670
834
|
output.write(" 1. claude\n");
|
|
671
835
|
output.write(" 2. codex\n");
|
|
672
|
-
output.write(" 3.
|
|
836
|
+
output.write(" 3. opencode\n");
|
|
837
|
+
output.write(" 4. all\n");
|
|
673
838
|
while (true) {
|
|
674
|
-
const answer = await rl.question("Enter selection [1-
|
|
839
|
+
const answer = await rl.question("Enter selection [1-4]: ");
|
|
675
840
|
const normalized = answer.trim().toLowerCase();
|
|
676
841
|
if (normalized === "1" || normalized === "claude") {
|
|
677
842
|
return ["claude"];
|
|
@@ -679,10 +844,13 @@ async function promptHostSelection() {
|
|
|
679
844
|
if (normalized === "2" || normalized === "codex") {
|
|
680
845
|
return ["codex"];
|
|
681
846
|
}
|
|
682
|
-
if (normalized === "3" || normalized === "
|
|
683
|
-
return ["
|
|
847
|
+
if (normalized === "3" || normalized === "opencode") {
|
|
848
|
+
return ["opencode"];
|
|
684
849
|
}
|
|
685
|
-
|
|
850
|
+
if (normalized === "4" || normalized === "all") {
|
|
851
|
+
return ["claude", "codex", "opencode"];
|
|
852
|
+
}
|
|
853
|
+
output.write("Invalid selection. Choose 1, 2, 3, or 4.\n");
|
|
686
854
|
}
|
|
687
855
|
} finally {
|
|
688
856
|
rl.close();
|
|
@@ -714,7 +882,7 @@ async function promptUninstallPlan(hosts) {
|
|
|
714
882
|
return { proceed: false, purge: false };
|
|
715
883
|
}
|
|
716
884
|
const purge = await promptConfirm(
|
|
717
|
-
"Also purge Lineup data (~/.claude/lineup/agents, ~/.codex/lineup/agents, ~/.codex/lineup/memory)?",
|
|
885
|
+
"Also purge Lineup data (~/.claude/lineup/agents, ~/.codex/lineup/agents, ~/.codex/lineup/memory, ~/.config/opencode/lineup)?",
|
|
718
886
|
false
|
|
719
887
|
);
|
|
720
888
|
return { proceed: true, purge };
|
|
@@ -723,16 +891,16 @@ async function promptUninstallPlan(hosts) {
|
|
|
723
891
|
// src/lib/release.ts
|
|
724
892
|
import { createHash } from "crypto";
|
|
725
893
|
import {
|
|
726
|
-
existsSync as
|
|
727
|
-
mkdirSync as
|
|
894
|
+
existsSync as existsSync6,
|
|
895
|
+
mkdirSync as mkdirSync5,
|
|
728
896
|
readdirSync as readdirSync2,
|
|
729
897
|
readFileSync as readFileSync4,
|
|
730
|
-
renameSync as
|
|
731
|
-
rmSync as
|
|
898
|
+
renameSync as renameSync3,
|
|
899
|
+
rmSync as rmSync3,
|
|
732
900
|
writeFileSync as writeFileSync3
|
|
733
901
|
} from "fs";
|
|
734
902
|
import { readFile } from "fs/promises";
|
|
735
|
-
import
|
|
903
|
+
import path7 from "path";
|
|
736
904
|
var OWNER = "izantech";
|
|
737
905
|
var REPO = "lineup";
|
|
738
906
|
var API_BASE = `https://api.github.com/repos/${OWNER}/${REPO}`;
|
|
@@ -784,13 +952,13 @@ async function downloadBinary(url) {
|
|
|
784
952
|
return Buffer.from(arrayBuffer);
|
|
785
953
|
}
|
|
786
954
|
function cachePaths(tag) {
|
|
787
|
-
const cacheDir =
|
|
955
|
+
const cacheDir = path7.join(lineupCacheDir(), tag);
|
|
788
956
|
return {
|
|
789
957
|
cacheDir,
|
|
790
|
-
manifestPath:
|
|
791
|
-
tarballPath:
|
|
792
|
-
extractDir:
|
|
793
|
-
sourceRoot:
|
|
958
|
+
manifestPath: path7.join(cacheDir, "manifest.json"),
|
|
959
|
+
tarballPath: path7.join(cacheDir, "release.tar.gz"),
|
|
960
|
+
extractDir: path7.join(cacheDir, "extracted"),
|
|
961
|
+
sourceRoot: path7.join(cacheDir, "source")
|
|
794
962
|
};
|
|
795
963
|
}
|
|
796
964
|
function sha256File(filePath) {
|
|
@@ -842,15 +1010,29 @@ async function fetchReleaseManifest(tag) {
|
|
|
842
1010
|
code: "release_manifest_missing"
|
|
843
1011
|
});
|
|
844
1012
|
}
|
|
845
|
-
function validateExtractedSource(sourceRoot) {
|
|
846
|
-
const
|
|
1013
|
+
function validateExtractedSource(sourceRoot, hosts) {
|
|
1014
|
+
const coreRequired = [
|
|
847
1015
|
".lineup-core/skills/kick-off/core.md",
|
|
848
|
-
".lineup-core/hosts/claude.json",
|
|
849
|
-
".lineup-core/hosts/codex.json",
|
|
850
1016
|
"agents/researcher.md",
|
|
851
1017
|
"templates/tactic.yaml"
|
|
852
1018
|
];
|
|
853
|
-
const
|
|
1019
|
+
const hostAdapterFiles = {
|
|
1020
|
+
claude: ".lineup-core/hosts/claude.json",
|
|
1021
|
+
codex: ".lineup-core/hosts/codex.json",
|
|
1022
|
+
opencode: ".lineup-core/hosts/opencode.json"
|
|
1023
|
+
};
|
|
1024
|
+
const required = [...coreRequired];
|
|
1025
|
+
if (hosts && hosts.length > 0) {
|
|
1026
|
+
for (const host of hosts) {
|
|
1027
|
+
const adapterFile = hostAdapterFiles[host];
|
|
1028
|
+
if (adapterFile) {
|
|
1029
|
+
required.push(adapterFile);
|
|
1030
|
+
}
|
|
1031
|
+
}
|
|
1032
|
+
} else {
|
|
1033
|
+
required.push(...Object.values(hostAdapterFiles));
|
|
1034
|
+
}
|
|
1035
|
+
const missing = required.filter((item) => !existsSync6(path7.join(sourceRoot, item)));
|
|
854
1036
|
if (missing.length > 0) {
|
|
855
1037
|
throw new CliError(`Release source missing required files:
|
|
856
1038
|
${missing.map((item) => `- ${item}`).join("\n")}`, {
|
|
@@ -865,16 +1047,16 @@ function chooseExtractedRoot(extractDir) {
|
|
|
865
1047
|
code: "extract_failed"
|
|
866
1048
|
});
|
|
867
1049
|
}
|
|
868
|
-
return
|
|
1050
|
+
return path7.join(extractDir, dirs[0].name);
|
|
869
1051
|
}
|
|
870
1052
|
async function extractTarball(tarballPath, extractDir) {
|
|
871
|
-
|
|
872
|
-
|
|
1053
|
+
rmSync3(extractDir, { recursive: true, force: true });
|
|
1054
|
+
mkdirSync5(extractDir, { recursive: true });
|
|
873
1055
|
const result = await runCommand("tar", ["-xzf", tarballPath, "-C", extractDir]);
|
|
874
1056
|
assertSuccess(result, `tar -xzf ${tarballPath}`);
|
|
875
1057
|
}
|
|
876
1058
|
function loadCachedManifest(manifestPath) {
|
|
877
|
-
if (!
|
|
1059
|
+
if (!existsSync6(manifestPath)) {
|
|
878
1060
|
return null;
|
|
879
1061
|
}
|
|
880
1062
|
try {
|
|
@@ -887,10 +1069,10 @@ function loadCachedManifest(manifestPath) {
|
|
|
887
1069
|
async function resolveRelease(input2 = {}) {
|
|
888
1070
|
const tag = input2.version && input2.version !== "latest" ? input2.version : await resolveLatestTag();
|
|
889
1071
|
const { cacheDir, manifestPath, tarballPath, extractDir, sourceRoot } = cachePaths(tag);
|
|
890
|
-
|
|
1072
|
+
mkdirSync5(cacheDir, { recursive: true });
|
|
891
1073
|
const cachedManifest = loadCachedManifest(manifestPath);
|
|
892
|
-
if (cachedManifest &&
|
|
893
|
-
validateExtractedSource(sourceRoot);
|
|
1074
|
+
if (cachedManifest && existsSync6(sourceRoot)) {
|
|
1075
|
+
validateExtractedSource(sourceRoot, input2.hosts);
|
|
894
1076
|
return {
|
|
895
1077
|
tag,
|
|
896
1078
|
sourceRoot,
|
|
@@ -905,17 +1087,17 @@ async function resolveRelease(input2 = {}) {
|
|
|
905
1087
|
writeFileSync3(tarballPath, tarball);
|
|
906
1088
|
const digest = sha256File(tarballPath);
|
|
907
1089
|
if (digest.toLowerCase() !== manifest.sha256.toLowerCase()) {
|
|
908
|
-
|
|
1090
|
+
rmSync3(tarballPath, { force: true });
|
|
909
1091
|
throw new CliError(`Checksum mismatch for ${tag}. expected=${manifest.sha256} actual=${digest}`, {
|
|
910
1092
|
code: "checksum_mismatch"
|
|
911
1093
|
});
|
|
912
1094
|
}
|
|
913
1095
|
await extractTarball(tarballPath, extractDir);
|
|
914
1096
|
const extractedRoot = chooseExtractedRoot(extractDir);
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
validateExtractedSource(sourceRoot);
|
|
1097
|
+
rmSync3(sourceRoot, { recursive: true, force: true });
|
|
1098
|
+
renameSync3(extractedRoot, sourceRoot);
|
|
1099
|
+
rmSync3(extractDir, { recursive: true, force: true });
|
|
1100
|
+
validateExtractedSource(sourceRoot, input2.hosts);
|
|
919
1101
|
return {
|
|
920
1102
|
tag,
|
|
921
1103
|
sourceRoot,
|
|
@@ -923,17 +1105,17 @@ async function resolveRelease(input2 = {}) {
|
|
|
923
1105
|
manifest
|
|
924
1106
|
};
|
|
925
1107
|
}
|
|
926
|
-
function resolveLocalRelease(dirPath) {
|
|
927
|
-
const resolvedPath =
|
|
928
|
-
if (!
|
|
1108
|
+
function resolveLocalRelease(dirPath, hosts) {
|
|
1109
|
+
const resolvedPath = path7.resolve(dirPath);
|
|
1110
|
+
if (!existsSync6(resolvedPath)) {
|
|
929
1111
|
throw new CliError(`Local directory does not exist: ${resolvedPath}`, {
|
|
930
1112
|
code: "local_dir_not_found"
|
|
931
1113
|
});
|
|
932
1114
|
}
|
|
933
|
-
validateExtractedSource(resolvedPath);
|
|
1115
|
+
validateExtractedSource(resolvedPath, hosts);
|
|
934
1116
|
let tag = "local";
|
|
935
1117
|
try {
|
|
936
|
-
const pkgPath =
|
|
1118
|
+
const pkgPath = path7.join(resolvedPath, "cli", "package.json");
|
|
937
1119
|
const parsed = JSON.parse(readFileSync4(pkgPath, "utf8"));
|
|
938
1120
|
if (parsed.version) {
|
|
939
1121
|
tag = parsed.version;
|
|
@@ -953,8 +1135,8 @@ function resolveLocalRelease(dirPath) {
|
|
|
953
1135
|
}
|
|
954
1136
|
|
|
955
1137
|
// src/lib/state.ts
|
|
956
|
-
import { existsSync as
|
|
957
|
-
import
|
|
1138
|
+
import { existsSync as existsSync7, mkdirSync as mkdirSync6, readFileSync as readFileSync5, writeFileSync as writeFileSync4 } from "fs";
|
|
1139
|
+
import path8 from "path";
|
|
958
1140
|
var STATE_SCHEMA_VERSION = 1;
|
|
959
1141
|
function defaultState() {
|
|
960
1142
|
return {
|
|
@@ -964,7 +1146,7 @@ function defaultState() {
|
|
|
964
1146
|
};
|
|
965
1147
|
}
|
|
966
1148
|
function loadState(filePath = lineupStateFile()) {
|
|
967
|
-
if (!
|
|
1149
|
+
if (!existsSync7(filePath)) {
|
|
968
1150
|
return defaultState();
|
|
969
1151
|
}
|
|
970
1152
|
try {
|
|
@@ -982,7 +1164,7 @@ function saveState(state, filePath = lineupStateFile()) {
|
|
|
982
1164
|
hosts: state.hosts
|
|
983
1165
|
};
|
|
984
1166
|
const valid = validateInstallerState(payload, filePath);
|
|
985
|
-
|
|
1167
|
+
mkdirSync6(path8.dirname(filePath), { recursive: true });
|
|
986
1168
|
writeFileSync4(filePath, `${JSON.stringify(valid, null, 2)}
|
|
987
1169
|
`, "utf8");
|
|
988
1170
|
return valid;
|
|
@@ -1016,6 +1198,11 @@ var defaultDeps = {
|
|
|
1016
1198
|
statusCodex,
|
|
1017
1199
|
uninstallCodex,
|
|
1018
1200
|
codexHostRoot,
|
|
1201
|
+
installOpencode,
|
|
1202
|
+
statusOpencode,
|
|
1203
|
+
uninstallOpencode,
|
|
1204
|
+
opencodeHostRoot,
|
|
1205
|
+
homeDir: os2.homedir,
|
|
1019
1206
|
lineupStateFile,
|
|
1020
1207
|
purgeTargets,
|
|
1021
1208
|
isInteractive,
|
|
@@ -1025,7 +1212,7 @@ var defaultDeps = {
|
|
|
1025
1212
|
saveState,
|
|
1026
1213
|
updateHostState,
|
|
1027
1214
|
removePath: (target) => {
|
|
1028
|
-
|
|
1215
|
+
rmSync4(target, { recursive: true, force: true });
|
|
1029
1216
|
},
|
|
1030
1217
|
asErrorMessage
|
|
1031
1218
|
};
|
|
@@ -1078,7 +1265,7 @@ function createOperations(overrides = {}) {
|
|
|
1078
1265
|
return true;
|
|
1079
1266
|
}
|
|
1080
1267
|
async function performInstallOrUpdate2(input2) {
|
|
1081
|
-
const release = input2.fromDir ? deps.resolveLocalRelease(input2.fromDir) : await deps.resolveRelease({ version: input2.version });
|
|
1268
|
+
const release = input2.fromDir ? deps.resolveLocalRelease(input2.fromDir, input2.hosts) : await deps.resolveRelease({ version: input2.version, hosts: input2.hosts });
|
|
1082
1269
|
deps.validateSourceBundle(release.sourceRoot);
|
|
1083
1270
|
const state = deps.loadState();
|
|
1084
1271
|
const failures = [];
|
|
@@ -1127,6 +1314,21 @@ function createOperations(overrides = {}) {
|
|
|
1127
1314
|
message: `Codex ${input2.action} complete (${release.tag}).`
|
|
1128
1315
|
});
|
|
1129
1316
|
}
|
|
1317
|
+
if (host === "opencode") {
|
|
1318
|
+
const opencodeResult = deps.installOpencode(release.sourceRoot, deps.homeDir());
|
|
1319
|
+
deps.updateHostState(state, "opencode", {
|
|
1320
|
+
installed: true,
|
|
1321
|
+
version: release.tag,
|
|
1322
|
+
source: "cli-managed",
|
|
1323
|
+
skills_dir: opencodeResult.skills_dir,
|
|
1324
|
+
last_action: input2.action
|
|
1325
|
+
});
|
|
1326
|
+
results.push({
|
|
1327
|
+
host,
|
|
1328
|
+
ok: true,
|
|
1329
|
+
message: `OpenCode ${input2.action} complete (${release.tag}).`
|
|
1330
|
+
});
|
|
1331
|
+
}
|
|
1130
1332
|
} catch (error) {
|
|
1131
1333
|
const message = deps.asErrorMessage(error);
|
|
1132
1334
|
failures.push({ host, error: message });
|
|
@@ -1200,6 +1402,21 @@ function createOperations(overrides = {}) {
|
|
|
1200
1402
|
message: "Codex uninstall complete."
|
|
1201
1403
|
});
|
|
1202
1404
|
}
|
|
1405
|
+
if (host === "opencode") {
|
|
1406
|
+
deps.uninstallOpencode(deps.homeDir());
|
|
1407
|
+
deps.updateHostState(state, "opencode", {
|
|
1408
|
+
installed: false,
|
|
1409
|
+
version: null,
|
|
1410
|
+
source: null,
|
|
1411
|
+
skills_dir: null,
|
|
1412
|
+
last_action: "uninstall"
|
|
1413
|
+
});
|
|
1414
|
+
results.push({
|
|
1415
|
+
host,
|
|
1416
|
+
ok: true,
|
|
1417
|
+
message: "OpenCode uninstall complete."
|
|
1418
|
+
});
|
|
1419
|
+
}
|
|
1203
1420
|
} catch (error) {
|
|
1204
1421
|
const message = deps.asErrorMessage(error);
|
|
1205
1422
|
failures.push({ host, error: message });
|
|
@@ -1251,6 +1468,17 @@ function createOperations(overrides = {}) {
|
|
|
1251
1468
|
} : void 0;
|
|
1252
1469
|
outputHosts.codex = mergeStatus(stateHost, runtime);
|
|
1253
1470
|
}
|
|
1471
|
+
if (host === "opencode") {
|
|
1472
|
+
const runtime = deps.statusOpencode(deps.homeDir());
|
|
1473
|
+
const stateHost = state.hosts.opencode ? {
|
|
1474
|
+
host: "opencode",
|
|
1475
|
+
installed: state.hosts.opencode.installed,
|
|
1476
|
+
version: state.hosts.opencode.version ?? null,
|
|
1477
|
+
source: state.hosts.opencode.source ?? null,
|
|
1478
|
+
last_action: state.hosts.opencode.last_action
|
|
1479
|
+
} : void 0;
|
|
1480
|
+
outputHosts.opencode = mergeStatus(stateHost, runtime);
|
|
1481
|
+
}
|
|
1254
1482
|
}
|
|
1255
1483
|
return {
|
|
1256
1484
|
schema_version: state.schema_version,
|
|
@@ -1271,13 +1499,14 @@ var readStatus = operations.readStatus;
|
|
|
1271
1499
|
|
|
1272
1500
|
// src/lib/hosts.ts
|
|
1273
1501
|
var HOST_SET = /* @__PURE__ */ new Set([...SUPPORTED_HOSTS, "all"]);
|
|
1502
|
+
var HOST_OPTIONS = [...SUPPORTED_HOSTS, "all"];
|
|
1274
1503
|
function normalizeHostOption(raw) {
|
|
1275
1504
|
if (!raw) {
|
|
1276
1505
|
return null;
|
|
1277
1506
|
}
|
|
1278
1507
|
const normalized = raw.trim().toLowerCase();
|
|
1279
1508
|
if (!HOST_SET.has(normalized)) {
|
|
1280
|
-
throw new CliError(`Invalid --host value: ${raw}. Expected
|
|
1509
|
+
throw new CliError(`Invalid --host value: ${raw}. Expected ${HOST_OPTIONS.join(", ")}.`, {
|
|
1281
1510
|
code: "invalid_host"
|
|
1282
1511
|
});
|
|
1283
1512
|
}
|
|
@@ -1298,7 +1527,7 @@ async function resolveRequestedHosts(rawHost, options) {
|
|
|
1298
1527
|
if (interactive) {
|
|
1299
1528
|
return (options?.prompt ?? promptHostSelection)();
|
|
1300
1529
|
}
|
|
1301
|
-
throw new CliError(
|
|
1530
|
+
throw new CliError(`No host selected. Use --host ${HOST_OPTIONS.join("|")} when running non-interactively.`, {
|
|
1302
1531
|
code: "host_required"
|
|
1303
1532
|
});
|
|
1304
1533
|
}
|
|
@@ -1388,7 +1617,7 @@ async function runUpdateCommand(options) {
|
|
|
1388
1617
|
|
|
1389
1618
|
// src/cli.ts
|
|
1390
1619
|
function packageVersion() {
|
|
1391
|
-
const packageJsonPath =
|
|
1620
|
+
const packageJsonPath = path9.join(packageRoot(), "package.json");
|
|
1392
1621
|
const raw = readFileSync6(packageJsonPath, "utf8");
|
|
1393
1622
|
const parsed = JSON.parse(raw);
|
|
1394
1623
|
return parsed.version ?? "0.0.0";
|
|
@@ -1402,11 +1631,11 @@ function buildProgram(handlers) {
|
|
|
1402
1631
|
...handlers
|
|
1403
1632
|
};
|
|
1404
1633
|
const program = new Command();
|
|
1405
|
-
program.name("lineup").description("Lineup multi-host manager for Claude Code and
|
|
1406
|
-
program.command("install").description("Install Lineup for selected host(s)").option("--host <host>", "Target host(s): claude|codex|all").option("--version <tag>", "Release tag to install", "latest").option("--from-dir <path>", "Install from local directory instead of GitHub release").option("--yes", "Auto-confirm prompts").action(commandHandlers.install);
|
|
1407
|
-
program.command("update").description("Update Lineup for selected host(s)").option("--host <host>", "Target host(s): claude|codex|all").option("--version <tag>", "Release tag to install", "latest").option("--from-dir <path>", "Install from local directory instead of GitHub release").option("--yes", "Auto-confirm prompts").action(commandHandlers.update);
|
|
1408
|
-
program.command("uninstall").description("Uninstall Lineup for selected host(s)").option("--host <host>", "Target host(s): claude|codex|all").option("--yes", "Auto-confirm prompts").option("--purge", "Purge Lineup data directories").action(commandHandlers.uninstall);
|
|
1409
|
-
program.command("status").description("Show Lineup installation status").option("--host <host>", "Target host(s): claude|codex|all").option("--json", "Emit machine-readable JSON output").action(commandHandlers.status);
|
|
1634
|
+
program.name("lineup").description("Lineup multi-host manager for Claude Code, Codex, and OpenCode").version(packageVersion(), "--cli-version", "output CLI version").showHelpAfterError();
|
|
1635
|
+
program.command("install").description("Install Lineup for selected host(s)").option("--host <host>", "Target host(s): claude|codex|opencode|all").option("--version <tag>", "Release tag to install", "latest").option("--from-dir <path>", "Install from local directory instead of GitHub release").option("--yes", "Auto-confirm prompts").action(commandHandlers.install);
|
|
1636
|
+
program.command("update").description("Update Lineup for selected host(s)").option("--host <host>", "Target host(s): claude|codex|opencode|all").option("--version <tag>", "Release tag to install", "latest").option("--from-dir <path>", "Install from local directory instead of GitHub release").option("--yes", "Auto-confirm prompts").action(commandHandlers.update);
|
|
1637
|
+
program.command("uninstall").description("Uninstall Lineup for selected host(s)").option("--host <host>", "Target host(s): claude|codex|opencode|all").option("--yes", "Auto-confirm prompts").option("--purge", "Purge Lineup data directories").action(commandHandlers.uninstall);
|
|
1638
|
+
program.command("status").description("Show Lineup installation status").option("--host <host>", "Target host(s): claude|codex|opencode|all").option("--json", "Emit machine-readable JSON output").action(commandHandlers.status);
|
|
1410
1639
|
return program;
|
|
1411
1640
|
}
|
|
1412
1641
|
function printCliError(error) {
|
|
@@ -1433,7 +1662,7 @@ function isDirectExecution(argv) {
|
|
|
1433
1662
|
if (!entry) {
|
|
1434
1663
|
return false;
|
|
1435
1664
|
}
|
|
1436
|
-
return
|
|
1665
|
+
return path9.resolve(entry) === path9.resolve(packageRoot(), "dist", "cli.js");
|
|
1437
1666
|
}
|
|
1438
1667
|
if (isDirectExecution(process2.argv)) {
|
|
1439
1668
|
run().catch(handleFatalError);
|
package/dist/cli.js
CHANGED
package/dist/index.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
export { CliHandlers, buildProgram, printCliError, resolveExitCode, run } from './cli.js';
|
|
2
2
|
import 'commander';
|
|
3
3
|
|
|
4
|
-
declare const SUPPORTED_HOSTS: readonly ["claude", "codex"];
|
|
4
|
+
declare const SUPPORTED_HOSTS: readonly ["claude", "codex", "opencode"];
|
|
5
5
|
type HostName = (typeof SUPPORTED_HOSTS)[number];
|
|
6
6
|
|
|
7
7
|
type HostState = {
|
package/dist/index.js
CHANGED
package/package.json
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
"properties": {
|
|
9
9
|
"host": {
|
|
10
10
|
"type": "string",
|
|
11
|
-
"enum": ["claude", "codex", "
|
|
11
|
+
"enum": ["claude", "codex", "opencode"]
|
|
12
12
|
},
|
|
13
13
|
"vars": {
|
|
14
14
|
"type": "object",
|
|
@@ -18,10 +18,12 @@
|
|
|
18
18
|
"SKILL_NAME_CONFIGURE",
|
|
19
19
|
"SKILL_NAME_EXPLAIN",
|
|
20
20
|
"SKILL_NAME_PLAYBOOK",
|
|
21
|
+
"SKILL_NAME_DIGEST",
|
|
21
22
|
"CMD_KICKOFF",
|
|
22
23
|
"CMD_CONFIGURE",
|
|
23
24
|
"CMD_EXPLAIN",
|
|
24
25
|
"CMD_PLAYBOOK",
|
|
26
|
+
"CMD_DIGEST",
|
|
25
27
|
"KICKOFF_INIT_PATH",
|
|
26
28
|
"QUESTION_PRIMITIVE",
|
|
27
29
|
"OVERRIDES_DIR",
|
|
@@ -32,17 +34,20 @@
|
|
|
32
34
|
"HOST_DEFAULTS_TERM",
|
|
33
35
|
"HOST_ARTIFACT_LABEL",
|
|
34
36
|
"HOST_ARTIFACT_LABEL_LOWER",
|
|
35
|
-
"AGENTS_DIR"
|
|
37
|
+
"AGENTS_DIR",
|
|
38
|
+
"OLLAMA_CONFIG_PATH"
|
|
36
39
|
],
|
|
37
40
|
"properties": {
|
|
38
41
|
"SKILL_NAME_KICKOFF": { "type": "string" },
|
|
39
42
|
"SKILL_NAME_CONFIGURE": { "type": "string" },
|
|
40
43
|
"SKILL_NAME_EXPLAIN": { "type": "string" },
|
|
41
44
|
"SKILL_NAME_PLAYBOOK": { "type": "string" },
|
|
45
|
+
"SKILL_NAME_DIGEST": { "type": "string" },
|
|
42
46
|
"CMD_KICKOFF": { "type": "string" },
|
|
43
47
|
"CMD_CONFIGURE": { "type": "string" },
|
|
44
48
|
"CMD_EXPLAIN": { "type": "string" },
|
|
45
49
|
"CMD_PLAYBOOK": { "type": "string" },
|
|
50
|
+
"CMD_DIGEST": { "type": "string" },
|
|
46
51
|
"KICKOFF_INIT_PATH": { "type": "string" },
|
|
47
52
|
"QUESTION_PRIMITIVE": { "type": "string" },
|
|
48
53
|
"OVERRIDES_DIR": { "type": "string" },
|
|
@@ -53,7 +58,8 @@
|
|
|
53
58
|
"HOST_DEFAULTS_TERM": { "type": "string" },
|
|
54
59
|
"HOST_ARTIFACT_LABEL": { "type": "string" },
|
|
55
60
|
"HOST_ARTIFACT_LABEL_LOWER": { "type": "string" },
|
|
56
|
-
"AGENTS_DIR": { "type": "string" }
|
|
61
|
+
"AGENTS_DIR": { "type": "string" },
|
|
62
|
+
"OLLAMA_CONFIG_PATH": { "type": "string" }
|
|
57
63
|
}
|
|
58
64
|
}
|
|
59
65
|
}
|
|
@@ -20,8 +20,12 @@
|
|
|
20
20
|
"items": {
|
|
21
21
|
"type": "object",
|
|
22
22
|
"additionalProperties": false,
|
|
23
|
-
"required": ["type", "agent"],
|
|
24
23
|
"properties": {
|
|
24
|
+
"tactic": {
|
|
25
|
+
"type": "string",
|
|
26
|
+
"pattern": "^[a-z0-9]+(?:-[a-z0-9]+)*$",
|
|
27
|
+
"description": "Name of another tactic to inline at this position"
|
|
28
|
+
},
|
|
25
29
|
"type": {
|
|
26
30
|
"type": "string",
|
|
27
31
|
"enum": [
|
|
@@ -49,7 +53,15 @@
|
|
|
49
53
|
"type": ["string", "null"],
|
|
50
54
|
"enum": ["approval", null]
|
|
51
55
|
}
|
|
52
|
-
}
|
|
56
|
+
},
|
|
57
|
+
"if": { "properties": { "tactic": true }, "required": ["tactic"] },
|
|
58
|
+
"then": {
|
|
59
|
+
"allOf": [
|
|
60
|
+
{ "not": { "properties": { "type": true }, "required": ["type"] } },
|
|
61
|
+
{ "not": { "properties": { "agent": true }, "required": ["agent"] } }
|
|
62
|
+
]
|
|
63
|
+
},
|
|
64
|
+
"else": { "properties": { "type": true, "agent": true }, "required": ["type", "agent"] }
|
|
53
65
|
}
|
|
54
66
|
},
|
|
55
67
|
"verification": {
|