@clearlydev/nx-archweave 0.0.0-stage → 0.1.1
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/LICENSE +21 -0
- package/README.md +265 -2
- package/dist/bin/review-verdict.js +330 -0
- package/dist/eslint/fsd-boundaries.d.ts +119 -0
- package/dist/eslint/fsd-boundaries.js +144 -0
- package/dist/eslint/index.d.ts +78 -0
- package/dist/eslint/index.js +256 -0
- package/dist/eslint/tsconfig-aliases.d.ts +18 -0
- package/dist/eslint/tsconfig-aliases.js +114 -0
- package/dist/generators/add-agent-rules/generator.js +550 -0
- package/dist/generators/add-agent-rules/render.js +46 -0
- package/dist/generators/add-backend/generator.js +784 -0
- package/dist/generators/add-backend/render.js +102 -0
- package/dist/generators/add-fsd-lite/generator.js +553 -0
- package/dist/generators/add-fsd-lite/render.js +72 -0
- package/dist/generators/add-github-templates/files/en/codeowners/.github/CODEOWNERS.template +4 -0
- package/dist/generators/add-github-templates/files/en/issue/.github/ISSUE_TEMPLATE/bug-report.yml.template +28 -0
- package/dist/generators/add-github-templates/files/en/issue/.github/ISSUE_TEMPLATE/feature-request.yml.template +17 -0
- package/dist/generators/add-github-templates/files/en/pr/.github/PULL_REQUEST_TEMPLATE.md.template +12 -0
- package/dist/generators/add-github-templates/files/ko/codeowners/.github/CODEOWNERS.template +4 -0
- package/dist/generators/add-github-templates/files/ko/issue/.github/ISSUE_TEMPLATE/bug-report.yml.template +28 -0
- package/dist/generators/add-github-templates/files/ko/issue/.github/ISSUE_TEMPLATE/feature-request.yml.template +17 -0
- package/dist/generators/add-github-templates/files/ko/pr/.github/PULL_REQUEST_TEMPLATE.md.template +12 -0
- package/dist/generators/add-github-templates/generator.js +125 -0
- package/dist/generators/add-github-templates/render.js +26 -0
- package/dist/generators/add-integration/generator.js +818 -0
- package/dist/generators/add-integration/render.js +34 -0
- package/dist/generators/add-review/generator.js +921 -0
- package/dist/generators/add-review/render.js +96 -0
- package/dist/generators/allow-builds/generator.js +436 -0
- package/dist/generators/allow-builds/render.js +82 -0
- package/dist/generators/doctor/generator.js +2431 -0
- package/dist/generators/doctor/integration-report.js +70 -0
- package/dist/generators/doctor/render.js +10 -0
- package/dist/generators/entity/generator.js +395 -0
- package/dist/generators/feature/generator.js +395 -0
- package/dist/generators/init/generator.js +3211 -0
- package/dist/generators/init/render.js +125 -0
- package/dist/generators/init/validation.js +74 -0
- package/dist/generators/install-integration/generator.js +1233 -0
- package/dist/generators/install-integration/render.js +128 -0
- package/dist/generators/page/generator.js +395 -0
- package/dist/generators/record-integration/generator.js +852 -0
- package/dist/generators/record-integration/render.js +31 -0
- package/dist/generators/shared/dry-run.js +12 -0
- package/dist/generators/shared/frontend-app.js +57 -0
- package/dist/generators/shared/generator.js +395 -0
- package/dist/generators/shared/project-type.js +39 -0
- package/dist/generators/shared/record-pipeline.js +34 -0
- package/dist/generators/shared/run-nx.js +24 -0
- package/dist/generators/shared/template-files.js +38 -0
- package/dist/generators/shared/toolkit-config-file.js +110 -0
- package/dist/generators/slice/core.js +185 -0
- package/dist/generators/slice/files/index/index.ts.template +14 -0
- package/dist/generators/slice/files/module/__name__.spec.ts.template +11 -0
- package/dist/generators/slice/files/module/__name__.ts.template +11 -0
- package/dist/generators/slice/files/segment/index.ts.template +20 -0
- package/dist/generators/slice/render.js +70 -0
- package/dist/generators/widget/generator.js +395 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +680 -0
- package/dist/lib/nx-plugin.d.ts +2 -0
- package/dist/lib/nx-plugin.js +7 -0
- package/dist/runtime/integrations-composition-root.js +20 -0
- package/dist/runtime/node-command-runner.js +81 -0
- package/generators.json +85 -0
- package/package.json +95 -4
- package/src/generators/add-agent-rules/schema.json +43 -0
- package/src/generators/add-backend/schema.json +39 -0
- package/src/generators/add-fsd-lite/schema.json +43 -0
- package/src/generators/add-github-templates/schema.json +35 -0
- package/src/generators/add-integration/schema.json +35 -0
- package/src/generators/add-review/schema.json +72 -0
- package/src/generators/allow-builds/schema.json +20 -0
- package/src/generators/doctor/schema.json +20 -0
- package/src/generators/entity/schema.json +37 -0
- package/src/generators/feature/schema.json +37 -0
- package/src/generators/init/schema.json +98 -0
- package/src/generators/install-integration/schema.json +35 -0
- package/src/generators/page/schema.json +53 -0
- package/src/generators/record-integration/schema.json +26 -0
- package/src/generators/shared/schema.json +32 -0
- package/src/generators/widget/schema.json +45 -0
|
@@ -0,0 +1,1233 @@
|
|
|
1
|
+
// ../integrations/dist/registry/integration-registry.js
|
|
2
|
+
function createIntegrationRegistry(integrations) {
|
|
3
|
+
const seenIds = /* @__PURE__ */ new Set();
|
|
4
|
+
for (const integration of integrations) {
|
|
5
|
+
if (seenIds.has(integration.id)) {
|
|
6
|
+
throw new Error(`Duplicate integration id: "${integration.id}"`);
|
|
7
|
+
}
|
|
8
|
+
seenIds.add(integration.id);
|
|
9
|
+
}
|
|
10
|
+
return integrations;
|
|
11
|
+
}
|
|
12
|
+
function listIntegrations(registry) {
|
|
13
|
+
return registry;
|
|
14
|
+
}
|
|
15
|
+
function getIntegration(registry, id) {
|
|
16
|
+
return registry.find((integration) => integration.id === id);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// ../integrations/dist/manifest/manifest.js
|
|
20
|
+
import { existsSync, lstatSync, readFileSync, renameSync, unlinkSync, writeFileSync } from "node:fs";
|
|
21
|
+
import { join } from "node:path";
|
|
22
|
+
import { randomBytes } from "node:crypto";
|
|
23
|
+
var MANIFEST_FILENAME = "archweave-integrations.json";
|
|
24
|
+
function createEmptyManifest() {
|
|
25
|
+
return { integrations: {} };
|
|
26
|
+
}
|
|
27
|
+
function loadManifest(root) {
|
|
28
|
+
const manifestPath = join(root, MANIFEST_FILENAME);
|
|
29
|
+
if (!existsSync(manifestPath)) {
|
|
30
|
+
return createEmptyManifest();
|
|
31
|
+
}
|
|
32
|
+
try {
|
|
33
|
+
const parsed = JSON.parse(readFileSync(manifestPath, "utf8"));
|
|
34
|
+
if (parsed && typeof parsed === "object" && typeof parsed.integrations === "object") {
|
|
35
|
+
return { integrations: parsed.integrations ?? {} };
|
|
36
|
+
}
|
|
37
|
+
return createEmptyManifest();
|
|
38
|
+
} catch {
|
|
39
|
+
return createEmptyManifest();
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
function serializeManifest(manifest) {
|
|
43
|
+
return `${JSON.stringify(manifest, null, 2)}
|
|
44
|
+
`;
|
|
45
|
+
}
|
|
46
|
+
function writeManifestAtomic(root, manifest) {
|
|
47
|
+
const finalPath = join(root, MANIFEST_FILENAME);
|
|
48
|
+
if (existsSync(finalPath) && lstatSync(finalPath).isSymbolicLink()) {
|
|
49
|
+
throw new Error(`Refusing to write ${MANIFEST_FILENAME}: the path is a symlink. Remove or replace it with a regular file first.`);
|
|
50
|
+
}
|
|
51
|
+
const suffix = randomBytes(6).toString("hex");
|
|
52
|
+
const tempPath = join(root, `.${MANIFEST_FILENAME}.tmp-${suffix}`);
|
|
53
|
+
try {
|
|
54
|
+
writeFileSync(tempPath, serializeManifest(manifest));
|
|
55
|
+
renameSync(tempPath, finalPath);
|
|
56
|
+
} catch (error) {
|
|
57
|
+
if (existsSync(tempPath)) {
|
|
58
|
+
unlinkSync(tempPath);
|
|
59
|
+
}
|
|
60
|
+
throw error;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// ../integrations/dist/manifest/record-plan.js
|
|
65
|
+
function entriesEqual(a, b) {
|
|
66
|
+
return JSON.stringify(a) === JSON.stringify(b);
|
|
67
|
+
}
|
|
68
|
+
function buildRecordPlan(input) {
|
|
69
|
+
const isRecordable = input.verification.verified && input.verificationSource === "claude-cli";
|
|
70
|
+
const proposedEntry = isRecordable ? {
|
|
71
|
+
source: input.metadata.source,
|
|
72
|
+
repository: input.metadata.repository,
|
|
73
|
+
version: input.verification.resolvedVersion,
|
|
74
|
+
method: input.metadata.method,
|
|
75
|
+
managedBy: "nx-archweave",
|
|
76
|
+
verification: "passed",
|
|
77
|
+
target: input.target,
|
|
78
|
+
evidenceKind: "claude-cli"
|
|
79
|
+
} : void 0;
|
|
80
|
+
const hasConflict = input.existingEntry !== void 0 && proposedEntry !== void 0 && !entriesEqual(input.existingEntry, proposedEntry);
|
|
81
|
+
return {
|
|
82
|
+
id: input.id,
|
|
83
|
+
target: input.target,
|
|
84
|
+
verificationSource: input.verificationSource,
|
|
85
|
+
currentVerification: input.verification,
|
|
86
|
+
existingEntry: input.existingEntry,
|
|
87
|
+
proposedEntry,
|
|
88
|
+
hasConflict
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// ../shared/dist/lib/spawnable-command.js
|
|
93
|
+
import { statSync } from "node:fs";
|
|
94
|
+
import { win32 } from "node:path";
|
|
95
|
+
function isFile(path) {
|
|
96
|
+
try {
|
|
97
|
+
return statSync(path).isFile();
|
|
98
|
+
} catch {
|
|
99
|
+
return false;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
var DEFAULT_ENV = { platform: process.platform, env: process.env, isFile };
|
|
103
|
+
function envValue(env, name) {
|
|
104
|
+
const key = Object.keys(env).find((candidate) => candidate.toUpperCase() === name);
|
|
105
|
+
return key === void 0 ? void 0 : env[key];
|
|
106
|
+
}
|
|
107
|
+
function resolveWindowsCommand(command, environment) {
|
|
108
|
+
const exts = (envValue(environment.env, "PATHEXT") ?? ".COM;.EXE;.BAT;.CMD").split(";").filter(Boolean);
|
|
109
|
+
const candidates = (base) => win32.extname(base) !== "" ? [base] : exts.map((ext) => base + ext);
|
|
110
|
+
if (win32.isAbsolute(command) || /[\\/]/.test(command)) {
|
|
111
|
+
return candidates(command).find(environment.isFile);
|
|
112
|
+
}
|
|
113
|
+
const dirs = (envValue(environment.env, "PATH") ?? "").split(";").filter(Boolean);
|
|
114
|
+
for (const dir of dirs) {
|
|
115
|
+
const found = candidates(win32.join(dir, command)).find(environment.isFile);
|
|
116
|
+
if (found !== void 0)
|
|
117
|
+
return found;
|
|
118
|
+
}
|
|
119
|
+
return void 0;
|
|
120
|
+
}
|
|
121
|
+
var META_CHARS = /([()\][%!^"`<>&|;, *?])/g;
|
|
122
|
+
var BIN_SHIM = /node_modules[\\/]\.bin[\\/][^\\/]+\.cmd$/i;
|
|
123
|
+
function escapeCmdArgument(arg, doubleEscape = false) {
|
|
124
|
+
let quoted = arg.replace(/(\\*)"/g, '$1$1\\"');
|
|
125
|
+
quoted = quoted.replace(/(\\*)$/, "$1$1");
|
|
126
|
+
const escaped = `"${quoted}"`.replace(META_CHARS, "^$1");
|
|
127
|
+
return doubleEscape ? escaped.replace(META_CHARS, "^$1") : escaped;
|
|
128
|
+
}
|
|
129
|
+
function spawnableCommand(command, args, environment = DEFAULT_ENV) {
|
|
130
|
+
if (environment.platform !== "win32")
|
|
131
|
+
return { command, args: [...args] };
|
|
132
|
+
const resolved = resolveWindowsCommand(command, environment);
|
|
133
|
+
if (resolved === void 0 || !/\.(cmd|bat)$/i.test(resolved))
|
|
134
|
+
return { command, args: [...args] };
|
|
135
|
+
const doubleEscape = BIN_SHIM.test(resolved);
|
|
136
|
+
const line = [resolved.replace(META_CHARS, "^$1"), ...args.map((arg) => escapeCmdArgument(arg, doubleEscape))].join(" ");
|
|
137
|
+
return {
|
|
138
|
+
command: envValue(environment.env, "COMSPEC") ?? "cmd.exe",
|
|
139
|
+
args: ["/d", "/s", "/c", `"${line}"`],
|
|
140
|
+
windowsVerbatimArguments: true
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// ../integrations/dist/config/declare-enabled-integration.js
|
|
145
|
+
import { existsSync as existsSync2, lstatSync as lstatSync2, readFileSync as readFileSync2, renameSync as renameSync2, statSync as statSync2, unlinkSync as unlinkSync2, writeFileSync as writeFileSync2 } from "node:fs";
|
|
146
|
+
import { randomBytes as randomBytes2 } from "node:crypto";
|
|
147
|
+
import { join as join2 } from "node:path";
|
|
148
|
+
var CONFIG_FILENAME = "toolkit.config.json";
|
|
149
|
+
function detectIndent(raw) {
|
|
150
|
+
if (!raw.includes("\n"))
|
|
151
|
+
return void 0;
|
|
152
|
+
for (const line of raw.split(/\r?\n/)) {
|
|
153
|
+
const match = /^(\t+| +)/.exec(line);
|
|
154
|
+
if (match)
|
|
155
|
+
return match[1];
|
|
156
|
+
}
|
|
157
|
+
return " ";
|
|
158
|
+
}
|
|
159
|
+
function declareEnabledIntegration(root, id, options) {
|
|
160
|
+
const configPath = join2(root, CONFIG_FILENAME);
|
|
161
|
+
if (!existsSync2(configPath))
|
|
162
|
+
return { action: "no-config" };
|
|
163
|
+
if (lstatSync2(configPath).isSymbolicLink())
|
|
164
|
+
return { action: "unrecognized" };
|
|
165
|
+
const raw = readFileSync2(configPath, "utf8");
|
|
166
|
+
let parsed;
|
|
167
|
+
try {
|
|
168
|
+
parsed = JSON.parse(raw);
|
|
169
|
+
} catch {
|
|
170
|
+
return { action: "unrecognized" };
|
|
171
|
+
}
|
|
172
|
+
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed))
|
|
173
|
+
return { action: "unrecognized" };
|
|
174
|
+
const existing = parsed;
|
|
175
|
+
const integrationsField = existing["integrations"];
|
|
176
|
+
if (integrationsField !== void 0 && (typeof integrationsField !== "object" || integrationsField === null || Array.isArray(integrationsField))) {
|
|
177
|
+
return { action: "unrecognized" };
|
|
178
|
+
}
|
|
179
|
+
const integrations = integrationsField ?? {};
|
|
180
|
+
const enabledField = integrations["enabled"];
|
|
181
|
+
if (enabledField !== void 0 && (!Array.isArray(enabledField) || !enabledField.every((e) => typeof e === "string"))) {
|
|
182
|
+
return { action: "unrecognized" };
|
|
183
|
+
}
|
|
184
|
+
const enabled = enabledField ?? [];
|
|
185
|
+
if (enabled.includes(id))
|
|
186
|
+
return { action: "unchanged" };
|
|
187
|
+
if (options.write) {
|
|
188
|
+
const next = { ...existing, integrations: { ...integrations, enabled: [...enabled, id] } };
|
|
189
|
+
let body = JSON.stringify(next, null, detectIndent(raw));
|
|
190
|
+
if (raw.endsWith("\n"))
|
|
191
|
+
body += "\n";
|
|
192
|
+
if (raw.includes("\r\n"))
|
|
193
|
+
body = body.replace(/\n/g, "\r\n");
|
|
194
|
+
const tempPath = join2(root, `.${CONFIG_FILENAME}.tmp-${randomBytes2(6).toString("hex")}`);
|
|
195
|
+
try {
|
|
196
|
+
writeFileSync2(tempPath, body, { mode: statSync2(configPath).mode & 511 });
|
|
197
|
+
renameSync2(tempPath, configPath);
|
|
198
|
+
} catch (error) {
|
|
199
|
+
if (existsSync2(tempPath))
|
|
200
|
+
unlinkSync2(tempPath);
|
|
201
|
+
throw error;
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
return { action: "updated" };
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// ../integrations/dist/process/read-only-command-runner.js
|
|
208
|
+
var CLAUDE_CLI_TIMEOUT_MS = 5e3;
|
|
209
|
+
var CLAUDE_CLI_STDOUT_LIMIT_BYTES = 1048576;
|
|
210
|
+
var CLAUDE_CLI_STDERR_LIMIT_BYTES = 65536;
|
|
211
|
+
|
|
212
|
+
// ../integrations/dist/catalog/plugin-catalog.js
|
|
213
|
+
var PLUGIN_HOSTS = ["claude-code"];
|
|
214
|
+
var PLUGIN_CATALOG = [
|
|
215
|
+
{
|
|
216
|
+
// Researched 2026-07-15. The claude-code values are the ones the manifest and verifier
|
|
217
|
+
// were measured with. ponytail documents official installs for ~20 hosts; only claude-code
|
|
218
|
+
// has an adapter.
|
|
219
|
+
id: "ponytail",
|
|
220
|
+
displayName: "ponytail",
|
|
221
|
+
// The project's own tagline.
|
|
222
|
+
description: 'over-engineering review rules and skills \u2014 "makes your AI agent think like the laziest senior dev in the room"',
|
|
223
|
+
homepage: "https://ponytail.dev",
|
|
224
|
+
repository: "https://github.com/DietrichGebert/ponytail",
|
|
225
|
+
license: "MIT",
|
|
226
|
+
npmPackage: "@dietrichgebert/ponytail",
|
|
227
|
+
installDocs: "https://github.com/DietrichGebert/ponytail#install",
|
|
228
|
+
uninstallSteps: [
|
|
229
|
+
"Run this from a clone of https://github.com/DietrichGebert/ponytail (or before removing the plugin below \u2014 the script is itself a plugin file, so removing the plugin first deletes it): node scripts/uninstall.js",
|
|
230
|
+
"Then remove the plugin via your host's own command, e.g. in Claude Code: /plugin uninstall ponytail@ponytail (or use the Claude Code Desktop app: Customize > manage marketplaces > remove plugin).",
|
|
231
|
+
"See https://github.com/DietrichGebert/ponytail#uninstall for the full, current official instructions."
|
|
232
|
+
],
|
|
233
|
+
hosts: {
|
|
234
|
+
"claude-code": { name: "ponytail", marketplace: "DietrichGebert/ponytail", pluginId: "ponytail@ponytail" }
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
];
|
|
238
|
+
var PLUGIN_IDS = PLUGIN_CATALOG.map((entry) => entry.id);
|
|
239
|
+
function getCatalogEntry(id) {
|
|
240
|
+
return PLUGIN_CATALOG.find((entry) => entry.id === id);
|
|
241
|
+
}
|
|
242
|
+
var CLAUDE_MARKETPLACE_METHOD = "claude-code-plugin-marketplace";
|
|
243
|
+
function recordMetadataFor(entry) {
|
|
244
|
+
const claude = entry.hosts["claude-code"];
|
|
245
|
+
if (!claude)
|
|
246
|
+
return void 0;
|
|
247
|
+
return { source: claude.marketplace, repository: entry.repository, method: CLAUDE_MARKETPLACE_METHOD };
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
// ../integrations/dist/hosts/claude-code/detect.js
|
|
251
|
+
import { existsSync as existsSync3, readFileSync as readFileSync3 } from "node:fs";
|
|
252
|
+
import { join as join3 } from "node:path";
|
|
253
|
+
async function detectWorkspacePackage(entry, context) {
|
|
254
|
+
const npmPackage = entry.npmPackage;
|
|
255
|
+
if (npmPackage === void 0) {
|
|
256
|
+
return { installed: false, managedBy: "unknown", evidence: [] };
|
|
257
|
+
}
|
|
258
|
+
const packageJsonPath = join3(context.workspaceRoot, "package.json");
|
|
259
|
+
if (!existsSync3(packageJsonPath)) {
|
|
260
|
+
return { installed: false, managedBy: "unknown", evidence: [] };
|
|
261
|
+
}
|
|
262
|
+
let pkg;
|
|
263
|
+
try {
|
|
264
|
+
pkg = JSON.parse(readFileSync3(packageJsonPath, "utf8"));
|
|
265
|
+
} catch {
|
|
266
|
+
return { installed: false, managedBy: "unknown", evidence: [] };
|
|
267
|
+
}
|
|
268
|
+
const deps = pkg["dependencies"] ?? {};
|
|
269
|
+
const devDeps = pkg["devDependencies"] ?? {};
|
|
270
|
+
const version = deps[npmPackage] ?? devDeps[npmPackage];
|
|
271
|
+
if (!version) {
|
|
272
|
+
return { installed: false, managedBy: "unknown", evidence: [] };
|
|
273
|
+
}
|
|
274
|
+
return {
|
|
275
|
+
installed: true,
|
|
276
|
+
installedVersion: version,
|
|
277
|
+
managedBy: "external-tool",
|
|
278
|
+
evidence: [`package.json: ${npmPackage}@${version}`]
|
|
279
|
+
};
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
// ../integrations/dist/hosts/claude-code/plan.js
|
|
283
|
+
var VERSION_DECIDED_BY_MARKETPLACE = "latest (decided by the marketplace)";
|
|
284
|
+
var WORKSPACE_FILES_BY_SCOPE = {
|
|
285
|
+
user: [],
|
|
286
|
+
project: [".claude/settings.json"],
|
|
287
|
+
local: [".claude/settings.local.json"]
|
|
288
|
+
};
|
|
289
|
+
var DEFAULT_PLUGIN_SCOPE = "project";
|
|
290
|
+
function createClaudeMarketplacePlan(entry) {
|
|
291
|
+
return async (options) => {
|
|
292
|
+
const distribution = entry.hosts["claude-code"];
|
|
293
|
+
if (options.targetEnvironment !== "claude-code" || distribution === void 0) {
|
|
294
|
+
const supported = Object.entries(entry.hosts).filter(([, value]) => value !== void 0).map(([host]) => host).join(", ");
|
|
295
|
+
throw new Error(`Target environment "${options.targetEnvironment}" is not supported by this integration. Supported: ${supported}.`);
|
|
296
|
+
}
|
|
297
|
+
const resolvedVersion = options.versionRange ?? VERSION_DECIDED_BY_MARKETPLACE;
|
|
298
|
+
const scope = options.scope ?? DEFAULT_PLUGIN_SCOPE;
|
|
299
|
+
return {
|
|
300
|
+
officialCommand: {
|
|
301
|
+
executable: "claude",
|
|
302
|
+
args: [
|
|
303
|
+
"plugin",
|
|
304
|
+
"install",
|
|
305
|
+
distribution.name,
|
|
306
|
+
"--marketplace",
|
|
307
|
+
distribution.marketplace,
|
|
308
|
+
"--scope",
|
|
309
|
+
scope,
|
|
310
|
+
"--json"
|
|
311
|
+
]
|
|
312
|
+
},
|
|
313
|
+
resolvedVersion,
|
|
314
|
+
filesCreated: [],
|
|
315
|
+
// project/local scope: Claude Code records the plugin in the workspace's own
|
|
316
|
+
// .claude settings — shown before approval as the files that will change.
|
|
317
|
+
filesModified: [...WORKSPACE_FILES_BY_SCOPE[scope]],
|
|
318
|
+
conflicts: [],
|
|
319
|
+
sourceRepository: entry.repository,
|
|
320
|
+
officialHomepage: entry.homepage,
|
|
321
|
+
license: entry.license,
|
|
322
|
+
requiresManualExecution: false
|
|
323
|
+
};
|
|
324
|
+
};
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
// ../integrations/dist/hosts/claude-code/install.js
|
|
328
|
+
import { homedir } from "node:os";
|
|
329
|
+
function manualInstallSteps(entry) {
|
|
330
|
+
const distribution = entry.hosts["claude-code"];
|
|
331
|
+
if (distribution === void 0)
|
|
332
|
+
return [`See ${entry.installDocs} for the official instructions.`];
|
|
333
|
+
return [
|
|
334
|
+
`In Claude Code, send this as its own prompt: /plugin marketplace add ${distribution.marketplace}`,
|
|
335
|
+
`Then, as a second separate prompt: /plugin install ${distribution.pluginId}`,
|
|
336
|
+
`See ${entry.installDocs} for the official instructions.`
|
|
337
|
+
];
|
|
338
|
+
}
|
|
339
|
+
var CLAUDE_CLI_INSTALL_TIMEOUT_MS = 12e4;
|
|
340
|
+
var CLAUDE_CLI_INSTALL_STDOUT_LIMIT_BYTES = 1048576;
|
|
341
|
+
var CLAUDE_CLI_INSTALL_STDERR_LIMIT_BYTES = 262144;
|
|
342
|
+
function defaultRerunCommand(entry, runNx = "pnpm nx") {
|
|
343
|
+
return `${runNx} g @clearlydev/nx-archweave:install-integration --tool=${entry.id} --apply`;
|
|
344
|
+
}
|
|
345
|
+
function chatAlternative(entry) {
|
|
346
|
+
return ["Alternatively, inside a Claude Code session (the chat path):", ...manualInstallSteps(entry)];
|
|
347
|
+
}
|
|
348
|
+
var MARKETPLACE_NOTE = "If the marketplace was registered before the failure, it stays in your user settings \u2014 see `claude plugin marketplace list`.";
|
|
349
|
+
function claudeCliNotFoundSteps(entry, rerunCommand) {
|
|
350
|
+
return [
|
|
351
|
+
"The claude CLI was not found on PATH. Install the Claude Code CLI from your terminal: npm install -g @anthropic-ai/claude-code (setup guide: https://code.claude.com/docs/en/setup)",
|
|
352
|
+
`Then rerun: ${rerunCommand}`,
|
|
353
|
+
...chatAlternative(entry)
|
|
354
|
+
];
|
|
355
|
+
}
|
|
356
|
+
function claudeCliUnsupportedSteps(entry, rerunCommand, cliSaid) {
|
|
357
|
+
return [
|
|
358
|
+
`Your claude CLI does not know this command (it said: ${cliSaid}). Scriptable plugin install needs Claude Code 2.1.285 or newer: run \`claude update\`, or reinstall it with npm install -g @anthropic-ai/claude-code@latest`,
|
|
359
|
+
`Then rerun: ${rerunCommand}`,
|
|
360
|
+
...chatAlternative(entry)
|
|
361
|
+
];
|
|
362
|
+
}
|
|
363
|
+
var TAIL_CHARS = 500;
|
|
364
|
+
var UNSUPPORTED_CLI_PATTERN = /error: unknown (command|option) '(plugin|install|marketplace|--json|--marketplace|--scope)'/;
|
|
365
|
+
function tail(text) {
|
|
366
|
+
const trimmed = text.trim();
|
|
367
|
+
return trimmed.length > TAIL_CHARS ? trimmed.slice(-TAIL_CHARS) : trimmed;
|
|
368
|
+
}
|
|
369
|
+
var ESCAPED_HOME = homedir().replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
370
|
+
var HOME_PATTERN = new RegExp(`${ESCAPED_HOME}(?=[/\\\\\\s'"\`]|$)`, "g");
|
|
371
|
+
function redactHomePaths(text) {
|
|
372
|
+
let out = ESCAPED_HOME.length > 1 ? text.replace(HOME_PATTERN, "~") : text;
|
|
373
|
+
out = out.replace(/\/(?:Users|home)\/[^/\s'"`]+/g, "~");
|
|
374
|
+
out = out.replace(/[A-Za-z]:\\Users\\[^\\\s'"`]+/g, "~");
|
|
375
|
+
out = out.replace(/\/root(?=[/\s'"`]|$)/g, "~");
|
|
376
|
+
return out;
|
|
377
|
+
}
|
|
378
|
+
function parseLastJsonObject(stdout) {
|
|
379
|
+
const lines = stdout.split("\n");
|
|
380
|
+
for (let i = lines.length - 1; i >= 0; i--) {
|
|
381
|
+
const line = lines[i].trim();
|
|
382
|
+
if (!line.startsWith("{"))
|
|
383
|
+
continue;
|
|
384
|
+
try {
|
|
385
|
+
const parsed = JSON.parse(line);
|
|
386
|
+
if (typeof parsed === "object" && parsed !== null && !Array.isArray(parsed))
|
|
387
|
+
return parsed;
|
|
388
|
+
} catch {
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
return null;
|
|
392
|
+
}
|
|
393
|
+
function renderUserCommand(command) {
|
|
394
|
+
return [command.executable, ...command.args.filter((arg) => arg !== "--json")].join(" ");
|
|
395
|
+
}
|
|
396
|
+
async function classifyRun(runner, command, cwd) {
|
|
397
|
+
let raw;
|
|
398
|
+
try {
|
|
399
|
+
raw = await runner.run({
|
|
400
|
+
executable: command.executable,
|
|
401
|
+
argv: command.args,
|
|
402
|
+
...cwd === void 0 ? {} : { cwd },
|
|
403
|
+
timeoutMs: CLAUDE_CLI_INSTALL_TIMEOUT_MS,
|
|
404
|
+
stdoutLimitBytes: CLAUDE_CLI_INSTALL_STDOUT_LIMIT_BYTES,
|
|
405
|
+
stderrLimitBytes: CLAUDE_CLI_INSTALL_STDERR_LIMIT_BYTES
|
|
406
|
+
});
|
|
407
|
+
} catch (error) {
|
|
408
|
+
const code = typeof error?.code === "string" ? error.code : "unknown";
|
|
409
|
+
return code === "ENOENT" ? { kind: "cli-not-found" } : { kind: "failed-to-start", code };
|
|
410
|
+
}
|
|
411
|
+
if (raw.timedOut)
|
|
412
|
+
return { kind: "timeout" };
|
|
413
|
+
if (raw.outputExceeded)
|
|
414
|
+
return { kind: "output-too-large" };
|
|
415
|
+
const json = parseLastJsonObject(raw.stdout);
|
|
416
|
+
const jsonSaysFailed = json !== null && json["outcome"] === "failed";
|
|
417
|
+
if (raw.exitCode === 0 && !jsonSaysFailed)
|
|
418
|
+
return { kind: "executed" };
|
|
419
|
+
if (json !== null) {
|
|
420
|
+
const message = redactHomePaths(typeof json["message"] === "string" ? json["message"] : `the claude CLI exited with code ${raw.exitCode}`);
|
|
421
|
+
const failureCode = typeof json["failureCode"] === "string" ? json["failureCode"] : void 0;
|
|
422
|
+
const shown = json["shownCommand"];
|
|
423
|
+
if (typeof shown === "object" && shown !== null)
|
|
424
|
+
return { kind: "confirmation-required", message };
|
|
425
|
+
return failureCode === void 0 ? { kind: "failed", message } : { kind: "failed", failureCode, message };
|
|
426
|
+
}
|
|
427
|
+
if (UNSUPPORTED_CLI_PATTERN.test(raw.stderr)) {
|
|
428
|
+
const line = raw.stderr.split("\n").find((l) => UNSUPPORTED_CLI_PATTERN.test(l)) ?? "";
|
|
429
|
+
return { kind: "cli-unsupported", stderrTail: redactHomePaths(line.trim()) };
|
|
430
|
+
}
|
|
431
|
+
const said = redactHomePaths(tail(raw.stderr.length > 0 ? raw.stderr : raw.stdout));
|
|
432
|
+
return { kind: "failed", message: said.length > 0 ? said : `the claude CLI exited with code ${raw.exitCode}` };
|
|
433
|
+
}
|
|
434
|
+
function manualStepsFor(entry, detail, command, rerunCommand) {
|
|
435
|
+
const chat = chatAlternative(entry);
|
|
436
|
+
const userCommand = command === null ? "" : renderUserCommand(command);
|
|
437
|
+
const rerun = `Once resolved, rerun to install, verify and record it: ${rerunCommand}`;
|
|
438
|
+
switch (detail.kind) {
|
|
439
|
+
case "executed":
|
|
440
|
+
return [];
|
|
441
|
+
case "manual-only":
|
|
442
|
+
return manualInstallSteps(entry);
|
|
443
|
+
case "cli-not-found":
|
|
444
|
+
return claudeCliNotFoundSteps(entry, rerunCommand);
|
|
445
|
+
case "failed-to-start":
|
|
446
|
+
return [
|
|
447
|
+
`The claude CLI was found but could not be started (${detail.code}) \u2014 check its permissions and your system limits.`,
|
|
448
|
+
rerun,
|
|
449
|
+
...chat
|
|
450
|
+
];
|
|
451
|
+
case "cli-unsupported":
|
|
452
|
+
return claudeCliUnsupportedSteps(entry, rerunCommand, detail.stderrTail);
|
|
453
|
+
case "confirmation-required":
|
|
454
|
+
return [
|
|
455
|
+
`The marketplace declares a command that must be confirmed by a person; nx-archweave never accepts it for you (${detail.message})`,
|
|
456
|
+
`Review it and run the install yourself: ${userCommand}`,
|
|
457
|
+
rerun,
|
|
458
|
+
...chat
|
|
459
|
+
];
|
|
460
|
+
case "failed":
|
|
461
|
+
return [`The install command did not succeed: ${detail.message}`, MARKETPLACE_NOTE, rerun, ...chat];
|
|
462
|
+
case "timeout":
|
|
463
|
+
return [
|
|
464
|
+
`The install command did not finish within ${CLAUDE_CLI_INSTALL_TIMEOUT_MS / 1e3} s and was stopped.`,
|
|
465
|
+
MARKETPLACE_NOTE,
|
|
466
|
+
rerun,
|
|
467
|
+
...chat
|
|
468
|
+
];
|
|
469
|
+
case "output-too-large":
|
|
470
|
+
return [
|
|
471
|
+
"The install command produced more output than nx-archweave is willing to buffer and was stopped.",
|
|
472
|
+
`Run it yourself to see the full output: ${userCommand}`,
|
|
473
|
+
rerun,
|
|
474
|
+
...chat
|
|
475
|
+
];
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
async function installClaudeMarketplacePlugin(entry, deps, plan, options = {}) {
|
|
479
|
+
const rerunCommand = options.rerunCommand ?? defaultRerunCommand(entry);
|
|
480
|
+
const detail = plan.officialCommand === null ? { kind: "manual-only" } : await classifyRun(deps.runner, plan.officialCommand, options.cwd);
|
|
481
|
+
const executed = detail.kind === "executed";
|
|
482
|
+
const result = {
|
|
483
|
+
executed,
|
|
484
|
+
// One command: it either reached exit 0 or it did not. What a failed run may
|
|
485
|
+
// have left behind is re-checked live by install-integration, not guessed here.
|
|
486
|
+
partialFailure: false,
|
|
487
|
+
filesCreated: [],
|
|
488
|
+
// The workspace files the plan said the host would change (project/local scope);
|
|
489
|
+
// Claude's own plugin store is outside the workspace and never listed.
|
|
490
|
+
filesModified: executed ? [...plan.filesModified] : [],
|
|
491
|
+
manualStepsRemaining: manualStepsFor(entry, detail, plan.officialCommand, rerunCommand)
|
|
492
|
+
};
|
|
493
|
+
return { result, detail };
|
|
494
|
+
}
|
|
495
|
+
function projectMarketplaceCommand(entry) {
|
|
496
|
+
const distribution = entry.hosts["claude-code"];
|
|
497
|
+
if (distribution === void 0)
|
|
498
|
+
return null;
|
|
499
|
+
return {
|
|
500
|
+
executable: "claude",
|
|
501
|
+
args: ["plugin", "marketplace", "add", distribution.marketplace, "--scope", "project", "--json"]
|
|
502
|
+
};
|
|
503
|
+
}
|
|
504
|
+
async function declareProjectMarketplace(entry, deps, options = {}) {
|
|
505
|
+
const command = projectMarketplaceCommand(entry);
|
|
506
|
+
return command === null ? { kind: "manual-only" } : classifyRun(deps.runner, command, options.cwd);
|
|
507
|
+
}
|
|
508
|
+
function createClaudeMarketplaceInstall(entry, deps) {
|
|
509
|
+
return async (plan) => (await installClaudeMarketplacePlugin(entry, deps, plan)).result;
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
// ../integrations/dist/hosts/claude-code/verify-via-claude-cli.js
|
|
513
|
+
import { realpathSync } from "node:fs";
|
|
514
|
+
import { resolve } from "node:path";
|
|
515
|
+
|
|
516
|
+
// ../integrations/dist/hosts/claude-code/parse-claude-plugin-list.js
|
|
517
|
+
var KNOWN_SCOPES = /* @__PURE__ */ new Set(["user", "project", "local", "managed"]);
|
|
518
|
+
function isKnownScope(scope) {
|
|
519
|
+
return KNOWN_SCOPES.has(scope);
|
|
520
|
+
}
|
|
521
|
+
function isValidEntry(value) {
|
|
522
|
+
if (typeof value !== "object" || value === null) {
|
|
523
|
+
return false;
|
|
524
|
+
}
|
|
525
|
+
const record = value;
|
|
526
|
+
if (typeof record["id"] !== "string") {
|
|
527
|
+
return false;
|
|
528
|
+
}
|
|
529
|
+
if (typeof record["enabled"] !== "boolean") {
|
|
530
|
+
return false;
|
|
531
|
+
}
|
|
532
|
+
if (typeof record["scope"] !== "string" || record["scope"].length === 0) {
|
|
533
|
+
return false;
|
|
534
|
+
}
|
|
535
|
+
if (record["version"] !== void 0 && typeof record["version"] !== "string") {
|
|
536
|
+
return false;
|
|
537
|
+
}
|
|
538
|
+
return true;
|
|
539
|
+
}
|
|
540
|
+
function parseClaudePluginList(rawStdout, options = {}) {
|
|
541
|
+
let parsed;
|
|
542
|
+
try {
|
|
543
|
+
parsed = JSON.parse(rawStdout);
|
|
544
|
+
} catch {
|
|
545
|
+
return { ok: false, reason: "invalid-json" };
|
|
546
|
+
}
|
|
547
|
+
if (!Array.isArray(parsed)) {
|
|
548
|
+
return { ok: false, reason: "non-array" };
|
|
549
|
+
}
|
|
550
|
+
const entries = [];
|
|
551
|
+
const otherScopeIds = [];
|
|
552
|
+
for (const item of parsed) {
|
|
553
|
+
if (!isValidEntry(item)) {
|
|
554
|
+
return { ok: false, reason: "missing-fields" };
|
|
555
|
+
}
|
|
556
|
+
if (typeof item.projectPath === "string" && options.isThisWorkspace && !options.isThisWorkspace(item.projectPath)) {
|
|
557
|
+
continue;
|
|
558
|
+
}
|
|
559
|
+
const scope = item.scope;
|
|
560
|
+
if (!isKnownScope(scope)) {
|
|
561
|
+
otherScopeIds.push(item.id);
|
|
562
|
+
continue;
|
|
563
|
+
}
|
|
564
|
+
entries.push({
|
|
565
|
+
id: item.id,
|
|
566
|
+
version: item.version,
|
|
567
|
+
scope,
|
|
568
|
+
enabled: item.enabled
|
|
569
|
+
});
|
|
570
|
+
}
|
|
571
|
+
return { ok: true, entries, otherScopeIds };
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
// ../integrations/dist/hosts/claude-code/match-claude-plugin.js
|
|
575
|
+
function matchClaudePlugin(entries, pluginId) {
|
|
576
|
+
const matches = entries.filter((candidate) => candidate.id === pluginId);
|
|
577
|
+
if (matches.length === 0) {
|
|
578
|
+
return { kind: "absent" };
|
|
579
|
+
}
|
|
580
|
+
if (matches.length > 1) {
|
|
581
|
+
return { kind: "ambiguous", entries: matches };
|
|
582
|
+
}
|
|
583
|
+
return { kind: "matched", entry: matches[0] };
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
// ../integrations/dist/hosts/claude-code/claude-cli-verification-result.js
|
|
587
|
+
function evaluateClaudeCliMatch(match) {
|
|
588
|
+
switch (match.kind) {
|
|
589
|
+
case "matched":
|
|
590
|
+
return match.entry.enabled ? { status: "verified", version: match.entry.version, scope: match.entry.scope } : { status: "installed-but-disabled", scope: match.entry.scope };
|
|
591
|
+
case "absent":
|
|
592
|
+
return { status: "absent" };
|
|
593
|
+
case "ambiguous":
|
|
594
|
+
return { status: "ambiguous" };
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
// ../integrations/dist/hosts/claude-code/verify-via-claude-cli.js
|
|
599
|
+
async function verifyViaClaudeCli(runner, pluginId, cwd) {
|
|
600
|
+
let raw;
|
|
601
|
+
try {
|
|
602
|
+
raw = await runner.run({
|
|
603
|
+
executable: "claude",
|
|
604
|
+
argv: ["plugin", "list", "--json"],
|
|
605
|
+
...cwd === void 0 ? {} : { cwd },
|
|
606
|
+
timeoutMs: CLAUDE_CLI_TIMEOUT_MS,
|
|
607
|
+
stdoutLimitBytes: CLAUDE_CLI_STDOUT_LIMIT_BYTES,
|
|
608
|
+
stderrLimitBytes: CLAUDE_CLI_STDERR_LIMIT_BYTES
|
|
609
|
+
});
|
|
610
|
+
} catch {
|
|
611
|
+
return { source: "claude-cli-unavailable", reason: "not-found" };
|
|
612
|
+
}
|
|
613
|
+
if (raw.timedOut) {
|
|
614
|
+
return { source: "claude-cli-unavailable", reason: "timeout" };
|
|
615
|
+
}
|
|
616
|
+
if (raw.outputExceeded) {
|
|
617
|
+
return { source: "claude-cli-unavailable", reason: "output-too-large" };
|
|
618
|
+
}
|
|
619
|
+
if (raw.exitCode !== 0) {
|
|
620
|
+
return { source: "claude-cli-unavailable", reason: "non-zero-exit" };
|
|
621
|
+
}
|
|
622
|
+
const parsed = parseClaudePluginList(raw.stdout, cwd === void 0 ? {} : { isThisWorkspace: samePathAs(cwd) });
|
|
623
|
+
if (!parsed.ok) {
|
|
624
|
+
return {
|
|
625
|
+
source: "claude-cli-unavailable",
|
|
626
|
+
reason: parsed.reason === "invalid-json" ? "invalid-json" : "unexpected-schema"
|
|
627
|
+
};
|
|
628
|
+
}
|
|
629
|
+
const match = matchClaudePlugin(parsed.entries, pluginId);
|
|
630
|
+
if (parsed.otherScopeIds.includes(pluginId)) {
|
|
631
|
+
return match.kind === "absent" ? { source: "claude-cli-unavailable", reason: "unexpected-schema" } : { source: "claude-cli", outcome: { status: "ambiguous" } };
|
|
632
|
+
}
|
|
633
|
+
return { source: "claude-cli", outcome: evaluateClaudeCliMatch(match) };
|
|
634
|
+
}
|
|
635
|
+
function canonical(path) {
|
|
636
|
+
try {
|
|
637
|
+
return realpathSync(path);
|
|
638
|
+
} catch {
|
|
639
|
+
return resolve(path);
|
|
640
|
+
}
|
|
641
|
+
}
|
|
642
|
+
function samePathAs(workspaceRoot) {
|
|
643
|
+
const root = canonical(workspaceRoot);
|
|
644
|
+
return (projectPath) => canonical(projectPath) === root;
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
// ../integrations/dist/hosts/claude-code/verify.js
|
|
648
|
+
async function verifyClaudeMarketplacePlugin(entry, deps, context) {
|
|
649
|
+
const detect = deps.detect ?? detectWorkspacePackage;
|
|
650
|
+
const pluginId = entry.hosts["claude-code"]?.pluginId;
|
|
651
|
+
if (pluginId === void 0) {
|
|
652
|
+
throw new Error(`${entry.displayName} has no Claude Code distribution \u2014 it cannot be verified through the claude CLI.`);
|
|
653
|
+
}
|
|
654
|
+
const cliResult = await verifyViaClaudeCli(deps.runner, pluginId, context.workspaceRoot);
|
|
655
|
+
if (cliResult.source === "claude-cli") {
|
|
656
|
+
switch (cliResult.outcome.status) {
|
|
657
|
+
case "verified":
|
|
658
|
+
return {
|
|
659
|
+
source: "claude-cli",
|
|
660
|
+
result: { verified: true, resolvedVersion: cliResult.outcome.version, problems: [] }
|
|
661
|
+
};
|
|
662
|
+
case "installed-but-disabled":
|
|
663
|
+
return {
|
|
664
|
+
source: "claude-cli",
|
|
665
|
+
result: {
|
|
666
|
+
verified: false,
|
|
667
|
+
problems: [
|
|
668
|
+
`${entry.displayName} is installed (scope: ${cliResult.outcome.scope}) but currently disabled in Claude Code.`,
|
|
669
|
+
"Enable it before recording this integration \u2014 a disabled plugin is never recorded as verified."
|
|
670
|
+
]
|
|
671
|
+
}
|
|
672
|
+
};
|
|
673
|
+
case "absent":
|
|
674
|
+
return {
|
|
675
|
+
source: "claude-cli",
|
|
676
|
+
result: {
|
|
677
|
+
verified: false,
|
|
678
|
+
problems: [
|
|
679
|
+
`${pluginId} was not found in the installed Claude Code plugin list.`,
|
|
680
|
+
`Install it (${defaultRerunCommand(entry)}) or complete the manual Claude Code steps, then try again.`
|
|
681
|
+
]
|
|
682
|
+
}
|
|
683
|
+
};
|
|
684
|
+
case "ambiguous":
|
|
685
|
+
return {
|
|
686
|
+
source: "claude-cli",
|
|
687
|
+
result: {
|
|
688
|
+
verified: false,
|
|
689
|
+
problems: [
|
|
690
|
+
`Verification error: more than one installed plugin matches ${pluginId} exactly. This is ambiguous and cannot be safely resolved automatically.`
|
|
691
|
+
]
|
|
692
|
+
}
|
|
693
|
+
};
|
|
694
|
+
}
|
|
695
|
+
}
|
|
696
|
+
if (cliResult.reason === "not-found") {
|
|
697
|
+
const detected = await detect(entry, context);
|
|
698
|
+
if (detected.installed) {
|
|
699
|
+
return {
|
|
700
|
+
source: "package-json",
|
|
701
|
+
result: { verified: true, resolvedVersion: detected.installedVersion, problems: [] }
|
|
702
|
+
};
|
|
703
|
+
}
|
|
704
|
+
return {
|
|
705
|
+
source: "package-json",
|
|
706
|
+
result: {
|
|
707
|
+
verified: false,
|
|
708
|
+
problems: [
|
|
709
|
+
`The claude CLI was not found, and ${entry.displayName} was not found as a workspace dependency either.`,
|
|
710
|
+
`Install the Claude Code CLI to use the authoritative verification signal, or add ${entry.displayName} as a workspace dependency for the weaker fallback signal.`
|
|
711
|
+
]
|
|
712
|
+
}
|
|
713
|
+
};
|
|
714
|
+
}
|
|
715
|
+
return {
|
|
716
|
+
source: "claude-cli",
|
|
717
|
+
result: {
|
|
718
|
+
verified: false,
|
|
719
|
+
problems: [`Verification error: the claude CLI returned an unexpected result (${cliResult.reason}).`]
|
|
720
|
+
}
|
|
721
|
+
};
|
|
722
|
+
}
|
|
723
|
+
function createClaudeMarketplaceVerifier(entry, deps) {
|
|
724
|
+
return async (context) => (await verifyClaudeMarketplacePlugin(entry, deps, context)).result;
|
|
725
|
+
}
|
|
726
|
+
|
|
727
|
+
// ../integrations/dist/catalog/create-catalog-integration.js
|
|
728
|
+
function createCatalogIntegration(entry, deps) {
|
|
729
|
+
return {
|
|
730
|
+
id: entry.id,
|
|
731
|
+
displayName: entry.displayName,
|
|
732
|
+
homepage: entry.homepage,
|
|
733
|
+
repository: entry.repository,
|
|
734
|
+
license: entry.license,
|
|
735
|
+
supportedEnvironments: PLUGIN_HOSTS.filter((host) => entry.hosts[host] !== void 0),
|
|
736
|
+
detect: (context) => detectWorkspacePackage(entry, context),
|
|
737
|
+
plan: createClaudeMarketplacePlan(entry),
|
|
738
|
+
install: createClaudeMarketplaceInstall(entry, { runner: deps.runner }),
|
|
739
|
+
verify: createClaudeMarketplaceVerifier(entry, { runner: deps.runner }),
|
|
740
|
+
uninstallInstructions: async () => [...entry.uninstallSteps]
|
|
741
|
+
};
|
|
742
|
+
}
|
|
743
|
+
function createCatalogIntegrations(deps) {
|
|
744
|
+
return PLUGIN_CATALOG.map((entry) => createCatalogIntegration(entry, deps));
|
|
745
|
+
}
|
|
746
|
+
|
|
747
|
+
// src/generators/install-integration/render.ts
|
|
748
|
+
function describeState(state) {
|
|
749
|
+
switch (state.kind) {
|
|
750
|
+
case "absent":
|
|
751
|
+
return "not installed";
|
|
752
|
+
case "already-installed":
|
|
753
|
+
return `already installed and enabled at ${state.scope} scope${state.version ? ` (version ${state.version})` : ""}`;
|
|
754
|
+
case "installed-other-scope":
|
|
755
|
+
return `installed and enabled, but at ${state.installedScope} scope`;
|
|
756
|
+
case "disabled":
|
|
757
|
+
return "installed but disabled";
|
|
758
|
+
case "ambiguous":
|
|
759
|
+
return "more than one installed plugin matches exactly";
|
|
760
|
+
case "cli-not-found":
|
|
761
|
+
return "unknown \u2014 the claude CLI was not found";
|
|
762
|
+
case "cli-error":
|
|
763
|
+
return `unknown \u2014 the claude CLI could not list plugins (${state.reason})`;
|
|
764
|
+
}
|
|
765
|
+
}
|
|
766
|
+
function describeInstall(detail) {
|
|
767
|
+
switch (detail.kind) {
|
|
768
|
+
case "executed":
|
|
769
|
+
return "the official command exited successfully";
|
|
770
|
+
case "manual-only":
|
|
771
|
+
return "no scriptable command exists for this target \u2014 nothing was executed";
|
|
772
|
+
case "cli-not-found":
|
|
773
|
+
return "not executed \u2014 the claude CLI was not found";
|
|
774
|
+
case "failed-to-start":
|
|
775
|
+
return `not executed \u2014 the claude CLI could not be started (${detail.code})`;
|
|
776
|
+
case "cli-unsupported":
|
|
777
|
+
return "not executed \u2014 this claude CLI does not support scriptable plugin install";
|
|
778
|
+
case "confirmation-required":
|
|
779
|
+
return "stopped \u2014 the marketplace declares a command that a person must confirm";
|
|
780
|
+
case "failed":
|
|
781
|
+
return `failed${detail.failureCode ? ` (${detail.failureCode})` : ""}`;
|
|
782
|
+
case "timeout":
|
|
783
|
+
return "stopped \u2014 the command timed out";
|
|
784
|
+
case "output-too-large":
|
|
785
|
+
return "stopped \u2014 the command produced too much output";
|
|
786
|
+
}
|
|
787
|
+
}
|
|
788
|
+
function describeDeclaration(declaration, tool, preview) {
|
|
789
|
+
switch (declaration.action) {
|
|
790
|
+
case "updated":
|
|
791
|
+
return `toolkit.config.json: "${tool}" ${preview ? "would be added" : "added"} to integrations.enabled`;
|
|
792
|
+
case "unchanged":
|
|
793
|
+
return `toolkit.config.json: "${tool}" is already declared in integrations.enabled (unchanged)`;
|
|
794
|
+
case "no-config":
|
|
795
|
+
return `toolkit.config.json: not declared \u2014 this workspace has no toolkit.config.json (it was not set up with init). Add "${tool}" to integrations.enabled there if you want doctor to track the declaration.`;
|
|
796
|
+
case "unrecognized":
|
|
797
|
+
return `toolkit.config.json: not updated \u2014 the file is not in a shape nx-archweave recognizes, so it was left alone. Add "${tool}" to integrations.enabled by hand.`;
|
|
798
|
+
case "write-failed":
|
|
799
|
+
return `toolkit.config.json: not updated \u2014 writing it failed (${declaration.message}). Add "${tool}" to integrations.enabled by hand.`;
|
|
800
|
+
}
|
|
801
|
+
}
|
|
802
|
+
function renderInstallIntegrationView(view) {
|
|
803
|
+
const lines = [
|
|
804
|
+
`Integration: ${view.tool} (a separate project \u2014 nx-archweave only runs its official installer)`,
|
|
805
|
+
`Source repository: ${view.sourceRepository}`,
|
|
806
|
+
`Official homepage: ${view.officialHomepage}`,
|
|
807
|
+
`License: ${view.license}`,
|
|
808
|
+
view.requestedVersion === null ? "Version: latest (decided by the marketplace) \u2014 the version actually installed is the one recorded" : `Requested version: ${view.requestedVersion} (not enforced \u2014 the marketplace decides; the version actually installed is the one recorded)`,
|
|
809
|
+
`Target: ${view.target}`,
|
|
810
|
+
`Scope: ${view.scope}`,
|
|
811
|
+
`Official command: ${view.command}`,
|
|
812
|
+
...view.teamMarketplace ? [`For teammates (project scope), the marketplace is declared in .claude/settings.json first: ${view.teamMarketplace.command}`] : [],
|
|
813
|
+
...view.workspaceFiles.length > 0 ? [`Claude Code will update in this workspace: ${view.workspaceFiles.join(", ")}`] : [],
|
|
814
|
+
`Current state: ${describeState(view.currentState)}`
|
|
815
|
+
];
|
|
816
|
+
if (view.applyIgnoredForNxDryRun) {
|
|
817
|
+
lines.push("Note: --apply was ignored because Nx's --dry-run is set.");
|
|
818
|
+
}
|
|
819
|
+
if (view.preview && view.currentState.kind === "absent") {
|
|
820
|
+
lines.push("", ...view.teamMarketplace ? [`Would run: ${view.teamMarketplace.command}`] : [], `Would run: ${view.command}`, `To run it: ${view.rerunCommand}`);
|
|
821
|
+
} else if (view.preview && view.teamMarketplace) {
|
|
822
|
+
lines.push("", `Would run: ${view.teamMarketplace.command}`, `To run it: ${view.rerunCommand}`);
|
|
823
|
+
}
|
|
824
|
+
if (view.teamMarketplace?.detail) {
|
|
825
|
+
const { detail } = view.teamMarketplace;
|
|
826
|
+
lines.push(
|
|
827
|
+
"",
|
|
828
|
+
detail.kind === "executed" ? "Marketplace: declared in .claude/settings.json (extraKnownMarketplaces) \u2014 teammates get its source from the committed file" : `Marketplace declaration: ${describeInstall(detail)}${detail.kind === "failed" ? ` \u2014 ${detail.message}` : ""}`
|
|
829
|
+
);
|
|
830
|
+
}
|
|
831
|
+
if (view.install) {
|
|
832
|
+
lines.push("", `Install: ${describeInstall(view.install.detail)}`);
|
|
833
|
+
}
|
|
834
|
+
if (view.record) {
|
|
835
|
+
const { record } = view;
|
|
836
|
+
lines.push(
|
|
837
|
+
"",
|
|
838
|
+
`Verification: ${record.verification.verified ? "passed" : "not verified"}`,
|
|
839
|
+
...record.verification.problems.map((problem) => ` ! ${problem}`)
|
|
840
|
+
);
|
|
841
|
+
if (record.hasConflict && record.existingEntry) {
|
|
842
|
+
lines.push(
|
|
843
|
+
"Conflict: an existing manifest entry differs from what was verified now \u2014 the fresh result replaces it.",
|
|
844
|
+
` Existing: ${JSON.stringify(record.existingEntry)}`,
|
|
845
|
+
` Verified: ${JSON.stringify(record.proposedEntry)}`
|
|
846
|
+
);
|
|
847
|
+
}
|
|
848
|
+
const installKind = view.install?.detail.kind;
|
|
849
|
+
if (record.proposedEntry) {
|
|
850
|
+
if (installKind !== void 0 && installKind !== "executed") {
|
|
851
|
+
lines.push(
|
|
852
|
+
`The install command reported ${installKind}, but a fresh check shows the plugin installed and enabled.`
|
|
853
|
+
);
|
|
854
|
+
}
|
|
855
|
+
lines.push(
|
|
856
|
+
record.wasWritten ? "Recorded \u2014 the following manifest entry was written to archweave-integrations.json:" : "archweave-integrations.json would get this entry:",
|
|
857
|
+
` ${JSON.stringify(record.proposedEntry)}`
|
|
858
|
+
);
|
|
859
|
+
} else if (installKind === "executed") {
|
|
860
|
+
lines.push("The install command exited 0, but the fresh verification did not confirm the plugin \u2014 nothing was written.");
|
|
861
|
+
} else {
|
|
862
|
+
lines.push("Nothing was written \u2014 verification did not succeed via the authoritative claude-cli signal.");
|
|
863
|
+
}
|
|
864
|
+
}
|
|
865
|
+
if (view.declaration) {
|
|
866
|
+
lines.push(describeDeclaration(view.declaration, view.tool, view.preview));
|
|
867
|
+
}
|
|
868
|
+
if (view.guidance.length > 0) {
|
|
869
|
+
lines.push("", "Next steps:", ...view.guidance.map((step) => ` - ${step}`));
|
|
870
|
+
}
|
|
871
|
+
lines.push(
|
|
872
|
+
"",
|
|
873
|
+
view.preview ? "(preview \u2014 only read-only checks ran; nothing was installed and no file was written)" : "(applied \u2014 the lines above state exactly what was executed and written)"
|
|
874
|
+
);
|
|
875
|
+
return lines.join("\n");
|
|
876
|
+
}
|
|
877
|
+
|
|
878
|
+
// src/generators/shared/record-pipeline.ts
|
|
879
|
+
async function runRecordPipeline(input) {
|
|
880
|
+
const entry = getCatalogEntry(input.id);
|
|
881
|
+
const metadata = entry === void 0 ? void 0 : recordMetadataFor(entry);
|
|
882
|
+
if (!entry || !metadata) {
|
|
883
|
+
throw new Error(`No manifest metadata is defined for "${input.id}".`);
|
|
884
|
+
}
|
|
885
|
+
const { result: verification, source: verificationSource } = await verifyClaudeMarketplacePlugin(
|
|
886
|
+
entry,
|
|
887
|
+
{ runner: input.runner },
|
|
888
|
+
{ workspaceRoot: input.workspaceRoot, targetEnvironment: input.target }
|
|
889
|
+
);
|
|
890
|
+
const manifest = loadManifest(input.workspaceRoot);
|
|
891
|
+
const existingEntry = manifest.integrations[input.id];
|
|
892
|
+
const plan = buildRecordPlan({ id: input.id, target: input.target, verificationSource, verification, existingEntry, metadata });
|
|
893
|
+
let wasWritten = false;
|
|
894
|
+
if (input.write && plan.proposedEntry) {
|
|
895
|
+
writeManifestAtomic(input.workspaceRoot, {
|
|
896
|
+
integrations: { ...manifest.integrations, [input.id]: plan.proposedEntry }
|
|
897
|
+
});
|
|
898
|
+
wasWritten = true;
|
|
899
|
+
}
|
|
900
|
+
return {
|
|
901
|
+
verificationSource,
|
|
902
|
+
verification,
|
|
903
|
+
existingEntry,
|
|
904
|
+
proposedEntry: plan.proposedEntry,
|
|
905
|
+
hasConflict: plan.hasConflict,
|
|
906
|
+
wasWritten
|
|
907
|
+
};
|
|
908
|
+
}
|
|
909
|
+
|
|
910
|
+
// src/generators/shared/run-nx.ts
|
|
911
|
+
import { existsSync as existsSync4, readFileSync as readFileSync4 } from "node:fs";
|
|
912
|
+
import { join as join4 } from "node:path";
|
|
913
|
+
|
|
914
|
+
// ../template-engine/dist/environment/package-manager.js
|
|
915
|
+
var LOCKFILES = [
|
|
916
|
+
["yarn", ["yarn.lock"]],
|
|
917
|
+
["bun", ["bun.lock", "bun.lockb"]],
|
|
918
|
+
["npm", ["package-lock.json"]]
|
|
919
|
+
];
|
|
920
|
+
function detectWorkspacePackageManager(exists, read = () => void 0) {
|
|
921
|
+
if (exists("pnpm-lock.yaml") || exists("pnpm-workspace.yaml"))
|
|
922
|
+
return "pnpm";
|
|
923
|
+
const found = LOCKFILES.filter(([, files]) => files.some((file) => exists(file))).map(([pm]) => pm);
|
|
924
|
+
if (found.length <= 1)
|
|
925
|
+
return found[0] ?? null;
|
|
926
|
+
const declared = /"packageManager"\s*:\s*"(npm|yarn|bun)@/.exec(read("package.json") ?? "")?.[1];
|
|
927
|
+
const named = found.find((pm) => pm === declared);
|
|
928
|
+
if (named)
|
|
929
|
+
return named;
|
|
930
|
+
return found.includes("npm") ? "npm" : found[0];
|
|
931
|
+
}
|
|
932
|
+
function packageManagerCommands(pm, flavor = "classic") {
|
|
933
|
+
switch (pm) {
|
|
934
|
+
case "pnpm":
|
|
935
|
+
return { addDevAtRoot: (spec) => `pnpm add -D -w ${spec}`, addAtRoot: (spec) => `pnpm add -w ${spec}`, runNx: "pnpm nx", install: "pnpm install", workspaceRegistration: "pnpm-workspace.yaml" };
|
|
936
|
+
case "yarn":
|
|
937
|
+
return {
|
|
938
|
+
addDevAtRoot: (spec) => flavor === "berry" ? `yarn add -D ${spec}` : `yarn add -D -W ${spec}`,
|
|
939
|
+
addAtRoot: (spec) => flavor === "berry" ? `yarn add ${spec}` : `yarn add -W ${spec}`,
|
|
940
|
+
runNx: "yarn nx",
|
|
941
|
+
install: "yarn install",
|
|
942
|
+
workspaceRegistration: "package.json workspaces"
|
|
943
|
+
};
|
|
944
|
+
case "bun":
|
|
945
|
+
return { addDevAtRoot: (spec) => `bun add -d ${spec}`, addAtRoot: (spec) => `bun add ${spec}`, runNx: "bun nx", install: "bun install", workspaceRegistration: "package.json workspaces" };
|
|
946
|
+
case "npm":
|
|
947
|
+
return { addDevAtRoot: (spec) => `npm install -D ${spec}`, addAtRoot: (spec) => `npm install ${spec}`, runNx: "npm exec nx --", install: "npm install", workspaceRegistration: "package.json workspaces" };
|
|
948
|
+
}
|
|
949
|
+
}
|
|
950
|
+
|
|
951
|
+
// src/generators/shared/toolkit-config-file.ts
|
|
952
|
+
var TOOLKIT_CONFIG_PATH = "toolkit.config.json";
|
|
953
|
+
function recordedChoice(config, key, allowed) {
|
|
954
|
+
const value = config?.[key];
|
|
955
|
+
return typeof value === "string" && allowed.includes(value) ? value : void 0;
|
|
956
|
+
}
|
|
957
|
+
var RECORDED_PACKAGE_MANAGERS = ["pnpm", "npm", "yarn", "bun"];
|
|
958
|
+
function workspacePackageManager(exists, read, config) {
|
|
959
|
+
return detectWorkspacePackageManager(exists, read) ?? recordedChoice(config, "packageManager", RECORDED_PACKAGE_MANAGERS) ?? null;
|
|
960
|
+
}
|
|
961
|
+
|
|
962
|
+
// src/generators/shared/run-nx.ts
|
|
963
|
+
function runNxFor(exists, read) {
|
|
964
|
+
let config = null;
|
|
965
|
+
try {
|
|
966
|
+
const parsed = JSON.parse(read(TOOLKIT_CONFIG_PATH) ?? "null");
|
|
967
|
+
if (typeof parsed === "object" && parsed !== null && !Array.isArray(parsed)) config = parsed;
|
|
968
|
+
} catch {
|
|
969
|
+
}
|
|
970
|
+
return packageManagerCommands(workspacePackageManager(exists, read, config) ?? "pnpm").runNx;
|
|
971
|
+
}
|
|
972
|
+
function diskRunNx(root) {
|
|
973
|
+
return runNxFor(
|
|
974
|
+
(path) => existsSync4(join4(root, path)),
|
|
975
|
+
(path) => existsSync4(join4(root, path)) ? readFileSync4(join4(root, path), "utf-8") : void 0
|
|
976
|
+
);
|
|
977
|
+
}
|
|
978
|
+
|
|
979
|
+
// src/runtime/node-command-runner.ts
|
|
980
|
+
import { spawn } from "node:child_process";
|
|
981
|
+
var KILL_GRACE_MS = 500;
|
|
982
|
+
var NodeCommandRunner = class {
|
|
983
|
+
run(input) {
|
|
984
|
+
return new Promise((resolve2, reject) => {
|
|
985
|
+
let stdout = "";
|
|
986
|
+
let stderr = "";
|
|
987
|
+
let timedOut = false;
|
|
988
|
+
let outputExceeded = false;
|
|
989
|
+
let settled = false;
|
|
990
|
+
let killTimer;
|
|
991
|
+
const spawnable = spawnableCommand(input.executable, input.argv);
|
|
992
|
+
const child = spawn(spawnable.command, spawnable.args, {
|
|
993
|
+
cwd: input.cwd,
|
|
994
|
+
shell: false,
|
|
995
|
+
windowsVerbatimArguments: spawnable.windowsVerbatimArguments,
|
|
996
|
+
windowsHide: true,
|
|
997
|
+
// No stdin: nothing here ever answers a prompt (a command that wants
|
|
998
|
+
// confirmation must fail fast instead of waiting on an open pipe).
|
|
999
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
1000
|
+
});
|
|
1001
|
+
const terminate = () => {
|
|
1002
|
+
child.kill("SIGTERM");
|
|
1003
|
+
killTimer = setTimeout(() => {
|
|
1004
|
+
child.kill("SIGKILL");
|
|
1005
|
+
}, KILL_GRACE_MS);
|
|
1006
|
+
};
|
|
1007
|
+
const timer = setTimeout(() => {
|
|
1008
|
+
timedOut = true;
|
|
1009
|
+
terminate();
|
|
1010
|
+
}, input.timeoutMs);
|
|
1011
|
+
child.stdout.on("data", (chunk) => {
|
|
1012
|
+
stdout += chunk.toString("utf8");
|
|
1013
|
+
if (Buffer.byteLength(stdout, "utf8") > input.stdoutLimitBytes) {
|
|
1014
|
+
outputExceeded = true;
|
|
1015
|
+
terminate();
|
|
1016
|
+
}
|
|
1017
|
+
});
|
|
1018
|
+
child.stderr.on("data", (chunk) => {
|
|
1019
|
+
stderr += chunk.toString("utf8");
|
|
1020
|
+
if (Buffer.byteLength(stderr, "utf8") > input.stderrLimitBytes) {
|
|
1021
|
+
outputExceeded = true;
|
|
1022
|
+
terminate();
|
|
1023
|
+
}
|
|
1024
|
+
});
|
|
1025
|
+
child.on("error", (error) => {
|
|
1026
|
+
if (settled) return;
|
|
1027
|
+
settled = true;
|
|
1028
|
+
clearTimeout(timer);
|
|
1029
|
+
clearTimeout(killTimer);
|
|
1030
|
+
reject(error);
|
|
1031
|
+
});
|
|
1032
|
+
child.on("close", (code) => {
|
|
1033
|
+
if (settled) return;
|
|
1034
|
+
settled = true;
|
|
1035
|
+
clearTimeout(timer);
|
|
1036
|
+
clearTimeout(killTimer);
|
|
1037
|
+
resolve2({ stdout, stderr, exitCode: code, timedOut, outputExceeded });
|
|
1038
|
+
});
|
|
1039
|
+
});
|
|
1040
|
+
}
|
|
1041
|
+
};
|
|
1042
|
+
|
|
1043
|
+
// src/runtime/integrations-composition-root.ts
|
|
1044
|
+
function createIntegrationsCompositionRoot(deps = {}) {
|
|
1045
|
+
const runner = deps.runner ?? new NodeCommandRunner();
|
|
1046
|
+
const registry = createIntegrationRegistry(createCatalogIntegrations({ runner }));
|
|
1047
|
+
return {
|
|
1048
|
+
registry,
|
|
1049
|
+
runner,
|
|
1050
|
+
getIntegration: (id) => getIntegration(registry, id),
|
|
1051
|
+
listIntegrations: () => listIntegrations(registry)
|
|
1052
|
+
};
|
|
1053
|
+
}
|
|
1054
|
+
|
|
1055
|
+
// src/generators/install-integration/generator.ts
|
|
1056
|
+
var RECHECK_AFTER = /* @__PURE__ */ new Set([
|
|
1057
|
+
"executed",
|
|
1058
|
+
"failed",
|
|
1059
|
+
"timeout",
|
|
1060
|
+
"output-too-large",
|
|
1061
|
+
"confirmation-required"
|
|
1062
|
+
]);
|
|
1063
|
+
async function runInstallIntegration(tree, options, deps = {}) {
|
|
1064
|
+
const root = deps.integrationsRoot ?? createIntegrationsCompositionRoot();
|
|
1065
|
+
const declare = deps.declare ?? declareEnabledIntegration;
|
|
1066
|
+
const target = options.target ?? "claude-code";
|
|
1067
|
+
const scope = options.scope ?? DEFAULT_PLUGIN_SCOPE;
|
|
1068
|
+
const nxDryRun = process.env["NX_DRY_RUN"] === "true";
|
|
1069
|
+
const apply = (options.apply ?? false) && !nxDryRun;
|
|
1070
|
+
const nxG = `${diskRunNx(tree.root)} g`;
|
|
1071
|
+
const rerunCommand = `${nxG} @clearlydev/nx-archweave:install-integration --tool=${options.tool} --apply` + (options.scope === void 0 ? "" : ` --scope=${options.scope}`);
|
|
1072
|
+
const integration = root.getIntegration(options.tool);
|
|
1073
|
+
const entry = getCatalogEntry(options.tool);
|
|
1074
|
+
const pluginId = entry?.hosts["claude-code"]?.pluginId;
|
|
1075
|
+
if (!integration || !entry || pluginId === void 0) {
|
|
1076
|
+
throw new Error(`Unsupported integration "${options.tool}". Supported: ${PLUGIN_IDS.join(", ")}.`);
|
|
1077
|
+
}
|
|
1078
|
+
const name = entry.displayName;
|
|
1079
|
+
const plan = await integration.plan({
|
|
1080
|
+
targetEnvironment: target,
|
|
1081
|
+
versionRange: options.versionRange,
|
|
1082
|
+
scope
|
|
1083
|
+
});
|
|
1084
|
+
const pre = await verifyViaClaudeCli(root.runner, pluginId, tree.root);
|
|
1085
|
+
let currentState;
|
|
1086
|
+
if (pre.source === "claude-cli") {
|
|
1087
|
+
const outcome = pre.outcome;
|
|
1088
|
+
switch (outcome.status) {
|
|
1089
|
+
case "verified":
|
|
1090
|
+
currentState = options.scope !== void 0 && outcome.scope !== options.scope ? { kind: "installed-other-scope", installedScope: outcome.scope } : { kind: "already-installed", version: outcome.version, scope: outcome.scope };
|
|
1091
|
+
break;
|
|
1092
|
+
case "installed-but-disabled":
|
|
1093
|
+
currentState = { kind: "disabled" };
|
|
1094
|
+
break;
|
|
1095
|
+
case "absent":
|
|
1096
|
+
currentState = { kind: "absent" };
|
|
1097
|
+
break;
|
|
1098
|
+
case "ambiguous":
|
|
1099
|
+
currentState = { kind: "ambiguous" };
|
|
1100
|
+
break;
|
|
1101
|
+
}
|
|
1102
|
+
} else {
|
|
1103
|
+
currentState = pre.reason === "not-found" ? { kind: "cli-not-found" } : { kind: "cli-error", reason: pre.reason };
|
|
1104
|
+
}
|
|
1105
|
+
const view = {
|
|
1106
|
+
tool: options.tool,
|
|
1107
|
+
target,
|
|
1108
|
+
scope,
|
|
1109
|
+
preview: !apply,
|
|
1110
|
+
applyIgnoredForNxDryRun: (options.apply ?? false) && nxDryRun,
|
|
1111
|
+
sourceRepository: plan.sourceRepository,
|
|
1112
|
+
officialHomepage: plan.officialHomepage,
|
|
1113
|
+
license: plan.license,
|
|
1114
|
+
requestedVersion: options.versionRange ?? null,
|
|
1115
|
+
workspaceFiles: plan.filesModified,
|
|
1116
|
+
command: plan.officialCommand === null ? "(none \u2014 manual install only)" : renderUserCommand(plan.officialCommand),
|
|
1117
|
+
rerunCommand,
|
|
1118
|
+
currentState,
|
|
1119
|
+
install: null,
|
|
1120
|
+
record: null,
|
|
1121
|
+
declaration: null,
|
|
1122
|
+
teamMarketplace: null,
|
|
1123
|
+
guidance: []
|
|
1124
|
+
};
|
|
1125
|
+
const teamCommand = projectMarketplaceCommand(entry);
|
|
1126
|
+
const planTeamDeclaration = () => {
|
|
1127
|
+
if (teamCommand === null) return false;
|
|
1128
|
+
view.teamMarketplace = { command: renderUserCommand(teamCommand), detail: null };
|
|
1129
|
+
return true;
|
|
1130
|
+
};
|
|
1131
|
+
const declareForTeam = async () => {
|
|
1132
|
+
if (!apply || view.teamMarketplace === null) return true;
|
|
1133
|
+
const detail = await declareProjectMarketplace(entry, { runner: root.runner }, { cwd: tree.root });
|
|
1134
|
+
view.teamMarketplace.detail = detail;
|
|
1135
|
+
return detail.kind === "executed";
|
|
1136
|
+
};
|
|
1137
|
+
const teamFailureStep = () => `Declaring the marketplace for teammates did not succeed \u2014 run it yourself to see why: ${view.teamMarketplace?.command}`;
|
|
1138
|
+
const recordAndDeclare = async () => {
|
|
1139
|
+
const outcome = await runRecordPipeline({
|
|
1140
|
+
id: integration.id,
|
|
1141
|
+
runner: root.runner,
|
|
1142
|
+
workspaceRoot: tree.root,
|
|
1143
|
+
target,
|
|
1144
|
+
write: apply
|
|
1145
|
+
});
|
|
1146
|
+
view.record = {
|
|
1147
|
+
verification: outcome.verification,
|
|
1148
|
+
existingEntry: outcome.existingEntry,
|
|
1149
|
+
proposedEntry: outcome.proposedEntry,
|
|
1150
|
+
hasConflict: outcome.hasConflict,
|
|
1151
|
+
wasWritten: outcome.wasWritten
|
|
1152
|
+
};
|
|
1153
|
+
if (outcome.proposedEntry) {
|
|
1154
|
+
try {
|
|
1155
|
+
view.declaration = declare(tree.root, integration.id, { write: apply });
|
|
1156
|
+
} catch (error) {
|
|
1157
|
+
view.declaration = {
|
|
1158
|
+
action: "write-failed",
|
|
1159
|
+
message: redactHomePaths(error instanceof Error ? error.message : String(error))
|
|
1160
|
+
};
|
|
1161
|
+
}
|
|
1162
|
+
}
|
|
1163
|
+
};
|
|
1164
|
+
switch (currentState.kind) {
|
|
1165
|
+
case "already-installed":
|
|
1166
|
+
if (currentState.scope === "project" && planTeamDeclaration() && !await declareForTeam()) {
|
|
1167
|
+
view.guidance = [teamFailureStep()];
|
|
1168
|
+
}
|
|
1169
|
+
await recordAndDeclare();
|
|
1170
|
+
break;
|
|
1171
|
+
case "installed-other-scope":
|
|
1172
|
+
view.guidance = currentState.installedScope === "managed" ? [
|
|
1173
|
+
`${name} is provided at managed scope by your organization and cannot be installed again at another scope.`,
|
|
1174
|
+
`Record the managed install instead (nothing is installed again): ${nxG} @clearlydev/nx-archweave:install-integration --tool=${options.tool} --apply`
|
|
1175
|
+
] : [
|
|
1176
|
+
`${name} is already installed at ${currentState.installedScope} scope. Installing it again at ${scope} scope would leave two entries that verification cannot tell apart, so nothing was done.`,
|
|
1177
|
+
`To record the existing install (nothing is installed again): ${nxG} @clearlydev/nx-archweave:install-integration --tool=${options.tool} --apply --scope=${currentState.installedScope}`,
|
|
1178
|
+
`To move it instead: claude plugin uninstall ${pluginId} --scope ${currentState.installedScope}, then rerun: ${rerunCommand}`
|
|
1179
|
+
];
|
|
1180
|
+
break;
|
|
1181
|
+
case "disabled":
|
|
1182
|
+
view.guidance = [
|
|
1183
|
+
`${name} is installed but disabled. Enable it yourself (nx-archweave never enables or disables plugins): claude plugin enable ${pluginId}`,
|
|
1184
|
+
`Then rerun to record and declare it: ${rerunCommand}`
|
|
1185
|
+
];
|
|
1186
|
+
break;
|
|
1187
|
+
case "ambiguous":
|
|
1188
|
+
view.guidance = [
|
|
1189
|
+
`More than one installed plugin matches ${pluginId} exactly (for example at user and project scope), so nothing was done.`,
|
|
1190
|
+
`Remove the extra one (claude plugin uninstall ${pluginId} --scope <scope>), then rerun: ${rerunCommand}`
|
|
1191
|
+
];
|
|
1192
|
+
break;
|
|
1193
|
+
case "cli-not-found":
|
|
1194
|
+
view.guidance = claudeCliNotFoundSteps(entry, rerunCommand);
|
|
1195
|
+
break;
|
|
1196
|
+
case "cli-error":
|
|
1197
|
+
view.guidance = [
|
|
1198
|
+
"The claude CLI is present but could not list its plugins, so no install was attempted.",
|
|
1199
|
+
`Run \`claude plugin list\` yourself to see the error, fix it, and rerun: ${rerunCommand}`
|
|
1200
|
+
];
|
|
1201
|
+
break;
|
|
1202
|
+
case "absent": {
|
|
1203
|
+
if (scope === "project") planTeamDeclaration();
|
|
1204
|
+
if (!apply) break;
|
|
1205
|
+
if (!await declareForTeam()) {
|
|
1206
|
+
view.guidance = [teamFailureStep(), `Nothing was installed. Once resolved, rerun: ${rerunCommand}`];
|
|
1207
|
+
break;
|
|
1208
|
+
}
|
|
1209
|
+
const approved = { ...plan, approvedBy: "user" };
|
|
1210
|
+
const { result, detail } = await installClaudeMarketplacePlugin(entry, { runner: root.runner }, approved, {
|
|
1211
|
+
cwd: tree.root,
|
|
1212
|
+
rerunCommand
|
|
1213
|
+
});
|
|
1214
|
+
view.install = { detail, manualStepsRemaining: result.manualStepsRemaining };
|
|
1215
|
+
if (RECHECK_AFTER.has(detail.kind)) await recordAndDeclare();
|
|
1216
|
+
if (view.record?.proposedEntry) break;
|
|
1217
|
+
view.guidance = detail.kind === "executed" ? [
|
|
1218
|
+
`Run \`claude plugin list\` to see what was installed; once it shows ${pluginId} enabled, rerun: ${rerunCommand}`
|
|
1219
|
+
] : result.manualStepsRemaining;
|
|
1220
|
+
break;
|
|
1221
|
+
}
|
|
1222
|
+
}
|
|
1223
|
+
return view;
|
|
1224
|
+
}
|
|
1225
|
+
async function installIntegrationGenerator(tree, options, deps = {}) {
|
|
1226
|
+
console.log(renderInstallIntegrationView(await runInstallIntegration(tree, options, deps)));
|
|
1227
|
+
}
|
|
1228
|
+
var generator_default = installIntegrationGenerator;
|
|
1229
|
+
export {
|
|
1230
|
+
generator_default as default,
|
|
1231
|
+
installIntegrationGenerator,
|
|
1232
|
+
runInstallIntegration
|
|
1233
|
+
};
|