@f5-sales-demo/xcsh 22.4.2 → 22.4.5
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/package.json +8 -8
- package/src/cli/linux-standalone-update.ts +118 -0
- package/src/cli/update-cli.ts +11 -0
- package/src/cli.ts +4 -1
- package/src/extensibility/extensions/loader.ts +37 -5
- package/src/internal-urls/api-catalog-qmd-index.generated.ts +2 -2
- package/src/internal-urls/build-info.generated.ts +8 -8
- package/src/internal-urls/docs-index.generated.ts +1 -1
- package/src/internal-urls/terraform-index.generated.ts +16 -1
- package/src/modes/components/btw-panel.ts +8 -6
- package/src/modes/components/plan-preview.ts +2 -6
- package/src/modes/components/question-transcript.ts +7 -19
- package/src/modes/components/searchable-select-list.ts +20 -6
- package/src/modes/components/selector-frame.ts +72 -4
- package/src/modes/components/transcript-notice.ts +8 -16
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"type": "module",
|
|
3
3
|
"name": "@f5-sales-demo/xcsh",
|
|
4
|
-
"version": "22.4.
|
|
4
|
+
"version": "22.4.5",
|
|
5
5
|
"description": "Coding agent CLI with read, bash, edit, write tools and session management",
|
|
6
6
|
"homepage": "https://github.com/f5-sales-demo/xcsh",
|
|
7
7
|
"author": "Can Boluk",
|
|
@@ -67,13 +67,13 @@
|
|
|
67
67
|
},
|
|
68
68
|
"dependencies": {
|
|
69
69
|
"@agentclientprotocol/sdk": "1.4.0",
|
|
70
|
-
"@f5-sales-demo/pi-agent-core": "22.4.
|
|
71
|
-
"@f5-sales-demo/pi-ai": "22.4.
|
|
72
|
-
"@f5-sales-demo/pi-natives": "22.4.
|
|
73
|
-
"@f5-sales-demo/pi-resource-management": "22.4.
|
|
74
|
-
"@f5-sales-demo/pi-tui": "22.4.
|
|
75
|
-
"@f5-sales-demo/pi-utils": "22.4.
|
|
76
|
-
"@f5-sales-demo/xcsh-stats": "22.4.
|
|
70
|
+
"@f5-sales-demo/pi-agent-core": "22.4.5",
|
|
71
|
+
"@f5-sales-demo/pi-ai": "22.4.5",
|
|
72
|
+
"@f5-sales-demo/pi-natives": "22.4.5",
|
|
73
|
+
"@f5-sales-demo/pi-resource-management": "22.4.5",
|
|
74
|
+
"@f5-sales-demo/pi-tui": "22.4.5",
|
|
75
|
+
"@f5-sales-demo/pi-utils": "22.4.5",
|
|
76
|
+
"@f5-sales-demo/xcsh-stats": "22.4.5",
|
|
77
77
|
"@mozilla/readability": "^0.6",
|
|
78
78
|
"@resvg/resvg-wasm": "2.6.2",
|
|
79
79
|
"@sinclair/typebox": "0.34.52",
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
|
|
4
|
+
const REPO = "f5-sales-demo/xcsh";
|
|
5
|
+
const MAX_INSTALLER_BYTES = 256 * 1024;
|
|
6
|
+
|
|
7
|
+
export interface LinuxInstallerInput {
|
|
8
|
+
script: Buffer;
|
|
9
|
+
installDir: string;
|
|
10
|
+
version: string;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export interface LinuxStandaloneUpdateOptions {
|
|
14
|
+
targetPath: string;
|
|
15
|
+
arch?: string;
|
|
16
|
+
fetchImpl?(input: string | URL | Request, init?: RequestInit): Promise<Response>;
|
|
17
|
+
runInstaller?(input: LinuxInstallerInput): Promise<void>;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
async function runInstaller(input: LinuxInstallerInput): Promise<void> {
|
|
21
|
+
const child = Bun.spawn(["/bin/sh", "-s", "--", "--binary", "--ref", `v${input.version}`], {
|
|
22
|
+
stdin: "pipe",
|
|
23
|
+
stdout: "inherit",
|
|
24
|
+
stderr: "inherit",
|
|
25
|
+
env: { ...process.env, PI_INSTALL_DIR: input.installDir },
|
|
26
|
+
});
|
|
27
|
+
try {
|
|
28
|
+
child.stdin.write(input.script);
|
|
29
|
+
await child.stdin.end();
|
|
30
|
+
} catch (error) {
|
|
31
|
+
child.kill();
|
|
32
|
+
await child.exited;
|
|
33
|
+
throw error;
|
|
34
|
+
}
|
|
35
|
+
const exitCode = await child.exited;
|
|
36
|
+
if (exitCode !== 0) throw new Error(`Verified Linux installer exited with status ${exitCode}`);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** Use the published installer's complete integrity transaction for every Linux replacement. */
|
|
40
|
+
export async function updateLinuxStandalone(version: string, options: LinuxStandaloneUpdateOptions): Promise<void> {
|
|
41
|
+
if (!/^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)$/.test(version))
|
|
42
|
+
throw new Error("A stable published version is required for Linux standalone updates");
|
|
43
|
+
if (!path.posix.isAbsolute(options.targetPath) || path.posix.basename(options.targetPath) !== "xcsh")
|
|
44
|
+
throw new Error("Linux standalone target must be an absolute xcsh executable path");
|
|
45
|
+
const arch = options.arch ?? process.arch;
|
|
46
|
+
if (arch !== "x64" && arch !== "arm64") throw new Error("Unsupported Linux standalone update architecture");
|
|
47
|
+
|
|
48
|
+
const fetchImpl = options.fetchImpl ?? fetch;
|
|
49
|
+
const tag = `v${version}`;
|
|
50
|
+
const api = `https://api.github.com/repos/${REPO}`;
|
|
51
|
+
const releaseResponse = await fetchImpl(`${api}/releases/tags/${tag}`, {
|
|
52
|
+
signal: AbortSignal.timeout(30_000),
|
|
53
|
+
});
|
|
54
|
+
if (!releaseResponse.ok) throw new Error(`Cannot read immutable release metadata: ${releaseResponse.status}`);
|
|
55
|
+
const release = (await releaseResponse.json()) as {
|
|
56
|
+
tag_name?: unknown;
|
|
57
|
+
draft?: unknown;
|
|
58
|
+
prerelease?: unknown;
|
|
59
|
+
immutable?: unknown;
|
|
60
|
+
assets?: unknown;
|
|
61
|
+
} | null;
|
|
62
|
+
if (
|
|
63
|
+
release?.tag_name !== tag ||
|
|
64
|
+
release.draft !== false ||
|
|
65
|
+
release.prerelease !== false ||
|
|
66
|
+
release.immutable !== true
|
|
67
|
+
)
|
|
68
|
+
throw new Error("Linux self-update requires the exact published immutable release");
|
|
69
|
+
if (!Array.isArray(release.assets)) throw new Error("Linux release is missing integrity assets");
|
|
70
|
+
const binary = `xcsh-linux-${arch}`;
|
|
71
|
+
for (const name of [binary, `${binary}.sha256`, `${binary}.provenance.json`, `${binary}.provenance.json.sha256`]) {
|
|
72
|
+
const matches = release.assets.filter(asset => asset?.name === name);
|
|
73
|
+
if (
|
|
74
|
+
matches.length !== 1 ||
|
|
75
|
+
typeof matches[0].digest !== "string" ||
|
|
76
|
+
!/^sha256:[a-f0-9]{64}$/.test(matches[0].digest) ||
|
|
77
|
+
!Number.isInteger(matches[0].size) ||
|
|
78
|
+
matches[0].size <= 0
|
|
79
|
+
)
|
|
80
|
+
throw new Error("Linux release has incomplete or ambiguous integrity assets");
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const sourceResponse = await fetchImpl(`${api}/contents/scripts/install.sh?ref=${tag}`, {
|
|
84
|
+
signal: AbortSignal.timeout(30_000),
|
|
85
|
+
});
|
|
86
|
+
if (!sourceResponse.ok) throw new Error(`Cannot read tagged installer source: ${sourceResponse.status}`);
|
|
87
|
+
const source = (await sourceResponse.json()) as {
|
|
88
|
+
type?: unknown;
|
|
89
|
+
path?: unknown;
|
|
90
|
+
encoding?: unknown;
|
|
91
|
+
size?: unknown;
|
|
92
|
+
sha?: unknown;
|
|
93
|
+
content?: unknown;
|
|
94
|
+
} | null;
|
|
95
|
+
if (
|
|
96
|
+
source?.type !== "file" ||
|
|
97
|
+
source.path !== "scripts/install.sh" ||
|
|
98
|
+
source.encoding !== "base64" ||
|
|
99
|
+
typeof source.size !== "number" ||
|
|
100
|
+
!Number.isInteger(source.size) ||
|
|
101
|
+
source.size <= 0 ||
|
|
102
|
+
source.size > MAX_INSTALLER_BYTES ||
|
|
103
|
+
typeof source.sha !== "string" ||
|
|
104
|
+
!/^[a-f0-9]{40}$/.test(source.sha) ||
|
|
105
|
+
typeof source.content !== "string"
|
|
106
|
+
)
|
|
107
|
+
throw new Error("Invalid tagged installer source metadata");
|
|
108
|
+
const script = Buffer.from(source.content, "base64");
|
|
109
|
+
const blobSha = createHash("sha1").update(`blob ${script.length}\0`).update(script).digest("hex");
|
|
110
|
+
if (script.length !== source.size || blobSha !== source.sha)
|
|
111
|
+
throw new Error("Tagged installer source does not match its Git blob identity");
|
|
112
|
+
|
|
113
|
+
await (options.runInstaller ?? runInstaller)({
|
|
114
|
+
script,
|
|
115
|
+
installDir: path.posix.dirname(options.targetPath),
|
|
116
|
+
version,
|
|
117
|
+
});
|
|
118
|
+
}
|
package/src/cli/update-cli.ts
CHANGED
|
@@ -5,6 +5,7 @@ import path from "node:path";
|
|
|
5
5
|
import { pipeline } from "node:stream/promises";
|
|
6
6
|
import { APP_NAME, isEnoent, VERSION } from "@f5-sales-demo/pi-utils";
|
|
7
7
|
import chalk from "chalk";
|
|
8
|
+
import { type LinuxInstallerInput, updateLinuxStandalone } from "./linux-standalone-update";
|
|
8
9
|
import {
|
|
9
10
|
createInstallReceipt,
|
|
10
11
|
defaultInstallChannelDependencies,
|
|
@@ -76,6 +77,7 @@ export interface StandaloneReplacementOptions {
|
|
|
76
77
|
writeFile?(file: string, contents: string): Promise<void>;
|
|
77
78
|
unlink?(file: string): Promise<void>;
|
|
78
79
|
validate?(executable: string, expectedVersion: string): Promise<void>;
|
|
80
|
+
runLinuxInstaller?(input: LinuxInstallerInput): Promise<void>;
|
|
79
81
|
}
|
|
80
82
|
|
|
81
83
|
async function ignoreMissing(unlink: (file: string) => Promise<void>, file: string): Promise<void> {
|
|
@@ -94,6 +96,15 @@ export async function replaceStandaloneExecutable(
|
|
|
94
96
|
const targetPath = options.targetPath;
|
|
95
97
|
const platform = options.platform ?? process.platform;
|
|
96
98
|
const arch = options.arch ?? process.arch;
|
|
99
|
+
if (platform === "linux") {
|
|
100
|
+
if (arch !== "x64" && arch !== "arm64") throw new Error("Unsupported Linux standalone update architecture");
|
|
101
|
+
return updateLinuxStandalone(expectedVersion, {
|
|
102
|
+
targetPath,
|
|
103
|
+
arch,
|
|
104
|
+
fetchImpl: options.fetchImpl,
|
|
105
|
+
runInstaller: options.runLinuxInstaller,
|
|
106
|
+
});
|
|
107
|
+
}
|
|
97
108
|
const binaryName = getBinaryName(platform, arch);
|
|
98
109
|
const url = `https://github.com/${REPO}/releases/download/v${expectedVersion}/${binaryName}`;
|
|
99
110
|
const tempPath = `${targetPath}.new-${process.pid}`;
|
package/src/cli.ts
CHANGED
|
@@ -5,11 +5,14 @@ import { APP_NAME, initI18n, MIN_BUN_VERSION, registerLocales, t, VERSION } from
|
|
|
5
5
|
* lightweight CLI runner from pi-utils.
|
|
6
6
|
*/
|
|
7
7
|
import { CliUsageError, type CommandEntry, run } from "@f5-sales-demo/pi-utils/cli";
|
|
8
|
+
import { installCliOutputHandlers, writeCliOutput } from "@f5-sales-demo/pi-utils/cli-output";
|
|
8
9
|
import { validateInlineFlagSyntax } from "./cli/flag-spec";
|
|
9
10
|
import { findPrefixedCommand, launchFlagScopeMessage } from "./cli/root-command-routing";
|
|
10
11
|
import { sandboxArgs, sandboxFlags, validateSandboxInvocation } from "./cli/sandbox-spec";
|
|
11
12
|
import { locales } from "./locales/index";
|
|
12
13
|
|
|
14
|
+
installCliOutputHandlers();
|
|
15
|
+
|
|
13
16
|
registerLocales(locales);
|
|
14
17
|
initI18n();
|
|
15
18
|
|
|
@@ -97,7 +100,7 @@ async function showHelp(config: import("@f5-sales-demo/pi-utils/cli").CliConfig)
|
|
|
97
100
|
renderRootHelp(config);
|
|
98
101
|
const extra = getExtraHelpText();
|
|
99
102
|
if (extra.trim().length > 0) {
|
|
100
|
-
process.stdout
|
|
103
|
+
writeCliOutput(process.stdout, `\n${extra}\n`);
|
|
101
104
|
}
|
|
102
105
|
}
|
|
103
106
|
|
|
@@ -679,13 +679,32 @@ export async function discoverAndLoadExtensions(
|
|
|
679
679
|
addPaths(await logger.time("ext:pluginPaths", () => getAllPluginExtensionPaths(cwd)));
|
|
680
680
|
|
|
681
681
|
// 2b. Discover extension entry points from marketplace-cached plugins
|
|
682
|
+
const marketplaceDiagnostics: Array<{ path: string; error: string }> = [];
|
|
682
683
|
await logger.time("ext:marketplaceRoots", async () => {
|
|
683
684
|
for (const root of getPreloadedPluginRoots()) {
|
|
684
685
|
try {
|
|
685
686
|
const rootPath = await fs.realpath(root.path);
|
|
686
|
-
const
|
|
687
|
-
|
|
688
|
-
|
|
687
|
+
const canonicalPath = path.join(rootPath, ".xcsh-plugin", "plugin.json");
|
|
688
|
+
let canonical: { extensions?: unknown; lifecycle?: { integrations?: unknown } } | undefined;
|
|
689
|
+
try {
|
|
690
|
+
canonical = await Bun.file(canonicalPath).json();
|
|
691
|
+
} catch {
|
|
692
|
+
// A marketplace plugin may use package metadata only.
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
// The canonical manifest owns an explicitly declared extension list. Older marketplace
|
|
696
|
+
// plugins keep lifecycle metadata in that manifest and their extension declaration in
|
|
697
|
+
// package.json, so use the established package contract only when it is omitted.
|
|
698
|
+
let extensions = canonical?.extensions;
|
|
699
|
+
let declarationPath = canonicalPath;
|
|
700
|
+
if (extensions === undefined) {
|
|
701
|
+
const packagePath = path.join(rootPath, "package.json");
|
|
702
|
+
const pkg = await Bun.file(packagePath).json();
|
|
703
|
+
extensions = pkg?.xcsh?.extensions ?? pkg?.pi?.extensions;
|
|
704
|
+
declarationPath = packagePath;
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
let loadedEntries = 0;
|
|
689
708
|
if (Array.isArray(extensions)) {
|
|
690
709
|
for (const entry of extensions) {
|
|
691
710
|
if (typeof entry !== "string") continue;
|
|
@@ -699,10 +718,22 @@ export async function discoverAndLoadExtensions(
|
|
|
699
718
|
if (!resolved.startsWith(rootPath + path.sep) && resolved !== rootPath) continue;
|
|
700
719
|
if (isDisabledName(getExtensionNameFromPath(resolved))) continue;
|
|
701
720
|
addPath(resolved);
|
|
721
|
+
loadedEntries++;
|
|
702
722
|
}
|
|
703
723
|
}
|
|
704
|
-
|
|
705
|
-
|
|
724
|
+
|
|
725
|
+
const integrations = canonical?.lifecycle?.integrations;
|
|
726
|
+
if (Array.isArray(integrations) && integrations.some(id => typeof id === "string") && loadedEntries === 0) {
|
|
727
|
+
marketplaceDiagnostics.push({
|
|
728
|
+
path: root.path,
|
|
729
|
+
error: `Marketplace plugin ${root.plugin} declares lifecycle integrations but no extension entrypoint could be loaded from ${declarationPath}`,
|
|
730
|
+
});
|
|
731
|
+
}
|
|
732
|
+
} catch (error) {
|
|
733
|
+
marketplaceDiagnostics.push({
|
|
734
|
+
path: root.path,
|
|
735
|
+
error: `Failed to read marketplace extension metadata: ${error instanceof Error ? error.message : String(error)}`,
|
|
736
|
+
});
|
|
706
737
|
}
|
|
707
738
|
}
|
|
708
739
|
});
|
|
@@ -737,6 +768,7 @@ export async function discoverAndLoadExtensions(
|
|
|
737
768
|
|
|
738
769
|
const resolvedEventBus = eventBus ?? new EventBus();
|
|
739
770
|
const result = await logger.time("ext:loadLoop", () => loadExtensions(allPaths, cwd, resolvedEventBus));
|
|
771
|
+
result.errors.push(...marketplaceDiagnostics);
|
|
740
772
|
await logger.time("ext:loadBundled", () => loadBundledExtensions(result, cwd, resolvedEventBus, isDisabledName));
|
|
741
773
|
return result;
|
|
742
774
|
}
|