@f5-sales-demo/xcsh 22.4.3 → 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 CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "type": "module",
3
3
  "name": "@f5-sales-demo/xcsh",
4
- "version": "22.4.3",
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.3",
71
- "@f5-sales-demo/pi-ai": "22.4.3",
72
- "@f5-sales-demo/pi-natives": "22.4.3",
73
- "@f5-sales-demo/pi-resource-management": "22.4.3",
74
- "@f5-sales-demo/pi-tui": "22.4.3",
75
- "@f5-sales-demo/pi-utils": "22.4.3",
76
- "@f5-sales-demo/xcsh-stats": "22.4.3",
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
+ }
@@ -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.write(`\n${extra}\n`);
103
+ writeCliOutput(process.stdout, `\n${extra}\n`);
101
104
  }
102
105
  }
103
106