@f5-sales-demo/xcsh 21.30.1 → 21.32.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +8 -8
- package/src/browser/office-pane-server.ts +1 -1
- package/src/cli/update-cli.ts +13 -5
- package/src/cli.ts +1 -0
- package/src/commands/profile.ts +72 -0
- package/src/extensibility/extensions/loader.ts +14 -31
- package/src/extensibility/extensions/types.ts +7 -3
- package/src/index.ts +1 -1
- package/src/internal-urls/build-info.generated.ts +9 -9
- package/src/internal-urls/docs-index.generated.ts +59 -64
- package/src/person-profile/README.md +54 -77
- package/src/person-profile/builder.ts +57 -17
- package/src/person-profile/machine-profile.ts +13 -1
- package/src/person-profile/private-store.ts +214 -54
- package/src/person-profile/salesforce-discovery.ts +1 -0
- package/src/person-profile/schema.ts +1 -0
- package/src/person-profile/service.ts +88 -20
- package/src/tools/machine-profile.ts +5 -1
- package/src/tools/person-profile.ts +5 -0
- package/src/person-profile/HISTORICAL-AUDIT.md +0 -163
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"type": "module",
|
|
3
3
|
"name": "@f5-sales-demo/xcsh",
|
|
4
|
-
"version": "21.
|
|
4
|
+
"version": "21.32.0",
|
|
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",
|
|
@@ -63,13 +63,13 @@
|
|
|
63
63
|
},
|
|
64
64
|
"dependencies": {
|
|
65
65
|
"@agentclientprotocol/sdk": "1.4.0",
|
|
66
|
-
"@f5-sales-demo/pi-agent-core": "21.
|
|
67
|
-
"@f5-sales-demo/pi-ai": "21.
|
|
68
|
-
"@f5-sales-demo/pi-natives": "21.
|
|
69
|
-
"@f5-sales-demo/pi-resource-management": "21.
|
|
70
|
-
"@f5-sales-demo/pi-tui": "21.
|
|
71
|
-
"@f5-sales-demo/pi-utils": "21.
|
|
72
|
-
"@f5-sales-demo/xcsh-stats": "21.
|
|
66
|
+
"@f5-sales-demo/pi-agent-core": "21.32.0",
|
|
67
|
+
"@f5-sales-demo/pi-ai": "21.32.0",
|
|
68
|
+
"@f5-sales-demo/pi-natives": "21.32.0",
|
|
69
|
+
"@f5-sales-demo/pi-resource-management": "21.32.0",
|
|
70
|
+
"@f5-sales-demo/pi-tui": "21.32.0",
|
|
71
|
+
"@f5-sales-demo/pi-utils": "21.32.0",
|
|
72
|
+
"@f5-sales-demo/xcsh-stats": "21.32.0",
|
|
73
73
|
"@mozilla/readability": "^0.6",
|
|
74
74
|
"@sinclair/typebox": "0.34.52",
|
|
75
75
|
"@xterm/headless": "^6.0",
|
|
@@ -155,7 +155,7 @@ export function paneUnavailableMessage(distDir: string, source: PaneSource): str
|
|
|
155
155
|
return (
|
|
156
156
|
"The Office pane is not included in the npm package — it ships as a build-time asset of the " +
|
|
157
157
|
"compiled binary, which is also what provides the sideload and the trusted certificate. " +
|
|
158
|
-
"Install it with `brew install f5-sales-demo/tap/xcsh` and run `xcsh office` from there."
|
|
158
|
+
"Install it with `brew install --cask f5-sales-demo/tap/xcsh` and run `xcsh office` from there."
|
|
159
159
|
);
|
|
160
160
|
}
|
|
161
161
|
|
package/src/cli/update-cli.ts
CHANGED
|
@@ -75,7 +75,7 @@ type UpdateTarget =
|
|
|
75
75
|
* Detection order:
|
|
76
76
|
* 1. bun — binary is inside bun's global bin directory
|
|
77
77
|
* 2. npm — binary is a symlink whose resolution chain contains "node_modules"
|
|
78
|
-
* 3. brew — binary path or realpath contains "Cellar" or "homebrew"
|
|
78
|
+
* 3. brew — binary path or realpath contains "Cellar", "Caskroom", or "homebrew"
|
|
79
79
|
* 4. binary — fallback for standalone installs
|
|
80
80
|
*/
|
|
81
81
|
function detectInstallMethod(binPath: string, bunBinDir: string | undefined): InstallMethod {
|
|
@@ -106,14 +106,18 @@ function detectInstallMethod(binPath: string, bunBinDir: string | undefined): In
|
|
|
106
106
|
// lstat/readlink may fail; fall through
|
|
107
107
|
}
|
|
108
108
|
|
|
109
|
-
// 3. brew: path or realpath contains Cellar or homebrew
|
|
109
|
+
// 3. brew: path or realpath contains Cellar, Caskroom, or homebrew
|
|
110
110
|
const lowerBinPath = binPath.toLowerCase();
|
|
111
|
-
if (
|
|
111
|
+
if (
|
|
112
|
+
lowerBinPath.includes("/cellar/") ||
|
|
113
|
+
lowerBinPath.includes("/caskroom/") ||
|
|
114
|
+
lowerBinPath.includes("/homebrew/")
|
|
115
|
+
) {
|
|
112
116
|
return "brew";
|
|
113
117
|
}
|
|
114
118
|
try {
|
|
115
119
|
const realPath = fs.realpathSync(binPath).toLowerCase();
|
|
116
|
-
if (realPath.includes("/cellar/") || realPath.includes("/homebrew/")) {
|
|
120
|
+
if (realPath.includes("/cellar/") || realPath.includes("/caskroom/") || realPath.includes("/homebrew/")) {
|
|
117
121
|
return "brew";
|
|
118
122
|
}
|
|
119
123
|
} catch {
|
|
@@ -322,7 +326,7 @@ async function updateViaNpm(expectedVersion: string): Promise<void> {
|
|
|
322
326
|
function updateViaBrew(targetPath: string, expectedVersion: string): void {
|
|
323
327
|
console.log(chalk.yellow(`\n${APP_NAME} at ${targetPath} was installed via Homebrew.`));
|
|
324
328
|
console.log(chalk.yellow(`To update to ${expectedVersion}, run:`));
|
|
325
|
-
console.log(chalk.cyan(`
|
|
329
|
+
console.log(chalk.cyan(` ${getBrewUpgradeCommand()}`));
|
|
326
330
|
console.log(chalk.dim("\nThis ensures the update goes through your organization's Homebrew tap."));
|
|
327
331
|
// #1874 Task 7: brew upgrade runs out-of-band, so we can't recycle automatically.
|
|
328
332
|
// Nudge the one command that applies it to the Chrome extension immediately.
|
|
@@ -330,6 +334,10 @@ function updateViaBrew(targetPath: string, expectedVersion: string): void {
|
|
|
330
334
|
console.log(chalk.dim("(otherwise it takes effect the next time you open Chrome)."));
|
|
331
335
|
}
|
|
332
336
|
|
|
337
|
+
export function getBrewUpgradeCommand(): string {
|
|
338
|
+
return `brew upgrade --cask ${APP_NAME}`;
|
|
339
|
+
}
|
|
340
|
+
|
|
333
341
|
/**
|
|
334
342
|
* Download a release binary to a target path, replacing an existing file.
|
|
335
343
|
*/
|
package/src/cli.ts
CHANGED
|
@@ -78,6 +78,7 @@ const commands: CommandEntry[] = [
|
|
|
78
78
|
{ name: "manager", load: () => import("./commands/manager").then(m => m.default) },
|
|
79
79
|
{ name: "office", load: () => import("./commands/office").then(m => m.default) },
|
|
80
80
|
{ name: "plugin", load: () => import("./commands/plugin").then(m => m.default) },
|
|
81
|
+
{ name: "profile", load: () => import("./commands/profile").then(m => m.default) },
|
|
81
82
|
{ name: "setup", load: () => import("./commands/setup").then(m => m.default) },
|
|
82
83
|
{ name: "shell", load: () => import("./commands/shell").then(m => m.default) },
|
|
83
84
|
{ name: "ssh", load: () => import("./commands/ssh").then(m => m.default) },
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { createInterface } from "node:readline/promises";
|
|
2
|
+
import { Args, Command, Flags } from "@f5-sales-demo/pi-utils/cli";
|
|
3
|
+
import { machineProfileService } from "../person-profile/machine-profile";
|
|
4
|
+
import { resetProfileTargets } from "../person-profile/private-store";
|
|
5
|
+
import { personProfileService } from "../person-profile/service";
|
|
6
|
+
|
|
7
|
+
type ProfileTarget = "person" | "computer" | "all";
|
|
8
|
+
|
|
9
|
+
export default class Profile extends Command {
|
|
10
|
+
static description = "Inspect or reset private person and computer profiles";
|
|
11
|
+
static args = {
|
|
12
|
+
action: Args.string({ description: "Profile action", options: ["status", "reset"], required: true }),
|
|
13
|
+
target: Args.string({ description: "Reset target", options: ["person", "computer", "all"] }),
|
|
14
|
+
};
|
|
15
|
+
static flags = {
|
|
16
|
+
json: Flags.boolean({ description: "Output machine-readable status", default: false }),
|
|
17
|
+
yes: Flags.boolean({ description: "Confirm permanent reset", default: false }),
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
async run(): Promise<void> {
|
|
21
|
+
const { args, flags } = await this.parse(Profile);
|
|
22
|
+
if (args.action === "status") {
|
|
23
|
+
if (flags.yes) throw new Error("profile status does not accept --yes");
|
|
24
|
+
if (args.target) throw new Error("profile status does not accept a target");
|
|
25
|
+
const result = {
|
|
26
|
+
person: await personProfileService.status(),
|
|
27
|
+
computer: await machineProfileService.status(),
|
|
28
|
+
};
|
|
29
|
+
if (flags.json) {
|
|
30
|
+
process.stdout.write(`${JSON.stringify(result)}\n`);
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
for (const [name, status] of Object.entries(result)) {
|
|
34
|
+
const details = [
|
|
35
|
+
status.schemaVersion === undefined ? undefined : `schema v${status.schemaVersion}`,
|
|
36
|
+
status.permissions?.file ? `file ${status.permissions.file}` : undefined,
|
|
37
|
+
status.permissions?.directory ? `directory ${status.permissions.directory}` : undefined,
|
|
38
|
+
status.reason,
|
|
39
|
+
].filter(Boolean);
|
|
40
|
+
process.stdout.write(`${name}: ${status.status}${details.length ? ` (${details.join(", ")})` : ""}\n`);
|
|
41
|
+
if (status.remedy) process.stdout.write(` remedy: ${status.remedy}\n`);
|
|
42
|
+
}
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const target = args.target as ProfileTarget | undefined;
|
|
47
|
+
if (!target) throw new Error("profile reset requires person, computer, or all");
|
|
48
|
+
if (flags.json) throw new Error("profile reset does not accept --json");
|
|
49
|
+
const services =
|
|
50
|
+
target === "all"
|
|
51
|
+
? [personProfileService, machineProfileService]
|
|
52
|
+
: [target === "person" ? personProfileService : machineProfileService];
|
|
53
|
+
process.stdout.write(
|
|
54
|
+
`Profiles selected for permanent reset:\n${services.map(service => `- ${service.path}`).join("\n")}\n`,
|
|
55
|
+
);
|
|
56
|
+
if (!flags.yes) {
|
|
57
|
+
if (!process.stdin.isTTY || !process.stdout.isTTY)
|
|
58
|
+
throw new Error("Non-interactive profile reset requires --yes");
|
|
59
|
+
const prompt = createInterface({ input: process.stdin, output: process.stdout });
|
|
60
|
+
try {
|
|
61
|
+
const answer = await prompt.question('Type "reset" to continue: ');
|
|
62
|
+
if (answer !== "reset") throw new Error("Profile reset cancelled");
|
|
63
|
+
} finally {
|
|
64
|
+
prompt.close();
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
const results = await resetProfileTargets(services);
|
|
68
|
+
for (const service of services) {
|
|
69
|
+
process.stdout.write(`${service.path}: ${results.get(service.path) ? "reset" : "already missing"}\n`);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
@@ -15,7 +15,7 @@ import { loadCapability } from "../../discovery";
|
|
|
15
15
|
import { getExtensionNameFromPath, getPreloadedPluginRoots } from "../../discovery/helpers";
|
|
16
16
|
import type { ExecOptions } from "../../exec/exec";
|
|
17
17
|
import { execCommand } from "../../exec/exec";
|
|
18
|
-
import {
|
|
18
|
+
import { personProfileService } from "../../person-profile/service";
|
|
19
19
|
import type { CustomMessage } from "../../session/messages";
|
|
20
20
|
import { EventBus } from "../../utils/event-bus";
|
|
21
21
|
import { getAllPluginExtensionPaths } from "../plugins/loader";
|
|
@@ -121,6 +121,7 @@ class ConcreteExtensionAPI implements ExtensionAPI, IExtensionRuntime {
|
|
|
121
121
|
config: import("./types").ProviderConfig;
|
|
122
122
|
sourceId: string;
|
|
123
123
|
}> = [];
|
|
124
|
+
readonly personProfile: ExtensionAPI["personProfile"];
|
|
124
125
|
|
|
125
126
|
constructor(
|
|
126
127
|
public readonly pi: typeof import("@f5-sales-demo/xcsh"),
|
|
@@ -128,7 +129,18 @@ class ConcreteExtensionAPI implements ExtensionAPI, IExtensionRuntime {
|
|
|
128
129
|
private readonly runtime: IExtensionRuntime,
|
|
129
130
|
private readonly cwd: string,
|
|
130
131
|
public readonly events: EventBus,
|
|
131
|
-
) {
|
|
132
|
+
) {
|
|
133
|
+
const registrant = extension.resolvedPath;
|
|
134
|
+
this.personProfile = Object.freeze({
|
|
135
|
+
get: () => personProfileService.get(),
|
|
136
|
+
registerCollector: collector => {
|
|
137
|
+
if (!collector || typeof collector.collect !== "function")
|
|
138
|
+
throw new Error("Invalid person profile collector");
|
|
139
|
+
personProfileService.registerProfileCollector(collector, registrant);
|
|
140
|
+
},
|
|
141
|
+
unregisterCollector: (id: string) => personProfileService.unregisterProfileCollector(id, registrant),
|
|
142
|
+
});
|
|
143
|
+
}
|
|
132
144
|
|
|
133
145
|
on<F extends HandlerFn>(event: string, handler: F): void {
|
|
134
146
|
const list = this.extension.handlers.get(event) ?? [];
|
|
@@ -249,35 +261,6 @@ class ConcreteExtensionAPI implements ExtensionAPI, IExtensionRuntime {
|
|
|
249
261
|
return this.runtime.setSessionName(name);
|
|
250
262
|
}
|
|
251
263
|
|
|
252
|
-
registerProfileCollector(collector: ProfileCollector): void {
|
|
253
|
-
if (!collector || typeof collector.collect !== "function") throw new Error("Invalid person profile collector");
|
|
254
|
-
personProfileService.registerProfileCollector(
|
|
255
|
-
{
|
|
256
|
-
...collector,
|
|
257
|
-
async collect(signal) {
|
|
258
|
-
const result = await collector.collect(signal);
|
|
259
|
-
if ("facts" in result) return result;
|
|
260
|
-
// Legacy flat output does not distinguish queried values from heuristic inference.
|
|
261
|
-
// Keep it as candidates; modern collectors can return explicit facts/observations.
|
|
262
|
-
return {
|
|
263
|
-
facts: {},
|
|
264
|
-
observations: Object.entries(result).map(([field, value]) => ({
|
|
265
|
-
field: field as keyof typeof result,
|
|
266
|
-
value,
|
|
267
|
-
source: collector.id,
|
|
268
|
-
kind: "inferred" as const,
|
|
269
|
-
observedAt: new Date().toISOString(),
|
|
270
|
-
})),
|
|
271
|
-
};
|
|
272
|
-
},
|
|
273
|
-
},
|
|
274
|
-
this.extension.resolvedPath,
|
|
275
|
-
);
|
|
276
|
-
}
|
|
277
|
-
unregisterProfileCollector(id: string): boolean {
|
|
278
|
-
return personProfileService.unregisterProfileCollector(id, this.extension.resolvedPath);
|
|
279
|
-
}
|
|
280
|
-
|
|
281
264
|
registerProvider(name: string, config: import("./types").ProviderConfig): void {
|
|
282
265
|
this.runtime.pendingProviderRegistrations.push({ name, config, sourceId: this.extension.path });
|
|
283
266
|
}
|
|
@@ -1013,6 +1013,13 @@ export interface ExtensionAPI {
|
|
|
1013
1013
|
/** Injected pi-coding-agent exports for accessing SDK utilities */
|
|
1014
1014
|
pi: typeof piCodingAgent;
|
|
1015
1015
|
|
|
1016
|
+
/** Canonical, provenance-aware person profile integration. */
|
|
1017
|
+
readonly personProfile: {
|
|
1018
|
+
get(): Promise<import("../../person-profile/schema").PersonProfile>;
|
|
1019
|
+
registerCollector(collector: import("../../person-profile/service").ExtensionProfileCollector): void;
|
|
1020
|
+
unregisterCollector(id: string): boolean;
|
|
1021
|
+
};
|
|
1022
|
+
|
|
1016
1023
|
// =========================================================================
|
|
1017
1024
|
// Event Subscription
|
|
1018
1025
|
// =========================================================================
|
|
@@ -1222,9 +1229,6 @@ export interface ExtensionAPI {
|
|
|
1222
1229
|
* });
|
|
1223
1230
|
*/
|
|
1224
1231
|
registerProvider(name: string, config: ProviderConfig): void;
|
|
1225
|
-
registerProfileCollector(collector: import("../../person-profile/service").ProfileCollector): void;
|
|
1226
|
-
unregisterProfileCollector(id: string): boolean;
|
|
1227
|
-
|
|
1228
1232
|
/** Shared event bus for extension communication. */
|
|
1229
1233
|
events: EventBus;
|
|
1230
1234
|
}
|
package/src/index.ts
CHANGED
|
@@ -40,7 +40,7 @@ export * from "./modes/components";
|
|
|
40
40
|
export * from "./modes/theme/theme";
|
|
41
41
|
export type { PersonProfile, UserProfile, UserProfileObservation } from "./person-profile/schema";
|
|
42
42
|
export type { ProfileCollection, ProfileCollector } from "./person-profile/service";
|
|
43
|
-
export {
|
|
43
|
+
export { PersonProfileService } from "./person-profile/service";
|
|
44
44
|
export * from "./routing/classifier";
|
|
45
45
|
export * from "./routing/coordinator";
|
|
46
46
|
export * from "./routing/delegation";
|
|
@@ -17,17 +17,17 @@ export interface BuildInfo {
|
|
|
17
17
|
}
|
|
18
18
|
|
|
19
19
|
export const BUILD_INFO: BuildInfo = {
|
|
20
|
-
"version": "21.
|
|
21
|
-
"commit": "
|
|
22
|
-
"shortCommit": "
|
|
23
|
-
"branch": "
|
|
24
|
-
"tag": "v21.
|
|
25
|
-
"commitDate": "2026-09-
|
|
26
|
-
"buildDate": "2026-09-
|
|
20
|
+
"version": "21.32.0",
|
|
21
|
+
"commit": "318283a955aea8725f8119d68e14db6ea2d8b3e7",
|
|
22
|
+
"shortCommit": "318283a",
|
|
23
|
+
"branch": "unknown",
|
|
24
|
+
"tag": "v21.32.0",
|
|
25
|
+
"commitDate": "2026-09-17T18:23:21Z",
|
|
26
|
+
"buildDate": "2026-09-17T23:24:55.633Z",
|
|
27
27
|
"dirty": true,
|
|
28
28
|
"prNumber": "",
|
|
29
29
|
"repoUrl": "https://github.com/f5-sales-demo/xcsh",
|
|
30
30
|
"repoSlug": "f5-sales-demo/xcsh",
|
|
31
|
-
"commitUrl": "https://github.com/f5-sales-demo/xcsh/commit/
|
|
32
|
-
"releaseUrl": "https://github.com/f5-sales-demo/xcsh/releases/tag/v21.
|
|
31
|
+
"commitUrl": "https://github.com/f5-sales-demo/xcsh/commit/318283a955aea8725f8119d68e14db6ea2d8b3e7",
|
|
32
|
+
"releaseUrl": "https://github.com/f5-sales-demo/xcsh/releases/tag/v21.32.0"
|
|
33
33
|
};
|