@f5-sales-demo/xcsh 21.30.1 → 21.31.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "type": "module",
3
3
  "name": "@f5-sales-demo/xcsh",
4
- "version": "21.30.1",
4
+ "version": "21.31.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.30.1",
67
- "@f5-sales-demo/pi-ai": "21.30.1",
68
- "@f5-sales-demo/pi-natives": "21.30.1",
69
- "@f5-sales-demo/pi-resource-management": "21.30.1",
70
- "@f5-sales-demo/pi-tui": "21.30.1",
71
- "@f5-sales-demo/pi-utils": "21.30.1",
72
- "@f5-sales-demo/xcsh-stats": "21.30.1",
66
+ "@f5-sales-demo/pi-agent-core": "21.31.0",
67
+ "@f5-sales-demo/pi-ai": "21.31.0",
68
+ "@f5-sales-demo/pi-natives": "21.31.0",
69
+ "@f5-sales-demo/pi-resource-management": "21.31.0",
70
+ "@f5-sales-demo/pi-tui": "21.31.0",
71
+ "@f5-sales-demo/pi-utils": "21.31.0",
72
+ "@f5-sales-demo/xcsh-stats": "21.31.0",
73
73
  "@mozilla/readability": "^0.6",
74
74
  "@sinclair/typebox": "0.34.52",
75
75
  "@xterm/headless": "^6.0",
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 { type ProfileCollector, personProfileService } from "../../person-profile/service";
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 { loadProfile, PersonProfileService } from "./person-profile/service";
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.30.1",
21
- "commit": "22d9767e595b2a3d2e57a62b192bd582fc9b00eb",
22
- "shortCommit": "22d9767",
23
- "branch": "main",
24
- "tag": "v21.30.1",
25
- "commitDate": "2026-09-17T15:05:21+00:00",
26
- "buildDate": "2026-09-17T16:16:04.516Z",
20
+ "version": "21.31.0",
21
+ "commit": "e606d58ea45deae40b61f053d92e3e9c0fe9b80e",
22
+ "shortCommit": "e606d58",
23
+ "branch": "unknown",
24
+ "tag": "v21.31.0",
25
+ "commitDate": "2026-09-17T16:35:46Z",
26
+ "buildDate": "2026-09-17T23:33:05.447Z",
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/22d9767e595b2a3d2e57a62b192bd582fc9b00eb",
32
- "releaseUrl": "https://github.com/f5-sales-demo/xcsh/releases/tag/v21.30.1"
31
+ "commitUrl": "https://github.com/f5-sales-demo/xcsh/commit/e606d58ea45deae40b61f053d92e3e9c0fe9b80e",
32
+ "releaseUrl": "https://github.com/f5-sales-demo/xcsh/releases/tag/v21.31.0"
33
33
  };