@explyt/ai-agent 0.3.1-dogfood.4 → 0.3.2

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.
Files changed (63) hide show
  1. package/README.md +3 -3
  2. package/agent-docs/README.md +5 -3
  3. package/agent-docs/mcp.md +1 -1
  4. package/agent-docs/rules.md +2 -1
  5. package/agent-docs/skills.md +1 -1
  6. package/dist/cli/brand-profile.js +95 -0
  7. package/dist/cli/engine.js +53 -19
  8. package/dist/cli/index.js +36 -29
  9. package/dist/cli/install-auto.js +8 -4
  10. package/dist/cli/install-cleanup.js +2 -1
  11. package/dist/cli/install-command.js +12 -8
  12. package/dist/cli/install-config.js +6 -4
  13. package/dist/cli/install-health.js +2 -1
  14. package/dist/cli/install-migrate.js +8 -7
  15. package/dist/cli/install-output.js +4 -2
  16. package/dist/cli/install-resolve.js +4 -3
  17. package/dist/cli/install-roots.js +3 -2
  18. package/dist/cli/install-run.js +14 -11
  19. package/dist/cli/install-settings.js +1 -1
  20. package/dist/cli/install-switch.js +3 -2
  21. package/dist/cli/installer-main.js +1 -1
  22. package/dist/cli/launch.js +71 -18
  23. package/dist/cli/npm-command.js +1 -1
  24. package/dist/cli/package-name.js +20 -8
  25. package/dist/cli/update-env.js +10 -5
  26. package/dist/cli/update.js +6 -5
  27. package/dist/extensions/ask-user-question.js +1 -1
  28. package/dist/extensions/bash.js +1 -1
  29. package/dist/extensions/custom-provider.js +1 -1
  30. package/dist/extensions/explyt-account.js +1 -1
  31. package/dist/extensions/hello.js +1 -1
  32. package/dist/extensions/identity.js +1 -1
  33. package/dist/extensions/mcp.js +1 -1
  34. package/dist/extensions/misc-hacks.js +1 -1
  35. package/dist/extensions/policy.js +1 -1
  36. package/dist/extensions/rules.js +1 -1
  37. package/dist/extensions/session-title.js +1 -1
  38. package/dist/extensions/skills.js +1 -1
  39. package/dist/extensions/subagents.js +1 -1
  40. package/dist/extensions/telemetry.js +1 -1
  41. package/dist/extensions/todo.js +1 -1
  42. package/dist/extensions/tool-rows.js +1 -1
  43. package/dist/extensions/tui.js +1 -1
  44. package/dist/extensions/update-notice.js +1 -1
  45. package/dist/extensions/web-access.js +1 -1
  46. package/dist/extensions/write.js +1 -1
  47. package/dist/install/installer.js +101 -31
  48. package/dist/rpiv-ask-user-question/{chunk-N7WDPKMG.js → chunk-JGZK32CE.js} +1 -1
  49. package/dist/rpiv-ask-user-question/chunk-NG5WTH2G.js +81 -0
  50. package/dist/rpiv-ask-user-question/{chunk-CXK2YRDP.js → chunk-VN36RZ2Z.js} +1 -1
  51. package/dist/rpiv-ask-user-question/index.js +3 -3
  52. package/dist/rpiv-todo/{chunk-QYKNBPSE.js → chunk-CQEKZQYZ.js} +1 -1
  53. package/dist/rpiv-todo/{chunk-GQ3KKPG4.js → chunk-KQHBIJFK.js} +1 -1
  54. package/dist/rpiv-todo/chunk-NG5WTH2G.js +82 -0
  55. package/dist/rpiv-todo/index.js +4 -4
  56. package/package.json +1 -1
  57. package/agent-docs/plugins.md +0 -62
  58. package/agent-docs/telegram.md +0 -170
  59. package/dist/cli/plugin.js +0 -1
  60. package/dist/extensions/plugins.js +0 -1
  61. package/dist/extensions/telegram.js +0 -1
  62. package/dist/rpiv-ask-user-question/chunk-ZI2BMBEN.js +0 -9
  63. package/dist/rpiv-todo/chunk-ZI2BMBEN.js +0 -10
package/README.md CHANGED
@@ -17,8 +17,8 @@ explyt update # install the newer latest, then restart open sessi
17
17
  For a one-off pinned run:
18
18
 
19
19
  ```bash
20
- npx -y "@explyt/ai-agent@0.3.1" # start the terminal UI
21
- npx -y "@explyt/ai-agent@0.3.1" login # store a provider credential, once
20
+ npx -y "@explyt/ai-agent@0.3.2" # start the terminal UI
21
+ npx -y "@explyt/ai-agent@0.3.2" login # store a provider credential, once
22
22
  ```
23
23
 
24
24
  The agent runs with exactly the extensions this build ships with and ignores any
@@ -28,4 +28,4 @@ you get.
28
28
  IDE (ACP) integration is not part of this package: it is provided by the Explyt
29
29
  managed backend, which the Neo plugin downloads and manages.
30
30
 
31
- Run `npx -y @explyt/ai-agent@0.3.1 --help` for the available options.
31
+ Run `npx -y @explyt/ai-agent@0.3.2 --help` for the available options.
@@ -15,7 +15,7 @@ it. Read the whole file before editing anything it describes.
15
15
  | `agents.md` | helper agents and model profiles: where the files live, every field, which model a helper runs on, disabling one |
16
16
  | `mcp.md` | MCP servers: the two `mcp.json` files, the entry shape, disabling a server or a tool |
17
17
  | `web-search.md` | web search and page fetch: `web-search.json`, where requests go, keys, switching search off, failures |
