@bvdm/delano 0.2.0 → 0.2.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/.delano/viewer/public/explorer.svg +2 -2
- package/.delano/viewer/public/index.html +21 -21
- package/.delano/viewer/public/markdown.svg +5 -5
- package/.delano/viewer/public/styles.css +1042 -1042
- package/.delano/viewer/public/vscode.svg +23 -23
- package/LICENSE +21 -0
- package/README.md +68 -5
- package/assets/payload/.agents/hooks/bash-worktree-fix.sh +0 -0
- package/assets/payload/.agents/hooks/post-tool-logger.js +0 -0
- package/assets/payload/.agents/hooks/session-tracker.js +0 -0
- package/assets/payload/.agents/hooks/user-prompt-logger.js +0 -0
- package/assets/payload/.agents/scripts/check-log-safety.sh +0 -0
- package/assets/payload/.agents/scripts/check-path-standards.sh +0 -0
- package/assets/payload/.agents/scripts/fix-path-standards.sh +0 -0
- package/assets/payload/.agents/scripts/git-sparse-download.sh +0 -0
- package/assets/payload/.agents/scripts/log-event.js +0 -0
- package/assets/payload/.agents/scripts/log-event.sh +0 -0
- package/assets/payload/.agents/scripts/pm/blocked.sh +0 -0
- package/assets/payload/.agents/scripts/pm/epic-list.sh +0 -0
- package/assets/payload/.agents/scripts/pm/in-progress.sh +0 -0
- package/assets/payload/.agents/scripts/pm/init.sh +0 -0
- package/assets/payload/.agents/scripts/pm/next.sh +0 -0
- package/assets/payload/.agents/scripts/pm/prd-list.sh +0 -0
- package/assets/payload/.agents/scripts/pm/search.sh +0 -0
- package/assets/payload/.agents/scripts/pm/standup.sh +0 -0
- package/assets/payload/.agents/scripts/pm/status.sh +0 -0
- package/assets/payload/.agents/scripts/pm/validate.sh +0 -0
- package/assets/payload/.agents/scripts/query-log.sh +0 -0
- package/assets/payload/.agents/scripts/test-and-log.sh +0 -0
- package/assets/payload/.delano/viewer/public/explorer.svg +2 -2
- package/assets/payload/.delano/viewer/public/index.html +21 -21
- package/assets/payload/.delano/viewer/public/markdown.svg +5 -5
- package/assets/payload/.delano/viewer/public/styles.css +1042 -1042
- package/assets/payload/.delano/viewer/public/vscode.svg +23 -23
- package/assets/payload/install-delano.sh +0 -0
- package/install-delano.sh +0 -0
- package/package.json +10 -2
- package/src/cli/commands/install.js +21 -1
- package/src/cli/lib/install.js +389 -8
|
@@ -1,24 +1,24 @@
|
|
|
1
|
-
<?xml version="1.0" encoding="utf-8"?><!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
|
|
2
|
-
<svg width="800px" height="800px" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
3
|
-
<path d="M21.0016 3.11679C21.0016 2.23783 20.0175 2.23782 19.5801 2.34769C20.1924 1.86426 20.9105 1.98147 21.1656 2.12796L27.079 5.02747C27.6424 5.30375 27.9998 5.8786 27.9998 6.50857V25.5831C27.9998 26.2215 27.6329 26.8025 27.058 27.0743L21.4937 29.7054C21.1109 29.8701 20.2799 30.2767 19.5801 29.7053C20.4549 29.8702 20.9287 29.2476 21.0016 28.8264V3.11679Z" fill="url(#paint0_linear_87_8101)"/>
|
|
4
|
-
<path d="M19.6512 2.3319C20.1154 2.24017 21.0018 2.28271 21.0018 3.11685V9.68254L3.07359 23.2453C2.76022 23.4824 2.3192 23.443 2.05229 23.1542L0.204532 21.1548C-0.0849358 20.8416 -0.0646824 20.3513 0.249624 20.0633L19.5802 2.34775L19.6512 2.3319Z" fill="url(#paint1_linear_87_8101)"/>
|
|
5
|
-
<path d="M21.0018 22.3708L3.07359 8.80801C2.76022 8.57094 2.3192 8.61028 2.05229 8.8991L0.204532 10.8985C-0.0849358 11.2117 -0.0646824 11.702 0.249624 11.9901L19.5802 29.7056C20.455 29.8704 20.9289 29.2478 21.0018 28.8266V22.3708Z" fill="url(#paint2_linear_87_8101)"/>
|
|
6
|
-
<defs>
|
|
7
|
-
<linearGradient id="paint0_linear_87_8101" x1="23.79" y1="2" x2="23.79" y2="30" gradientUnits="userSpaceOnUse">
|
|
8
|
-
<stop stop-color="#32B5F1"/>
|
|
9
|
-
<stop offset="1" stop-color="#2B9FED"/>
|
|
10
|
-
</linearGradient>
|
|
11
|
-
<linearGradient id="paint1_linear_87_8101" x1="21.0018" y1="5.53398" x2="1.0217" y2="22.3051" gradientUnits="userSpaceOnUse">
|
|
12
|
-
<stop stop-color="#0F6FB3"/>
|
|
13
|
-
<stop offset="0.270551" stop-color="#1279B7"/>
|
|
14
|
-
<stop offset="0.421376" stop-color="#1176B5"/>
|
|
15
|
-
<stop offset="0.618197" stop-color="#0E69AC"/>
|
|
16
|
-
<stop offset="0.855344" stop-color="#0F70AF"/>
|
|
17
|
-
<stop offset="1" stop-color="#0F6DAD"/>
|
|
18
|
-
</linearGradient>
|
|
19
|
-
<linearGradient id="paint2_linear_87_8101" x1="1.15522" y1="9.98389" x2="21.0791" y2="26.4808" gradientUnits="userSpaceOnUse">
|
|
20
|
-
<stop stop-color="#1791D2"/>
|
|
21
|
-
<stop offset="1" stop-color="#1173C5"/>
|
|
22
|
-
</linearGradient>
|
|
23
|
-
</defs>
|
|
1
|
+
<?xml version="1.0" encoding="utf-8"?><!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
|
|
2
|
+
<svg width="800px" height="800px" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
3
|
+
<path d="M21.0016 3.11679C21.0016 2.23783 20.0175 2.23782 19.5801 2.34769C20.1924 1.86426 20.9105 1.98147 21.1656 2.12796L27.079 5.02747C27.6424 5.30375 27.9998 5.8786 27.9998 6.50857V25.5831C27.9998 26.2215 27.6329 26.8025 27.058 27.0743L21.4937 29.7054C21.1109 29.8701 20.2799 30.2767 19.5801 29.7053C20.4549 29.8702 20.9287 29.2476 21.0016 28.8264V3.11679Z" fill="url(#paint0_linear_87_8101)"/>
|
|
4
|
+
<path d="M19.6512 2.3319C20.1154 2.24017 21.0018 2.28271 21.0018 3.11685V9.68254L3.07359 23.2453C2.76022 23.4824 2.3192 23.443 2.05229 23.1542L0.204532 21.1548C-0.0849358 20.8416 -0.0646824 20.3513 0.249624 20.0633L19.5802 2.34775L19.6512 2.3319Z" fill="url(#paint1_linear_87_8101)"/>
|
|
5
|
+
<path d="M21.0018 22.3708L3.07359 8.80801C2.76022 8.57094 2.3192 8.61028 2.05229 8.8991L0.204532 10.8985C-0.0849358 11.2117 -0.0646824 11.702 0.249624 11.9901L19.5802 29.7056C20.455 29.8704 20.9289 29.2478 21.0018 28.8266V22.3708Z" fill="url(#paint2_linear_87_8101)"/>
|
|
6
|
+
<defs>
|
|
7
|
+
<linearGradient id="paint0_linear_87_8101" x1="23.79" y1="2" x2="23.79" y2="30" gradientUnits="userSpaceOnUse">
|
|
8
|
+
<stop stop-color="#32B5F1"/>
|
|
9
|
+
<stop offset="1" stop-color="#2B9FED"/>
|
|
10
|
+
</linearGradient>
|
|
11
|
+
<linearGradient id="paint1_linear_87_8101" x1="21.0018" y1="5.53398" x2="1.0217" y2="22.3051" gradientUnits="userSpaceOnUse">
|
|
12
|
+
<stop stop-color="#0F6FB3"/>
|
|
13
|
+
<stop offset="0.270551" stop-color="#1279B7"/>
|
|
14
|
+
<stop offset="0.421376" stop-color="#1176B5"/>
|
|
15
|
+
<stop offset="0.618197" stop-color="#0E69AC"/>
|
|
16
|
+
<stop offset="0.855344" stop-color="#0F70AF"/>
|
|
17
|
+
<stop offset="1" stop-color="#0F6DAD"/>
|
|
18
|
+
</linearGradient>
|
|
19
|
+
<linearGradient id="paint2_linear_87_8101" x1="1.15522" y1="9.98389" x2="21.0791" y2="26.4808" gradientUnits="userSpaceOnUse">
|
|
20
|
+
<stop stop-color="#1791D2"/>
|
|
21
|
+
<stop offset="1" stop-color="#1173C5"/>
|
|
22
|
+
</linearGradient>
|
|
23
|
+
</defs>
|
|
24
24
|
</svg>
|
|
File without changes
|
package/install-delano.sh
CHANGED
|
File without changes
|
package/package.json
CHANGED
|
@@ -1,8 +1,16 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bvdm/delano",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.2",
|
|
4
4
|
"description": "CLI for the Delano delivery runtime.",
|
|
5
|
-
"license": "
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "https://github.com/MajesteitBart/delano"
|
|
9
|
+
},
|
|
10
|
+
"bugs": {
|
|
11
|
+
"url": "https://github.com/MajesteitBart/delano/issues"
|
|
12
|
+
},
|
|
13
|
+
"homepage": "https://github.com/MajesteitBart/delano#readme",
|
|
6
14
|
"bin": {
|
|
7
15
|
"delano": "bin/delano.js"
|
|
8
16
|
},
|
|
@@ -2,6 +2,7 @@ const {
|
|
|
2
2
|
applyInstallPlan,
|
|
3
3
|
buildInstallPlan,
|
|
4
4
|
collectConflicts,
|
|
5
|
+
configureInteractiveInstall,
|
|
5
6
|
confirmInstall,
|
|
6
7
|
parseInstallArgs,
|
|
7
8
|
printConflicts,
|
|
@@ -17,24 +18,43 @@ function getInstallHelp() {
|
|
|
17
18
|
"Options:",
|
|
18
19
|
" --target <dir> Install into the given directory. Defaults to the current working directory.",
|
|
19
20
|
" --agents <list> Comma-separated agent list for future opt-in adapter docs: claude,codex,opencode,pi.",
|
|
21
|
+
" --only <list> Install only selected categories.",
|
|
22
|
+
" --exclude <list> Omit selected categories from the install plan.",
|
|
23
|
+
" --no-project-state Omit .project/context, .project/projects, and .project/registry.",
|
|
24
|
+
" --no-project-context",
|
|
25
|
+
" Omit .project/context starter templates.",
|
|
26
|
+
" --interactive Choose an install preset and target in the terminal.",
|
|
27
|
+
" --tui Alias for --interactive.",
|
|
20
28
|
" --force Overwrite existing allowlisted target paths. Does not override parent-path blockers.",
|
|
21
29
|
" --yes Skip the final confirmation prompt.",
|
|
22
30
|
" -h, --help Show command help.",
|
|
23
31
|
"",
|
|
32
|
+
"Categories:",
|
|
33
|
+
" agent-runtime, skills, viewer, project-context, project-templates,",
|
|
34
|
+
" project-registry, project-projects, handbook, legacy-installer",
|
|
35
|
+
"",
|
|
24
36
|
"Behavior:",
|
|
25
37
|
" - Computes the full install plan before writing files.",
|
|
26
38
|
" - Aborts on conflicts by default.",
|
|
39
|
+
" - Filters the plan before conflict detection when --only or --exclude is used.",
|
|
40
|
+
" - Treats .project/context, .project/projects, and .project/registry as repo-owned state after install.",
|
|
27
41
|
" - Only installs the approved base payload; top-level adapter entry docs remain opt-in and are not installed in v1.",
|
|
28
42
|
"",
|
|
29
43
|
"Examples:",
|
|
44
|
+
" delano install --interactive",
|
|
30
45
|
" delano install --target ../repo --yes",
|
|
46
|
+
" delano install --only skills,project-templates --force --yes",
|
|
47
|
+
" delano install --exclude project-context,project-projects,project-registry --force --yes",
|
|
31
48
|
" delano --yes",
|
|
32
49
|
" npx -y @bvdm/delano@latest --yes"
|
|
33
50
|
].join("\n");
|
|
34
51
|
}
|
|
35
52
|
|
|
36
53
|
async function runInstall(args) {
|
|
37
|
-
|
|
54
|
+
let options = parseInstallArgs(args);
|
|
55
|
+
if (options.interactive) {
|
|
56
|
+
options = await configureInteractiveInstall(options);
|
|
57
|
+
}
|
|
38
58
|
const plan = buildInstallPlan(options);
|
|
39
59
|
const conflicts = collectConflicts(plan);
|
|
40
60
|
const unforceableConflicts = conflicts.filter((conflict) => !conflict.forceable);
|
package/src/cli/lib/install.js
CHANGED
|
@@ -8,13 +8,110 @@ const {
|
|
|
8
8
|
statSync,
|
|
9
9
|
} = require("node:fs");
|
|
10
10
|
const path = require("node:path");
|
|
11
|
-
const readline = require("node:readline
|
|
11
|
+
const readline = require("node:readline");
|
|
12
12
|
const { stdin, stdout } = require("node:process");
|
|
13
13
|
|
|
14
14
|
const { CliError } = require("./errors");
|
|
15
15
|
const { getPackageRoot, getPathType } = require("./runtime");
|
|
16
16
|
|
|
17
17
|
const SUPPORTED_AGENTS = ["claude", "codex", "opencode", "pi"];
|
|
18
|
+
const INSTALL_CATEGORIES = [
|
|
19
|
+
{
|
|
20
|
+
name: "agent-runtime",
|
|
21
|
+
description: ".agents runtime except skills",
|
|
22
|
+
matches: (target) => target.startsWith(".agents/") && !target.startsWith(".agents/skills/")
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
name: "skills",
|
|
26
|
+
description: ".agents/skills",
|
|
27
|
+
matches: (target) => target.startsWith(".agents/skills/")
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
name: "viewer",
|
|
31
|
+
description: ".delano viewer files",
|
|
32
|
+
matches: (target) => target.startsWith(".delano/")
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
name: "project-context",
|
|
36
|
+
description: ".project/context starter templates",
|
|
37
|
+
matches: (target) => target.startsWith(".project/context/")
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
name: "project-templates",
|
|
41
|
+
description: ".project/templates",
|
|
42
|
+
matches: (target) => target.startsWith(".project/templates/")
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
name: "project-registry",
|
|
46
|
+
description: ".project/registry",
|
|
47
|
+
matches: (target) => target.startsWith(".project/registry/")
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
name: "project-projects",
|
|
51
|
+
description: ".project/projects seed files",
|
|
52
|
+
matches: (target) => target.startsWith(".project/projects/")
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
name: "handbook",
|
|
56
|
+
description: "HANDBOOK.md",
|
|
57
|
+
matches: (target) => target === "HANDBOOK.md"
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
name: "legacy-installer",
|
|
61
|
+
description: "install-delano.sh",
|
|
62
|
+
matches: (target) => target === "install-delano.sh"
|
|
63
|
+
}
|
|
64
|
+
];
|
|
65
|
+
|
|
66
|
+
const INSTALL_CATEGORY_ALIASES = new Map([
|
|
67
|
+
["agent-skills", "skills"],
|
|
68
|
+
["agents", "agent-runtime"],
|
|
69
|
+
["runtime", "agent-runtime"],
|
|
70
|
+
["context", "project-context"],
|
|
71
|
+
["templates", "project-templates"],
|
|
72
|
+
["project-state", "project-projects"],
|
|
73
|
+
["projects", "project-projects"],
|
|
74
|
+
["registry", "project-registry"],
|
|
75
|
+
["delano", "viewer"],
|
|
76
|
+
["installer", "legacy-installer"]
|
|
77
|
+
]);
|
|
78
|
+
|
|
79
|
+
const INSTALL_CATEGORY_NAMES = INSTALL_CATEGORIES.map((category) => category.name);
|
|
80
|
+
const PROJECT_STATE_CATEGORIES = ["project-context", "project-projects", "project-registry"];
|
|
81
|
+
const INSTALL_PRESETS = [
|
|
82
|
+
{
|
|
83
|
+
id: "update-safe",
|
|
84
|
+
label: "Update Delano runtime, preserve project state",
|
|
85
|
+
description: "Refresh runtime files while excluding .project/context, .project/projects, and .project/registry.",
|
|
86
|
+
only: null,
|
|
87
|
+
exclude: PROJECT_STATE_CATEGORIES,
|
|
88
|
+
force: true
|
|
89
|
+
},
|
|
90
|
+
{
|
|
91
|
+
id: "skills-templates",
|
|
92
|
+
label: "Update skills and project templates",
|
|
93
|
+
description: "Refresh .agents/skills and .project/templates only.",
|
|
94
|
+
only: ["skills", "project-templates"],
|
|
95
|
+
exclude: [],
|
|
96
|
+
force: true
|
|
97
|
+
},
|
|
98
|
+
{
|
|
99
|
+
id: "full",
|
|
100
|
+
label: "Full install or repair",
|
|
101
|
+
description: "Install every allowlisted category. This includes project starter state.",
|
|
102
|
+
only: null,
|
|
103
|
+
exclude: [],
|
|
104
|
+
force: false
|
|
105
|
+
},
|
|
106
|
+
{
|
|
107
|
+
id: "custom",
|
|
108
|
+
label: "Choose categories",
|
|
109
|
+
description: "Pick exact categories and force behavior.",
|
|
110
|
+
only: null,
|
|
111
|
+
exclude: [],
|
|
112
|
+
force: false
|
|
113
|
+
}
|
|
114
|
+
];
|
|
18
115
|
|
|
19
116
|
function getMissingPackagedAssetMessage(relativePath) {
|
|
20
117
|
return [
|
|
@@ -64,12 +161,47 @@ function parseAgentList(rawValue) {
|
|
|
64
161
|
return selected;
|
|
65
162
|
}
|
|
66
163
|
|
|
164
|
+
function parseCategoryList(rawValue, optionName) {
|
|
165
|
+
if (!rawValue) {
|
|
166
|
+
throw new CliError(`Missing value for ${optionName}.`, 1);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
const selected = [];
|
|
170
|
+
for (const chunk of rawValue.split(",")) {
|
|
171
|
+
const value = chunk.trim().toLowerCase();
|
|
172
|
+
if (!value) {
|
|
173
|
+
continue;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
const categoryName = INSTALL_CATEGORY_ALIASES.get(value) || value;
|
|
177
|
+
if (!INSTALL_CATEGORY_NAMES.includes(categoryName)) {
|
|
178
|
+
throw new CliError(
|
|
179
|
+
`Unknown install category '${value}'. Supported values: ${INSTALL_CATEGORY_NAMES.join(", ")}.`,
|
|
180
|
+
1
|
|
181
|
+
);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
if (!selected.includes(categoryName)) {
|
|
185
|
+
selected.push(categoryName);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
if (selected.length === 0) {
|
|
190
|
+
throw new CliError(`No install categories selected for ${optionName}.`, 1);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
return selected;
|
|
194
|
+
}
|
|
195
|
+
|
|
67
196
|
function parseInstallArgs(args) {
|
|
68
197
|
const options = {
|
|
69
198
|
target: process.cwd(),
|
|
70
199
|
force: false,
|
|
71
200
|
yes: false,
|
|
72
|
-
|
|
201
|
+
interactive: false,
|
|
202
|
+
agents: [...SUPPORTED_AGENTS],
|
|
203
|
+
only: null,
|
|
204
|
+
exclude: []
|
|
73
205
|
};
|
|
74
206
|
|
|
75
207
|
for (let index = 0; index < args.length; index += 1) {
|
|
@@ -103,6 +235,42 @@ function parseInstallArgs(args) {
|
|
|
103
235
|
continue;
|
|
104
236
|
}
|
|
105
237
|
|
|
238
|
+
if (arg === "--only") {
|
|
239
|
+
index += 1;
|
|
240
|
+
options.only = parseCategoryList(args[index], "--only");
|
|
241
|
+
continue;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
if (arg.startsWith("--only=")) {
|
|
245
|
+
options.only = parseCategoryList(arg.slice("--only=".length), "--only");
|
|
246
|
+
continue;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
if (arg === "--exclude") {
|
|
250
|
+
index += 1;
|
|
251
|
+
options.exclude = parseCategoryList(args[index], "--exclude");
|
|
252
|
+
continue;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
if (arg.startsWith("--exclude=")) {
|
|
256
|
+
options.exclude = parseCategoryList(arg.slice("--exclude=".length), "--exclude");
|
|
257
|
+
continue;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
if (arg === "--no-project-context") {
|
|
261
|
+
options.exclude = mergeCategoryLists(options.exclude, ["project-context"]);
|
|
262
|
+
continue;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
if (arg === "--no-project-state") {
|
|
266
|
+
options.exclude = mergeCategoryLists(options.exclude, [
|
|
267
|
+
"project-context",
|
|
268
|
+
"project-projects",
|
|
269
|
+
"project-registry"
|
|
270
|
+
]);
|
|
271
|
+
continue;
|
|
272
|
+
}
|
|
273
|
+
|
|
106
274
|
if (arg === "--force") {
|
|
107
275
|
options.force = true;
|
|
108
276
|
continue;
|
|
@@ -113,6 +281,11 @@ function parseInstallArgs(args) {
|
|
|
113
281
|
continue;
|
|
114
282
|
}
|
|
115
283
|
|
|
284
|
+
if (arg === "--interactive" || arg === "--tui") {
|
|
285
|
+
options.interactive = true;
|
|
286
|
+
continue;
|
|
287
|
+
}
|
|
288
|
+
|
|
116
289
|
throw new CliError(`Unknown install option: ${arg}`, 1);
|
|
117
290
|
}
|
|
118
291
|
|
|
@@ -120,9 +293,170 @@ function parseInstallArgs(args) {
|
|
|
120
293
|
return options;
|
|
121
294
|
}
|
|
122
295
|
|
|
296
|
+
function applyInstallPreset(options, presetId) {
|
|
297
|
+
const preset = INSTALL_PRESETS.find((candidate) => candidate.id === presetId);
|
|
298
|
+
if (!preset) {
|
|
299
|
+
throw new CliError(`Unknown install preset: ${presetId}`, 1);
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
return {
|
|
303
|
+
...options,
|
|
304
|
+
only: preset.only ? [...preset.only] : null,
|
|
305
|
+
exclude: [...preset.exclude],
|
|
306
|
+
force: preset.force
|
|
307
|
+
};
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
async function configureInteractiveInstall(options) {
|
|
311
|
+
const prompt = createPrompter(stdin, stdout);
|
|
312
|
+
try {
|
|
313
|
+
console.log("Delano install");
|
|
314
|
+
console.log("==============");
|
|
315
|
+
console.log("");
|
|
316
|
+
console.log("Choose what to install:");
|
|
317
|
+
INSTALL_PRESETS.forEach((preset, index) => {
|
|
318
|
+
console.log(` ${index + 1}. ${preset.label}`);
|
|
319
|
+
console.log(` ${preset.description}`);
|
|
320
|
+
});
|
|
321
|
+
console.log("");
|
|
322
|
+
|
|
323
|
+
const presetAnswer = await prompt.ask("Selection [1]: ");
|
|
324
|
+
const presetIndex = parseSelectionNumber(presetAnswer, 1, INSTALL_PRESETS.length, 1) - 1;
|
|
325
|
+
const preset = INSTALL_PRESETS[presetIndex];
|
|
326
|
+
let configured = applyInstallPreset(options, preset.id);
|
|
327
|
+
|
|
328
|
+
const targetAnswer = await prompt.ask(`Target [${configured.target}]: `);
|
|
329
|
+
if (targetAnswer.trim()) {
|
|
330
|
+
configured.target = path.resolve(targetAnswer.trim());
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
if (preset.id === "custom") {
|
|
334
|
+
configured = await configureCustomInstallSelection(configured, prompt);
|
|
335
|
+
} else {
|
|
336
|
+
const forceDefault = configured.force ? "Y/n" : "y/N";
|
|
337
|
+
const forceAnswer = await prompt.ask(`Overwrite selected existing files with --force? [${forceDefault}] `);
|
|
338
|
+
configured.force = parseYesNo(forceAnswer, configured.force);
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
return configured;
|
|
342
|
+
} finally {
|
|
343
|
+
prompt.close();
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
async function configureCustomInstallSelection(options, prompt) {
|
|
348
|
+
console.log("");
|
|
349
|
+
console.log("Categories:");
|
|
350
|
+
INSTALL_CATEGORIES.forEach((category, index) => {
|
|
351
|
+
console.log(` ${index + 1}. ${category.name} - ${category.description}`);
|
|
352
|
+
});
|
|
353
|
+
console.log("");
|
|
354
|
+
console.log("Enter category numbers or names separated by commas.");
|
|
355
|
+
console.log("Use 'all' for every category.");
|
|
356
|
+
|
|
357
|
+
const categoryAnswer = await prompt.ask("Categories [all]: ");
|
|
358
|
+
const only = parseInteractiveCategorySelection(categoryAnswer);
|
|
359
|
+
const forceAnswer = await prompt.ask("Overwrite selected existing files with --force? [y/N] ");
|
|
360
|
+
|
|
361
|
+
return {
|
|
362
|
+
...options,
|
|
363
|
+
only,
|
|
364
|
+
exclude: [],
|
|
365
|
+
force: parseYesNo(forceAnswer, false)
|
|
366
|
+
};
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
function createPrompter(input, output) {
|
|
370
|
+
const rl = readline.createInterface({ input, crlfDelay: Infinity });
|
|
371
|
+
const iterator = rl[Symbol.asyncIterator]();
|
|
372
|
+
|
|
373
|
+
return {
|
|
374
|
+
async ask(promptText) {
|
|
375
|
+
output.write(promptText);
|
|
376
|
+
const next = await iterator.next();
|
|
377
|
+
const answer = next.done ? "" : next.value;
|
|
378
|
+
output.write("\n");
|
|
379
|
+
return answer;
|
|
380
|
+
},
|
|
381
|
+
close() {
|
|
382
|
+
rl.close();
|
|
383
|
+
}
|
|
384
|
+
};
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
|
|
388
|
+
function parseSelectionNumber(rawValue, min, max, defaultValue) {
|
|
389
|
+
const value = rawValue.trim();
|
|
390
|
+
if (!value) {
|
|
391
|
+
return defaultValue;
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
const parsed = Number(value);
|
|
395
|
+
if (!Number.isInteger(parsed) || parsed < min || parsed > max) {
|
|
396
|
+
throw new CliError(`Selection must be a number from ${min} to ${max}.`, 1);
|
|
397
|
+
}
|
|
398
|
+
return parsed;
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
function parseYesNo(rawValue, defaultValue) {
|
|
402
|
+
const value = rawValue.trim().toLowerCase();
|
|
403
|
+
if (!value) {
|
|
404
|
+
return defaultValue;
|
|
405
|
+
}
|
|
406
|
+
if (value === "y" || value === "yes") {
|
|
407
|
+
return true;
|
|
408
|
+
}
|
|
409
|
+
if (value === "n" || value === "no") {
|
|
410
|
+
return false;
|
|
411
|
+
}
|
|
412
|
+
throw new CliError("Answer must be yes or no.", 1);
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
function parseInteractiveCategorySelection(rawValue) {
|
|
416
|
+
const value = rawValue.trim();
|
|
417
|
+
if (!value || value.toLowerCase() === "all") {
|
|
418
|
+
return null;
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
const selected = [];
|
|
422
|
+
for (const chunk of value.split(",")) {
|
|
423
|
+
const token = chunk.trim();
|
|
424
|
+
if (!token) {
|
|
425
|
+
continue;
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
const numeric = Number(token);
|
|
429
|
+
const categoryName = Number.isInteger(numeric)
|
|
430
|
+
? INSTALL_CATEGORIES[numeric - 1]?.name
|
|
431
|
+
: (INSTALL_CATEGORY_ALIASES.get(token.toLowerCase()) || token.toLowerCase());
|
|
432
|
+
|
|
433
|
+
if (!categoryName || !INSTALL_CATEGORY_NAMES.includes(categoryName)) {
|
|
434
|
+
throw new CliError(
|
|
435
|
+
`Unknown install category '${token}'. Supported values: ${INSTALL_CATEGORY_NAMES.join(", ")}.`,
|
|
436
|
+
1
|
|
437
|
+
);
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
if (!selected.includes(categoryName)) {
|
|
441
|
+
selected.push(categoryName);
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
if (selected.length === 0) {
|
|
446
|
+
throw new CliError("No install categories selected.", 1);
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
return selected;
|
|
450
|
+
}
|
|
451
|
+
|
|
123
452
|
function buildInstallPlan(options) {
|
|
124
453
|
const { manifest, entries, payloadRoot } = readInstallManifest();
|
|
125
|
-
const
|
|
454
|
+
const selectedEntries = filterManifestEntries(entries, options);
|
|
455
|
+
if (selectedEntries.length === 0) {
|
|
456
|
+
throw new CliError("Install selection matched no files.", 1);
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
const items = selectedEntries.map((entry) => {
|
|
126
460
|
const sourcePath = path.join(payloadRoot, entry.target);
|
|
127
461
|
if (!existsSync(sourcePath)) {
|
|
128
462
|
throw new CliError(getMissingPackagedAssetMessage(entry.target), 1);
|
|
@@ -137,11 +471,41 @@ function buildInstallPlan(options) {
|
|
|
137
471
|
|
|
138
472
|
return {
|
|
139
473
|
manifest,
|
|
474
|
+
selectedEntries,
|
|
475
|
+
skippedCount: entries.length - selectedEntries.length,
|
|
140
476
|
items,
|
|
141
477
|
targetRoot: options.target
|
|
142
478
|
};
|
|
143
479
|
}
|
|
144
480
|
|
|
481
|
+
function mergeCategoryLists(left, right) {
|
|
482
|
+
const merged = [...left];
|
|
483
|
+
for (const item of right) {
|
|
484
|
+
if (!merged.includes(item)) {
|
|
485
|
+
merged.push(item);
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
return merged;
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
function getInstallCategory(target) {
|
|
492
|
+
const normalizedTarget = target.replace(/\\/g, "/");
|
|
493
|
+
return INSTALL_CATEGORIES.find((category) => category.matches(normalizedTarget))?.name || "uncategorized";
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
function filterManifestEntries(entries, options) {
|
|
497
|
+
const only = options.only ? new Set(options.only) : null;
|
|
498
|
+
const exclude = new Set(options.exclude || []);
|
|
499
|
+
|
|
500
|
+
return entries.filter((entry) => {
|
|
501
|
+
const category = getInstallCategory(entry.target);
|
|
502
|
+
if (only && !only.has(category)) {
|
|
503
|
+
return false;
|
|
504
|
+
}
|
|
505
|
+
return !exclude.has(category);
|
|
506
|
+
});
|
|
507
|
+
}
|
|
508
|
+
|
|
145
509
|
function collectConflicts(plan) {
|
|
146
510
|
const conflicts = [];
|
|
147
511
|
|
|
@@ -191,10 +555,18 @@ function printPlanSummary(plan, options) {
|
|
|
191
555
|
console.log("------------");
|
|
192
556
|
console.log(`Target: ${options.target}`);
|
|
193
557
|
console.log(`Files: ${plan.items.length}`);
|
|
558
|
+
if (plan.skippedCount > 0) {
|
|
559
|
+
console.log(`Skipped by selection: ${plan.skippedCount}`);
|
|
560
|
+
}
|
|
194
561
|
console.log(`Agents: ${options.agents.join(", ")}`);
|
|
562
|
+
console.log(`Only: ${options.only ? options.only.join(", ") : "all categories"}`);
|
|
563
|
+
if (options.exclude.length > 0) {
|
|
564
|
+
console.log(`Exclude: ${options.exclude.join(", ")}`);
|
|
565
|
+
}
|
|
195
566
|
console.log(`Force: ${options.force ? "yes" : "no"}`);
|
|
196
567
|
console.log("");
|
|
197
568
|
console.log("Note: --agents is accepted now for forward compatibility, but v1 base install still excludes top-level adapter entry docs by default.");
|
|
569
|
+
console.log("Note: .project/context, .project/projects, and .project/registry are repo-owned after install; use --no-project-state or --only for update-safe refreshes.");
|
|
198
570
|
}
|
|
199
571
|
|
|
200
572
|
function printConflicts(conflicts, options) {
|
|
@@ -211,7 +583,7 @@ function printConflicts(conflicts, options) {
|
|
|
211
583
|
if (options.force) {
|
|
212
584
|
console.error("Install cannot continue because at least one conflict is not forceable.");
|
|
213
585
|
} else {
|
|
214
|
-
console.error("Install aborted before writing files. Re-run with --force to overwrite only allowlisted target paths.");
|
|
586
|
+
console.error("Install aborted before writing files. Re-run with --force to overwrite only selected allowlisted target paths, or narrow the plan with --only/--exclude.");
|
|
215
587
|
}
|
|
216
588
|
}
|
|
217
589
|
|
|
@@ -220,15 +592,15 @@ async function confirmInstall(plan, options) {
|
|
|
220
592
|
return true;
|
|
221
593
|
}
|
|
222
594
|
|
|
223
|
-
const
|
|
595
|
+
const prompt = createPrompter(stdin, stdout);
|
|
224
596
|
try {
|
|
225
|
-
const
|
|
597
|
+
const promptText = options.force
|
|
226
598
|
? `Proceed with force-installing ${plan.items.length} files into ${options.target}? [y/N] `
|
|
227
599
|
: `Proceed with installing ${plan.items.length} files into ${options.target}? [y/N] `;
|
|
228
|
-
const answer = await
|
|
600
|
+
const answer = await prompt.ask(promptText);
|
|
229
601
|
return /^[Yy](es)?$/.test(answer.trim());
|
|
230
602
|
} finally {
|
|
231
|
-
|
|
603
|
+
prompt.close();
|
|
232
604
|
}
|
|
233
605
|
}
|
|
234
606
|
|
|
@@ -294,12 +666,21 @@ function validateManifestPath(relativePath, fieldName) {
|
|
|
294
666
|
}
|
|
295
667
|
|
|
296
668
|
module.exports = {
|
|
669
|
+
INSTALL_CATEGORIES,
|
|
670
|
+
INSTALL_PRESETS,
|
|
297
671
|
SUPPORTED_AGENTS,
|
|
298
672
|
applyInstallPlan,
|
|
673
|
+
applyInstallPreset,
|
|
299
674
|
buildInstallPlan,
|
|
300
675
|
collectConflicts,
|
|
301
676
|
confirmInstall,
|
|
677
|
+
configureInteractiveInstall,
|
|
678
|
+
createPrompter,
|
|
679
|
+
filterManifestEntries,
|
|
680
|
+
getInstallCategory,
|
|
302
681
|
normalizeManifestEntries,
|
|
682
|
+
parseInteractiveCategorySelection,
|
|
683
|
+
parseCategoryList,
|
|
303
684
|
parseAgentList,
|
|
304
685
|
parseInstallArgs,
|
|
305
686
|
printConflicts,
|