18
- | `plugins.md` | Marketplace plugins: the project manifest, the `explyt plugin` command, plugin values |
18
+ | `plugins.md` | Marketplace plugins (dogfood builds only; a stable build has neither this file nor `explyt plugin`): the project manifest, the `explyt plugin` command, plugin values |
19
19
  | `telegram.md` | the Telegram remote for terminal sessions (dogfood builds only; a stable build has neither this file nor `/telegram`): setup and pairing, `/telegram` and its subcommands, notifications, its files, what leaves the machine |
20
20
 
21
21
  ## User configuration lives under `.explyt/`
@@ -37,11 +37,13 @@ all projects, and in the project directory for one project:
37
37
  <project>/.explyt/mcp.json MCP servers for this project
38
38
  <project>/.explyt/agents/*.md helper agents for this project
39
39
  <project>/.explyt/models/*.md model profiles for this project
40
- <project>/.explyt/plugins.json Marketplace plugins enabled for this project
40
+ <project>/.explyt/plugins.json Marketplace plugins enabled for this project (dogfood builds only)
41
41
  ```
42
42
 
43
43
  Installed plugin payloads and plugin values are not user configuration files: they live in the
44
- product data directory and change only through `explyt plugin` (see `plugins.md`).
44
+ product data directory and change only through `explyt plugin` (see `plugins.md`). Marketplace
45
+ plugins ship in dogfood builds only: if `plugins.md` is absent here, this build has no `explyt
46
+ plugin`, does not read `plugins.json`, and no plugin adds rules, Skills or MCP servers.
45
47
 
46
48
  Create these files and directories when they do not exist; nothing has to be registered anywhere
47
49
  for them to take effect.
package/agent-docs/mcp.md CHANGED
@@ -9,7 +9,7 @@ An MCP server gives the agent extra tools, resources and prompts. Servers are de
9
9
  | global | `~/.explyt/mcp.json` | the user |
10
10
  | project | `<project>/.explyt/mcp.json` | the user |
11
11
  | session | the `mcpServers` field of the IDE's session request | the IDE |
12
- | plugin | the Marketplace plugins `<project>/.explyt/plugins.json` enables | the plugin author |
12
+ | plugin | the Marketplace plugins `<project>/.explyt/plugins.json` enables (dogfood builds only) | the plugin author |
13
13
 
14
14
  These four and nothing else. Other tools' MCP files are **not** read — not `~/.config/mcp/mcp.json`,
15
15
  not `~/.agents/mcp.json`, not `<project>/.mcp.json`, and not the legacy
@@ -15,7 +15,8 @@ does not repeat them in every conversation.
15
15
  | `<project>/.claude/CLAUDE.md` | `project:.claude/CLAUDE.md` | plain Markdown |
16
16
  | `<project>/CLAUDE.local.md` | `project:CLAUDE.local.md` | plain Markdown |
17
17
 
18
- Only these locations, plus the rules of the Marketplace plugins the project enables. A `CLAUDE.md`
18
+ Only these locations, plus the rules of the Marketplace plugins the project enables (dogfood builds
19
+ only). A `CLAUDE.md`
19
20
  or `AGENTS.md` in a subdirectory is not picked up, and there is no search up the directory tree —
20
21
  the project is the session's working directory.
21
22
 
@@ -27,7 +27,7 @@ project. No root outranks another, and the tool a Skill was written for is never
27
27
 
28
28
  The project root is the session's working directory; there is no search up the directory tree.
29
29
 
30
- The Marketplace plugins the project enables add their own Skills: each `skills/<name>/SKILL.md` of
30
+ In a dogfood build, the Marketplace plugins the project enables add their own Skills: each `skills/<name>/SKILL.md` of
31
31
  the enabled plugin version. A plugin Skill is part of the plugin — do not edit or copy it;
32
32
  `plugins.md` describes how a plugin is enabled.
33
33
 
@@ -0,0 +1,95 @@
1
+ /**
2
+ * The brand profile of this built tree, generated by scripts/build-release.mjs from
3
+ * cli/brand-profile.mjs (spec #344, RQ-20). The tree is built for one brand: it does not read
4
+ * AGENT_BRAND and carries no other brand's values.
5
+ */
6
+
7
+ /** The brand this tree was built for. */
8
+ const STAMPED_BRAND = "explyt";
9
+
10
+ /** @typedef {typeof STAMPED_BRAND} BrandId */
11
+
12
+ /** The brands of this tree: its own. */
13
+ export const BRAND_IDS = Object.freeze([STAMPED_BRAND]);
14
+
15
+ /** @param {any} value */
16
+ function freeze(value) {
17
+ for (const inner of Object.values(value)) if (inner && typeof inner === "object") freeze(inner);
18
+ return Object.freeze(value);
19
+ }
20
+
21
+ const PROFILE = freeze({
22
+ "id": "explyt",
23
+ "command": "explyt",
24
+ "cliPackage": "@explyt/ai-agent",
25
+ "adapterPackage": "@explyt/acp-adapter",
26
+ "stateDir": "explyt-ai-agent",
27
+ "productName": "Explyt AI Agent",
28
+ "developer": "Explyt",
29
+ "site": "explyt.ai",
30
+ "configDir": ".explyt",
31
+ "sourceRepository": "explyt/explyt-ai-agent",
32
+ "starters": {
33
+ "kind": "github",
34
+ "repository": "explyt/explyt-ai-agent-distribution"
35
+ },
36
+ "starterUrlBase": "https://raw.githubusercontent.com/explyt/explyt-ai-agent-distribution/main/",
37
+ "managedBackend": {
38
+ "repository": "explyt/explyt-ai-agent-distribution"
39
+ },
40
+ "telemetryEndpoint": "https://plugin.explyt.ai/telemetry",
41
+ "mcpAlias": "explyt-mcp",
42
+ "loginCommand": "explyt login",
43
+ "githubCloneDir": "explyt-github-repos",
44
+ "agentDirEnv": "EXPLYT_CODING_AGENT_DIR",
45
+ "updateCheckEnv": "EXPLYT_UPDATE_CHECK",
46
+ "oauth": {
47
+ "keyringService": "pi-mcp-adapter.oauth",
48
+ "redirectUri": "http://127.0.0.1:34517/callback",
49
+ "clientName": "Explyt"
50
+ },
51
+ "environments": {
52
+ "npmPublish": "npm-publish",
53
+ "npmPublishDogfood": "npm-publish-dogfood"
54
+ },
55
+ "localIds": {
56
+ "build": "explyt-local-build",
57
+ "release": "explyt-local-release"
58
+ },
59
+ "localNames": {
60
+ "build": "Explyt AI Agent LOCAL BUILD",
61
+ "release": "Explyt AI Agent LOCAL RELEASE"
62
+ },
63
+ "account": {
64
+ "providerId": "explyt",
65
+ "name": "Explyt",
66
+ "keycloakBase": "https://app.explyt.ai/keycloak",
67
+ "realm": "explyt",
68
+ "clientId": "plugin-client",
69
+ "cabinetApi": "https://app.explyt.ai/api/v1/data",
70
+ "personalAccount": "https://app.explyt.ai/",
71
+ "modelBase": "https://plugin.explyt.ai/openai/api/v1"
72
+ },
73
+ "language": {
74
+ "default": "en",
75
+ "selectable": false
76
+ }
77
+ });
78
+
79
+ export const BRAND_PROFILES = Object.freeze({ [STAMPED_BRAND]: PROFILE });
80
+
81
+ /** @param {string} id */
82
+ export function brandProfile(id) {
83
+ if (id !== STAMPED_BRAND) throw new Error(`unknown brand "${id}"; this tree is built for ${STAMPED_BRAND}`);
84
+ return PROFILE;
85
+ }
86
+
87
+ /** A built tree's brand is fixed: it ignores AGENT_BRAND. */
88
+ export function selectBrand() {
89
+ return STAMPED_BRAND;
90
+ }
91
+
92
+ /** This module is a built tree's stamped copy. */
93
+ export const BUILT = true;
94
+
95
+ export const BRAND = PROFILE;
@@ -23,6 +23,7 @@ import {
23
23
  import { basename, delimiter, dirname, isAbsolute, join, posix, resolve, sep, win32 } from "node:path";
24
24
  import { homedir, platform, tmpdir } from "node:os";
25
25
  import { fileURLToPath } from "node:url";
26
+ import { BRAND } from "./brand-profile.js";
26
27
  import { createHash, randomBytes } from "node:crypto";
27
28
 
28
29
  export const ENGINE_PACKAGE = "@earendil-works/pi-coding-agent";
@@ -35,8 +36,11 @@ export const ENGINE_PACKAGE = "@earendil-works/pi-coding-agent";
35
36
  * variable from this string (`${name.toUpperCase()}_CODING_AGENT_DIR`,
36
37
  * `dist/config.js`), and a space would put a space into an environment variable
37
38
  * name. The full product name goes into the header our own extension draws.
39
+ *
40
+ * It is the brand's terminal command (spec #344, RQ-6), so the engine's `/help` and exit hints
41
+ * name the command a user actually types.
38
42
  */
39
- export const ENGINE_BRAND = "explyt";
43
+ export const ENGINE_BRAND = BRAND.command;
40
44
 
41
45
  /**
42
46
  * Engine variable naming the directory it keeps its configuration in, as the
@@ -70,6 +74,17 @@ export const AGENT_DIR_ENV = "PI_CODING_AGENT_DIR";
70
74
  */
71
75
  export const BRANDED_AGENT_DIR_ENV = `${ENGINE_BRAND.toUpperCase()}_CODING_AGENT_DIR`;
72
76
 
77
+ /**
78
+ * Marks which brand set PI_CODING_AGENT_DIR (spec #344, RQ-18, RQ-23).
79
+ *
80
+ * The wrapper sets it to its brand id whenever it sets the agent directory. A process of the
81
+ * other brand started from inside this one (a run of the other brand's command from this
82
+ * session's bash tool)
83
+ * inherits PI_CODING_AGENT_DIR; the marker is how it tells the inherited directory from a
84
+ * user's deliberate export, which carries no marker and keeps winning (spec 010 RQ-4).
85
+ */
86
+ export const AGENT_DIR_BRAND_ENV = "AGENT_DIR_BRAND";
87
+
73
88
  /**
74
89
  * Silences the engine's "New version X is available. Run `<name> update`" banner.
75
90
  *
@@ -92,7 +107,7 @@ export const ENGINE_TELEMETRY_ENV = "PI_TELEMETRY";
92
107
  * `initialize` and a restored session's id (`vendor/pi-acp/src/acp/translate/telemetry-env.ts`), plus
93
108
  * the launching turn a subagent manager hands its child
94
109
  * (`packages/shared/src/telemetry/correlation.ts`). Spelled here because `cli/` ships without
95
- * those modules. A tool of an ACP engine inherits all three, so an `explyt` it runs would report
110
+ * those modules. A tool of an ACP engine inherits all three, so a product command it runs would report
96
111
  * the IDE's client and conversation; a terminal session is `terminal` (spec 022 RQ-6), so the
97
112
  * terminal launch removes them. `EXPLYT_TELEMETRY_RECORD_FILE` is not here: RQ-32 wants it
98
113
  * inherited.
@@ -368,12 +383,19 @@ export function agentConfigDir(env = process.env, home = homedir()) {
368
383
  * directory in MSYS form on Windows gets a split until that is confirmed on a machine.
369
384
  */
370
385
  export function withAgentDir(env, home = homedir()) {
386
+ // A PI_CODING_AGENT_DIR another brand's wrapper set is that brand's state, not an explicit
387
+ // choice (spec #344, RQ-23): only that inherited value is ignored. This brand's own variable
388
+ // (set by its launch file for a non-default installation root) is still honoured, the same
389
+ // rule cli/launch.mjs applies. Without a marker the value is a user's export, honoured as before.
390
+ const marker = env[AGENT_DIR_BRAND_ENV];
391
+ const inheritedFromOtherBrand = typeof marker === "string" && marker !== "" && marker !== BRAND.id;
371
392
  // The unbranded name wins when both are set and disagree: it is the one every
372
393
  // reader in the process understands, and two different directories in one process
373
394
  // is the split this function exists to prevent.
374
- const explicit = explicitAgentDir(env[AGENT_DIR_ENV], home) ?? explicitAgentDir(env[BRANDED_AGENT_DIR_ENV], home);
395
+ const unbranded = inheritedFromOtherBrand ? null : explicitAgentDir(env[AGENT_DIR_ENV], home);
396
+ const explicit = unbranded ?? explicitAgentDir(env[BRANDED_AGENT_DIR_ENV], home);
375
397
  const dir = explicit ?? agentConfigDir(env, home);
376
- return { ...env, [AGENT_DIR_ENV]: dir, [BRANDED_AGENT_DIR_ENV]: dir };
398
+ return { ...env, [AGENT_DIR_ENV]: dir, [BRANDED_AGENT_DIR_ENV]: dir, [AGENT_DIR_BRAND_ENV]: BRAND.id };
377
399
  }
378
400
 
379
401
  /** The engine's reading of an explicit agent-directory value; null when it counts as unset. */
@@ -516,7 +538,7 @@ export function ensureAgentSettings(agentDir, {
516
538
  const WEB_SEARCH_CONFIG_FILE = "web-search.json";
517
539
 
518
540
  /** Last path segment of the directory GitHub repositories are cloned into (spec 015, RQ-3). */
519
- const GITHUB_CLONE_DIR_NAME = "explyt-github-repos";
541
+ const GITHUB_CLONE_DIR_NAME = BRAND.githubCloneDir;
520
542
 
521
543
  const isPlainObject = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
522
544
 
@@ -976,7 +998,7 @@ export const TERMINAL_ISOLATION_FLAGS = ["--no-extensions", "--no-skills", "--no
976
998
  * Engine flag that keeps a run out of the session store.
977
999
  *
978
1000
  * `login` is not a work session: nothing said in it is worth resuming, and the engine's
979
- * exit hint ("To resume this session: explyt --session <id>") would advertise a session
1001
+ * exit hint ("To resume this session: <command> --session <id>") would advertise a session
980
1002
  * nobody wants back. Ephemeral, the engine writes no session file and prints no hint.
981
1003
  * The bare TUI keeps its sessions -- there resuming is a feature.
982
1004
  */
@@ -1183,22 +1205,34 @@ export function parseArgs(argv) {
1183
1205
  return { command, help, version, rest, terminalLogin };
1184
1206
  }
1185
1207
 
1186
- export function helpText() {
1208
+ /**
1209
+ * The usage text. `marketplace` says whether this installation carries the `plugin` command: the
1210
+ * Marketplace ships in dogfood and debug builds only (scripts/extension-classification.mjs), so a
1211
+ * stable build does not advertise it. Off unless the caller found the command.
1212
+ */
1213
+ export function helpText({ marketplace = false } = {}) {
1214
+ const { command: cmd, cliPackage: pkg, developer, productName } = BRAND;
1215
+ // Column alignment is part of the text: the existing lines stay byte-identical (spec #344,
1216
+ // RQ-17), and a longer or shorter command or package shifts the description columns.
1217
+ const pad = (text, width) => text.padEnd(Math.max(width, text.length + 1));
1218
+ const npxWidth = 48;
1219
+ const cmdWidth = 21;
1220
+ const installWidth = 49;
1187
1221
  return [
1188
- "Explyt AI Agent",
1222
+ productName,
1189
1223
  "",
1190
- "Install with the Explyt installer, then run explyt:",
1191
- " explyt install [--channel stable|dogfood] install or update the managed installation",
1224
+ `Install with the ${developer} installer, then run ${cmd}:`,
1225
+ `${pad(` ${cmd} install [--channel stable|dogfood]`, installWidth)}install or update the managed installation`,
1192
1226
  "Or run a pinned version without installing:",
1193
- " npx -y @explyt/ai-agent@<version> start the terminal UI",
1194
- " npx -y @explyt/ai-agent@<version> tui start the terminal UI",
1195
- " npx -y @explyt/ai-agent@<version> login store a provider credential",
1196
- " npx -y @explyt/ai-agent@<version> logout remove stored credentials",
1227
+ `${pad(` npx -y ${pkg}@<version>`, npxWidth)}start the terminal UI`,
1228
+ `${pad(` npx -y ${pkg}@<version> tui`, npxWidth)}start the terminal UI`,
1229
+ `${pad(` npx -y ${pkg}@<version> login`, npxWidth)}store a provider credential`,
1230
+ `${pad(` npx -y ${pkg}@<version> logout`, npxWidth)}remove stored credentials`,
1197
1231
  "",
1198
- "Commands: explyt tui | login | logout | plugin | install | update | config",
1199
- " explyt update update the managed installation now (same as install)",
1200
- " explyt config set updateChannel stable|dogfood | autoUpdate true|false",
1201
- " explyt plugin manage Marketplace plugins (explyt plugin help)",
1232
+ `Commands: ${cmd} tui | login | logout | ${marketplace ? "plugin | " : ""}install | update | config`,
1233
+ `${pad(` ${cmd} update`, cmdWidth)}update the managed installation now (same as install)`,
1234
+ ` ${cmd} config set updateChannel stable|dogfood | autoUpdate true|false`,
1235
+ ...(marketplace ? [`${pad(` ${cmd} plugin`, cmdWidth)}manage Marketplace plugins (${cmd} plugin help)`] : []),
1202
1236
  " --permission-mode default|paranoid|bypassPermissions start the terminal UI in that mode",
1203
1237
  " -h, --help show this message",
1204
1238
  " -v, --version print the version",
@@ -1206,7 +1240,7 @@ export function helpText() {
1206
1240
  "Credentials and sessions are separate from other agents: logging in to a pi you run",
1207
1241
  "by hand does not log in here, so run login once for this agent.",
1208
1242
  "The terminal UI notes a newer version at startup; PI_OFFLINE=1 turns the check off.",
1209
- "IDE (ACP) integration is set up by the Explyt installer, which places the adapter",
1243
+ `IDE (ACP) integration is set up by the ${developer} installer, which places the adapter`,
1210
1244
  "next to this package; it is not part of this package.",
1211
1245
  "",
1212
1246
  ].join("\n");
package/dist/cli/index.js CHANGED
@@ -1,15 +1,15 @@
1
1
  #!/usr/bin/env node
2
- // The single user-facing entry point of @explyt/ai-agent.
2
+ // The single user-facing entry point of the CLI package (the brand's `cliPackage`, spec #344).
3
3
  //
4
4
  // The public npm package is a TERMINAL product:
5
5
  //
6
- // npx @explyt/ai-agent a human: start the terminal UI
7
- // npx @explyt/ai-agent tui the same, spelled explicitly
8
- // npx @explyt/ai-agent login a human, once: store a provider credential
9
- // npx @explyt/ai-agent logout a human: remove the stored credentials
6
+ // npx <cli package> a human: start the terminal UI
7
+ // npx <cli package> tui the same, spelled explicitly
8
+ // npx <cli package> login a human, once: store a provider credential
9
+ // npx <cli package> logout a human: remove the stored credentials
10
10
  //
11
- // ACP is NOT part of this package: the adapter is the separate package
12
- // `@explyt/acp-adapter` (spec 019). `acp` loads it when the installer placed it next to
11
+ // ACP is NOT part of this package: the adapter is the brand's separate adapter package
12
+ // (spec 019). `acp` loads it when the installer placed it next to
13
13
  // this CLI in an installed release, or from the backend and debug trees, which carry it
14
14
  // inside this package root; otherwise `acp` refuses and names the installer. The CLI
15
15
  // never depends on the adapter (RQ-1).
@@ -29,6 +29,7 @@
29
29
  // only. Any stray write corrupts the stream and the client drops the connection, so
30
30
  // all diagnostics in this file go to stderr.
31
31
 
32
+ import { BRAND } from "./brand-profile.js";
32
33
  import { installationRoot, secondRootWarning } from "./install-roots.js";
33
34
  // The installer, `config` and the update helpers are imported in the branches that use them:
34
35
  // `acp` uses none, and their ~20 modules plus semver would be files every cold ACP start opens.
@@ -74,12 +75,18 @@ if (args.terminalLogin) {
74
75
  }
75
76
 
76
77
  function fail(message, code = 1) {
77
- process.stderr.write(`explyt: ${message}\n`);
78
+ process.stderr.write(`${BRAND.command}: ${message}\n`);
78
79
  process.exit(code);
79
80
  }
80
81
 
81
82
  if (!packageRoot) fail("cannot locate the package root (no package.json above this file)");
82
83
 
84
+ // The `plugin` command's module: bundled beside this file in a tree that carries the Marketplace,
85
+ // the TypeScript source in a checkout. A stable build carries neither (the Marketplace is
86
+ // dogfood-only, scripts/extension-classification.mjs), so it neither offers nor runs the command.
87
+ const pluginEntry = [join(here, "plugin.js"), join(packageRoot, "extensions", "plugins", "cli.ts")]
88
+ .find((candidate) => existsSync(candidate));
89
+
83
90
  /**
84
91
  * How to reach the engine that ships alongside this package.
85
92
  *
@@ -108,7 +115,7 @@ async function findEngine() {
108
115
  // the installed manifest. A failed shim is reported and the engine runs unbranded.
109
116
  const shim = ensureEngineShim(engineDir, engineShimDir(process.env, undefined, engineDir));
110
117
  if (shim.status === "failed") {
111
- process.stderr.write(`explyt: engine keeps its own name: ${shim.detail}\n`);
118
+ process.stderr.write(`${BRAND.command}: engine keeps its own name: ${shim.detail}\n`);
112
119
  }
113
120
  return { engineDir, entrypoint, binDir, shimDir: shim.shimDir };
114
121
  }
@@ -118,9 +125,9 @@ async function requireEngine() {
118
125
  if (engine) return engine;
119
126
  fail(
120
127
  `the agent engine is missing (looked for ${ENGINE_PACKAGE} next to ${packageRoot}).\n` +
121
- " A global install: reinstall it with npm i -g @explyt/ai-agent@<version>.\n" +
128
+ ` A global install: reinstall it with npm i -g ${BRAND.cliPackage}@<version>.\n` +
122
129
  " An npx run: npx reuses its cached copy, so delete the _npx directory inside\n" +
123
- " the npm cache (npm config get cache) and run npx -y @explyt/ai-agent@<version> again.\n" +
130
+ ` the npm cache (npm config get cache) and run npx -y ${BRAND.cliPackage}@<version> again.\n` +
124
131
  " The engine is a peer dependency: with legacy-peer-deps=true in an npmrc (or\n" +
125
132
  " --legacy-peer-deps) npm skips it, so remove that setting before reinstalling.",
126
133
  );
@@ -145,7 +152,7 @@ if (args.command === "acp" && (args.help || args.version)) {
145
152
  }
146
153
 
147
154
  if (args.help) {
148
- process.stdout.write(helpText());
155
+ process.stdout.write(helpText({ marketplace: pluginEntry !== undefined }));
149
156
  process.exit(0);
150
157
  }
151
158
 
@@ -155,7 +162,7 @@ if (args.version) {
155
162
  process.stdout.write(`${version}\n`);
156
163
  // RQ-46: a second installation root is named on stderr; stdout stays the version.
157
164
  const secondRoot = secondRootWarning({ root: installationRoot() });
158
- if (secondRoot) process.stderr.write(`explyt: warning: ${secondRoot}\n`);
165
+ if (secondRoot) process.stderr.write(`${BRAND.command}: warning: ${secondRoot}\n`);
159
166
  process.exit(0);
160
167
  }
161
168
 
@@ -181,10 +188,8 @@ if (args.command === "config") {
181
188
  // tree carries them bundled beside this file, a source checkout runs the module itself through
182
189
  // Node's type stripping.
183
190
  if (args.command === "plugin") {
184
- const candidates = [join(here, "plugin.js"), join(packageRoot, "extensions", "plugins", "cli.ts")];
185
- const entry = candidates.find((candidate) => existsSync(candidate));
186
- if (!entry) fail("the plugin command is missing from this installation");
187
- const { runPluginCli } = await import(pathToFileURL(entry).href);
191
+ if (!pluginEntry) fail("the plugin command is not available in this build: Marketplace plugins ship in dogfood builds only", 2);
192
+ const { runPluginCli } = await import(pathToFileURL(pluginEntry).href);
188
193
  const code = await runPluginCli(args.rest, {
189
194
  stdout: (text) => process.stdout.write(text),
190
195
  stderr: (text) => process.stderr.write(text),
@@ -207,7 +212,7 @@ const wantsTui = args.command === "tui" || (!args.command && !wantsAcp);
207
212
  if (!args.command && args.rest[0] && !args.rest[0].startsWith("-")) {
208
213
  // An unknown first word is a mistyped command, not a request to start the UI.
209
214
  // Flags are forwarded to the engine untouched.
210
- fail(`unknown command: ${args.rest[0]}\n\n${helpText()}`, 2);
215
+ fail(`unknown command: ${args.rest[0]}\n\n${helpText({ marketplace: pluginEntry !== undefined })}`, 2);
211
216
  }
212
217
 
213
218
  // `logout` is the disposal half of `login`: it deletes the credential file inside
@@ -226,8 +231,8 @@ if (wantsLogout) {
226
231
  fail(`cannot remove the stored credentials at ${authFile}: ${error.message}`);
227
232
  }
228
233
  process.stderr.write(existed
229
- ? `explyt: credentials removed (${authFile})\n`
230
- : `explyt: no stored credentials at ${authFile}\n`);
234
+ ? `${BRAND.command}: credentials removed (${authFile})\n`
235
+ : `${BRAND.command}: no stored credentials at ${authFile}\n`);
231
236
  process.exit(0);
232
237
  }
233
238
 
@@ -260,7 +265,7 @@ if (wantsTui || wantsLogin) {
260
265
  // The engine's own header must not flash before ours; see SEEDED_SETTINGS.
261
266
  const settings = ensureAgentSettings(env[AGENT_DIR_ENV]);
262
267
  if (settings.status === "failed") {
263
- process.stderr.write(`explyt: engine settings not seeded: ${settings.detail}\n`);
268
+ process.stderr.write(`${BRAND.command}: engine settings not seeded: ${settings.detail}\n`);
264
269
  }
265
270
  // The web tools' configuration (spec 015, RQ-14): before the engine, which fixes its path on load.
266
271
  seedWebSearchConfig(env[AGENT_DIR_ENV], warnOnStderr);
@@ -291,13 +296,15 @@ if (wantsTui || wantsLogin) {
291
296
  // 1. the candidate paths inside this package root -- the backend and debug trees:
292
297
  // backend artifact: dist/cli/index.js -> dist/acp/index.js
293
298
  // dev source: cli/index.mjs -> vendor/pi-acp/dist/index.js
294
- // 2. an installed release npm produced from `@explyt/acp-adapter`:
295
- // hoisted <modules>/@explyt/acp-adapter next to <modules>/@explyt/ai-agent
299
+ // 2. an installed release npm produced from the brand's adapter package:
300
+ // hoisted <modules>/<scope>/acp-adapter next to <modules>/<scope>/ai-agent
296
301
  // nested the package whose node_modules holds this CLI
297
302
  // Never NODE_PATH, the working directory, PATH, a workspace link or a global prefix:
298
303
  // plain Node resolution would honour NODE_PATH and would follow this checkout's
299
304
  // workspace link to the unobfuscated submodule.
300
- const ADAPTER_PACKAGE = "@explyt/acp-adapter";
305
+ const ADAPTER_PACKAGE = BRAND.adapterPackage;
306
+ /** The npm scope both brand packages live in (`@<brand>`), and the adapter's directory name in it. */
307
+ const [ADAPTER_SCOPE, ADAPTER_DIR] = ADAPTER_PACKAGE.split("/");
301
308
 
302
309
  /** The adapter's `main` entry in `dir`, or null when `dir` is not the adapter package. */
303
310
  function adapterMain(dir) {
@@ -324,7 +331,7 @@ function installedReleaseAdapter(root) {
324
331
  }
325
332
  const scope = dirname(real);
326
333
  const modules = dirname(scope);
327
- if (basename(scope) !== "@explyt" || basename(modules) !== "node_modules") return null;
334
+ if (basename(scope) !== ADAPTER_SCOPE || basename(modules) !== "node_modules") return null;
328
335
  const accept = (dir) => {
329
336
  try {
330
337
  // The package directory itself must be real: npm installs packages, a link is a workspace.
@@ -335,7 +342,7 @@ function installedReleaseAdapter(root) {
335
342
  return adapterMain(dir);
336
343
  };
337
344
  // hoisted: a sibling in the same node_modules
338
- const hoisted = accept(join(modules, "@explyt", "acp-adapter"));
345
+ const hoisted = accept(join(modules, ADAPTER_SCOPE, ADAPTER_DIR));
339
346
  if (hoisted) return hoisted;
340
347
  // nested: the package whose node_modules holds this CLI
341
348
  const owner = dirname(modules);
@@ -353,8 +360,8 @@ if (!adapter) {
353
360
  // What an old IDE entry, or a terminal-only install, runs (RQ-27).
354
361
  fail(
355
362
  "no ACP adapter is installed next to this package.\n" +
356
- " IDE integration is set up by the Explyt installer, which installs the adapter\n" +
357
- " together with this CLI; the Neo plugin runs it for you. See `explyt --help`.",
363
+ ` IDE integration is set up by the ${BRAND.developer} installer, which installs the adapter\n` +
364
+ ` together with this CLI; the Neo plugin runs it for you. See \`${BRAND.command} --help\`.`,
358
365
  );
359
366
  }
360
367
 
@@ -370,7 +377,7 @@ const engine = await requireEngine();
370
377
  // value inherited from an unrelated session must not mask a broken install (RQ-6, RQ-50).
371
378
  const explicitEngineCommand = process.env.PI_ACP_PI_COMMAND;
372
379
  if (!engine.binDir && !(explicitEngineCommand && existsSync(explicitEngineCommand))) {
373
- fail(`the agent engine's pi bin is missing (looked for node_modules/.bin/pi next to ${engine.engineDir}); reinstall with the Explyt installer`);
380
+ fail(`the agent engine's pi bin is missing (looked for node_modules/.bin/pi next to ${engine.engineDir}); reinstall with the ${BRAND.developer} installer`);
374
381
  }
375
382
  // Exactly one PATH key (RQ-6): the engine's `.bin` first, then this Node's directory, so
376
383
  // the engine's `#!/usr/bin/env node` finds the same Node even when the caller's PATH has
@@ -7,6 +7,10 @@
7
7
  import { openSync, readFileSync } from "node:fs";
8
8
  import { join, sep } from "node:path";
9
9
  import { isOffline } from "./update.js";
10
+ import { BRAND } from "./brand-profile.js";
11
+
12
+ /** The npm scope and directory of the brand's CLI package (`@<scope>/ai-agent`). */
13
+ const [CLI_SCOPE, CLI_DIR] = BRAND.cliPackage.split("/");
10
14
 
11
15
  export const AUTO_CHECK_TTL_MS = 24 * 60 * 60 * 1000;
12
16
  export const AUTO_CHECK_BACKOFF_MS = 60 * 60 * 1000;
@@ -17,7 +21,7 @@ export function managedRelease(packageRoot) {
17
21
  const parts = packageRoot.split(/[\\/]/);
18
22
  const n = parts.length;
19
23
  if (n < 7) return null;
20
- if (parts[n - 1] !== "ai-agent" || parts[n - 2] !== "@explyt" || parts[n - 3] !== "node_modules") return null;
24
+ if (parts[n - 1] !== CLI_DIR || parts[n - 2] !== CLI_SCOPE || parts[n - 3] !== "node_modules") return null;
21
25
  if (parts[n - 5] !== "releases" || parts[n - 6] !== "install") return null;
22
26
  const release = parts[n - 4];
23
27
  if (!release || release.startsWith(".")) return null;
@@ -49,7 +53,7 @@ function samePair(a, b) {
49
53
  }
50
54
 
51
55
  export function updateNoticeText(resolved) {
52
- return `Explyt AI Agent ${resolved.cliVersion} is available. Install it with the Explyt installer (explyt install).`;
56
+ return `${BRAND.productName} ${resolved.cliVersion} is available. Install it with the ${BRAND.developer} installer (${BRAND.command} install).`;
53
57
  }
54
58
 
55
59
  /**
@@ -90,7 +94,7 @@ export async function autoUpdate({ packageRoot, env = process.env, now = Date.no
90
94
  return "installer";
91
95
  }
92
96
 
93
- /** The detached `explyt install --json` a TUI starts (RQ-33). */
97
+ /** The detached `<command> install --json` a TUI starts (RQ-33). */
94
98
  export function installerCommand({ root, platform = process.platform }) {
95
99
  const install = join(root, "install");
96
100
  const file = readFileSync(join(install, "node.txt"), "utf8").trim();
@@ -105,7 +109,7 @@ export function installerCommand({ root, platform = process.platform }) {
105
109
  /** The restart notice (RQ-34), or null when the TUI runs the current release. */
106
110
  export function restartNotice({ running, current }) {
107
111
  if (!running || !current || running.release === current.release) return null;
108
- return `Explyt AI Agent ${current.cliVersion} is installed. Restart with explyt --continue to resume this session on it.`;
112
+ return `${BRAND.productName} ${current.cliVersion} is installed. Restart with ${BRAND.command} --continue to resume this session on it.`;
109
113
  }
110
114
 
111
115
  /**
@@ -9,6 +9,7 @@ import { join } from "node:path";
9
9
  import { randomBytes } from "node:crypto";
10
10
  import { isLive } from "./install-lock.js";
11
11
  import { withFileRetry } from "./install-file-retry.js";
12
+ import { BRAND } from "./brand-profile.js";
12
13
 
13
14
  export { withFileRetry };
14
15
 
@@ -49,7 +50,7 @@ export function readLeases(root, release) {
49
50
 
50
51
  /** The Node version an installed release's CLI manifest names (`explyt.node`). */
51
52
  export function releaseNodeVersion(releaseDir) {
52
- const manifest = readJson(join(releaseDir, "node_modules", "@explyt", "ai-agent", "package.json"));
53
+ const manifest = readJson(join(releaseDir, "node_modules", ...BRAND.cliPackage.split("/"), "package.json"));
53
54
  const node = manifest?.explyt?.node;
54
55
  return typeof node === "string" ? node : null;
55
56
  }
@@ -1,14 +1,18 @@
1
1
  // The terminal command and its PATH entry (spec 019, RQ-35, RQ-7).
2
2
  //
3
- // `~/.local/bin/explyt` (POSIX sh) or `%USERPROFILE%\.local\bin\explyt.cmd` runs the Node of
3
+ // `~/.local/bin/<command>` (POSIX sh) or `%USERPROFILE%\.local\bin\<command>.cmd` runs the Node of
4
4
  // `install/node.txt` with `install/launch.js`. Its content depends only on the installation
5
5
  // root, so a switch never rewrites a command a running shell or cmd is reading.
6
6
  import { existsSync, mkdirSync, readFileSync, renameSync, chmodSync, writeFileSync, rmSync } from "node:fs";
7
7
  import { dirname, posix, win32 } from "node:path";
8
8
  import { randomBytes } from "node:crypto";
9
+ import { BRAND } from "./brand-profile.js";
9
10
  import { PRODUCT_NAME } from "./package-name.js";
10
11
 
11
- export const COMMAND_MARKER = `${PRODUCT_NAME} terminal command, managed by the Explyt installer`;
12
+ /** The brand's command name (spec #344, RQ-6); the shell variable below is named after it too. */
13
+ const CMD = BRAND.command;
14
+
15
+ export const COMMAND_MARKER = `${PRODUCT_NAME} terminal command, managed by the ${BRAND.developer} installer`;
12
16
  export const PATH_LINE = 'export PATH="$HOME/.local/bin:$PATH"';
13
17
 
14
18
  function pathApi(platform) {
@@ -17,7 +21,7 @@ function pathApi(platform) {
17
21
 
18
22
  export function commandPath({ home, platform = process.platform }) {
19
23
  const p = pathApi(platform);
20
- return p.join(home, ".local", "bin", platform === "win32" ? "explyt.cmd" : "explyt");
24
+ return p.join(home, ".local", "bin", platform === "win32" ? `${CMD}.cmd` : CMD);
21
25
  }
22
26
 
23
27
  function shQuote(text) {
@@ -44,10 +48,10 @@ export function terminalCommand({ root, platform = process.platform }) {
44
48
  "#!/bin/sh",
45
49
  `# ${COMMAND_MARKER}`,
46
50
  `install_dir=${shQuote(install)}`,
47
- 'IFS= read -r explyt_node < "$install_dir/node.txt" || { echo "explyt: $install_dir/node.txt is missing; run the Explyt installer" >&2; exit 1; }',
48
- 'PATH="$(dirname "$explyt_node"):$PATH"',
51
+ `IFS= read -r ${CMD}_node < "$install_dir/node.txt" || { echo "${CMD}: $install_dir/node.txt is missing; run the ${BRAND.developer} installer" >&2; exit 1; }`,
52
+ `PATH="$(dirname "$${CMD}_node"):$PATH"`,
49
53
  "export PATH",
50
- 'exec "$explyt_node" "$install_dir/launch.js" "$@"',
54
+ `exec "$${CMD}_node" "$install_dir/launch.js" "$@"`,
51
55
  "",
52
56
  ].join("\n");
53
57
  }
@@ -120,10 +124,10 @@ export async function ensureCommandOnPath({ home, env = process.env, platform =
120
124
  return { pathInstruction: null, warnings: [] };
121
125
  }
122
126
 
123
- /** An earlier `explyt` on PATH than the managed command, or null. */
127
+ /** An earlier brand command on PATH than the managed command, or null. */
124
128
  export function findShadowing({ env = process.env, platform = process.platform, commandFile, exists = existsSync }) {
125
129
  const p = pathApi(platform);
126
- const names = platform === "win32" ? ["explyt.cmd", "explyt.exe", "explyt.bat", "explyt.ps1", "explyt"] : ["explyt"];
130
+ const names = platform === "win32" ? [`${CMD}.cmd`, `${CMD}.exe`, `${CMD}.bat`, `${CMD}.ps1`, CMD] : [CMD];
127
131
  for (const entry of pathEntries(env, platform)) {
128
132
  for (const name of names) {
129
133
  const candidate = p.join(entry, name);