@anhlp/alp 0.0.1-beta.3 → 0.0.1-beta.6

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 (175) hide show
  1. package/README.md +14 -410
  2. package/bin/alp.js +5 -0
  3. package/lib/launcher.js +136 -0
  4. package/package.json +19 -104
  5. package/dist/THIRD-PARTY-NOTICES.txt +0 -626
  6. package/dist/acp.js +0 -19903
  7. package/dist/alpd.js +0 -42558
  8. package/docs/modular-monorepo.md +0 -210
  9. package/packages/cli/README.md +0 -7
  10. package/packages/cli/dist/THIRD-PARTY-NOTICES.txt +0 -626
  11. package/packages/cli/dist/acp/agent.d.ts +0 -37
  12. package/packages/cli/dist/acp/agent.js +0 -489
  13. package/packages/cli/dist/acp/main.d.ts +0 -4
  14. package/packages/cli/dist/acp/main.js +0 -23
  15. package/packages/cli/dist/acp.js +0 -19903
  16. package/packages/cli/dist/alpd.js +0 -42558
  17. package/packages/cli/dist/backup.d.ts +0 -4
  18. package/packages/cli/dist/backup.js +0 -146
  19. package/packages/cli/dist/build-manifest.json +0 -142
  20. package/packages/cli/dist/cli.d.ts +0 -2
  21. package/packages/cli/dist/cli.js +0 -28
  22. package/packages/cli/dist/daemon.d.ts +0 -35
  23. package/packages/cli/dist/daemon.js +0 -126
  24. package/packages/cli/dist/index.d.ts +0 -1
  25. package/packages/cli/dist/index.js +0 -5
  26. package/packages/cli/dist/interfaces.d.ts +0 -16
  27. package/packages/cli/dist/interfaces.js +0 -96
  28. package/packages/cli/dist/library.d.ts +0 -140
  29. package/packages/cli/dist/library.js +0 -319
  30. package/packages/cli/dist/paths.d.ts +0 -3
  31. package/packages/cli/dist/paths.js +0 -18
  32. package/packages/cli/dist/projects.d.ts +0 -9
  33. package/packages/cli/dist/projects.js +0 -159
  34. package/packages/cli/dist/sessions.d.ts +0 -41
  35. package/packages/cli/dist/sessions.js +0 -447
  36. package/packages/cli/dist/tasks.d.ts +0 -29
  37. package/packages/cli/dist/tasks.js +0 -345
  38. package/packages/cli/dist/usage.d.ts +0 -3
  39. package/packages/cli/dist/usage.js +0 -72
  40. package/packages/client/README.md +0 -24
  41. package/packages/client/dist/acp-probe.d.ts +0 -44
  42. package/packages/client/dist/acp-probe.js +0 -37
  43. package/packages/client/dist/antigravity-probe.d.ts +0 -34
  44. package/packages/client/dist/antigravity-probe.js +0 -95
  45. package/packages/client/dist/command.d.ts +0 -5
  46. package/packages/client/dist/command.js +0 -33
  47. package/packages/client/dist/doctor.d.ts +0 -13
  48. package/packages/client/dist/doctor.js +0 -587
  49. package/packages/client/dist/index.d.ts +0 -116
  50. package/packages/client/dist/index.js +0 -500
  51. package/packages/client/dist/ipc.d.ts +0 -42
  52. package/packages/client/dist/ipc.js +0 -69
  53. package/packages/client/dist/library-test.d.ts +0 -94
  54. package/packages/client/dist/library-test.js +0 -37
  55. package/packages/client/dist/mcp-probe.d.ts +0 -24
  56. package/packages/client/dist/mcp-probe.js +0 -147
  57. package/packages/client/dist/portable.d.ts +0 -18
  58. package/packages/client/dist/portable.js +0 -27
  59. package/packages/client/dist/prompt.d.ts +0 -32
  60. package/packages/client/dist/prompt.js +0 -59
  61. package/packages/client/dist/render.d.ts +0 -6
  62. package/packages/client/dist/render.js +0 -120
  63. package/packages/client/dist/rpc/client.d.ts +0 -82
  64. package/packages/client/dist/rpc/client.js +0 -292
  65. package/packages/client/dist/rpc/errors.d.ts +0 -42
  66. package/packages/client/dist/rpc/errors.js +0 -34
  67. package/packages/client/dist/rpc/framing.d.ts +0 -21
  68. package/packages/client/dist/rpc/framing.js +0 -57
  69. package/packages/client/dist/rpc/handshake.d.ts +0 -19
  70. package/packages/client/dist/rpc/handshake.js +0 -8
  71. package/packages/client/dist/rpc/reconnect.d.ts +0 -97
  72. package/packages/client/dist/rpc/reconnect.js +0 -227
  73. package/packages/client/dist/rpc/timers.d.ts +0 -11
  74. package/packages/client/dist/rpc/timers.js +0 -11
  75. package/packages/client/dist/service.d.ts +0 -50
  76. package/packages/client/dist/service.js +0 -143
  77. package/packages/client/dist/setup.d.ts +0 -2
  78. package/packages/client/dist/setup.js +0 -146
  79. package/packages/client/dist/supervise.d.ts +0 -12
  80. package/packages/client/dist/supervise.js +0 -87
  81. package/packages/client/dist/websocket.d.ts +0 -51
  82. package/packages/client/dist/websocket.js +0 -190
  83. package/packages/core/README.md +0 -7
  84. package/packages/core/dist/adapter.d.ts +0 -20
  85. package/packages/core/dist/adapter.js +0 -30
  86. package/packages/core/dist/atomic.d.ts +0 -2
  87. package/packages/core/dist/atomic.js +0 -28
  88. package/packages/core/dist/backup.d.ts +0 -27
  89. package/packages/core/dist/backup.js +0 -582
  90. package/packages/core/dist/beads.d.ts +0 -65
  91. package/packages/core/dist/beads.js +0 -213
  92. package/packages/core/dist/delegation.d.ts +0 -2
  93. package/packages/core/dist/delegation.js +0 -32
  94. package/packages/core/dist/errors.d.ts +0 -4
  95. package/packages/core/dist/errors.js +0 -11
  96. package/packages/core/dist/formulas.d.ts +0 -25
  97. package/packages/core/dist/formulas.js +0 -201
  98. package/packages/core/dist/hook-run.d.ts +0 -44
  99. package/packages/core/dist/hook-run.js +0 -60
  100. package/packages/core/dist/hooks.d.ts +0 -35
  101. package/packages/core/dist/hooks.js +0 -30
  102. package/packages/core/dist/index.d.ts +0 -30
  103. package/packages/core/dist/index.js +0 -63
  104. package/packages/core/dist/init.d.ts +0 -30
  105. package/packages/core/dist/init.js +0 -89
  106. package/packages/core/dist/library-edit.d.ts +0 -93
  107. package/packages/core/dist/library-edit.js +0 -368
  108. package/packages/core/dist/library.d.ts +0 -28
  109. package/packages/core/dist/library.js +0 -108
  110. package/packages/core/dist/log.d.ts +0 -37
  111. package/packages/core/dist/log.js +0 -156
  112. package/packages/core/dist/migrations.d.ts +0 -9
  113. package/packages/core/dist/migrations.js +0 -95
  114. package/packages/core/dist/models.d.ts +0 -11
  115. package/packages/core/dist/models.js +0 -44
  116. package/packages/core/dist/permissions.d.ts +0 -14
  117. package/packages/core/dist/permissions.js +0 -229
  118. package/packages/core/dist/profiles.d.ts +0 -27
  119. package/packages/core/dist/profiles.js +0 -122
  120. package/packages/core/dist/promptsafe.d.ts +0 -1
  121. package/packages/core/dist/promptsafe.js +0 -13
  122. package/packages/core/dist/providers.d.ts +0 -90
  123. package/packages/core/dist/providers.js +0 -101
  124. package/packages/core/dist/resolver.d.ts +0 -41
  125. package/packages/core/dist/resolver.js +0 -240
  126. package/packages/core/dist/safe-path.d.ts +0 -2
  127. package/packages/core/dist/safe-path.js +0 -24
  128. package/packages/core/dist/shell.d.ts +0 -16
  129. package/packages/core/dist/shell.js +0 -22
  130. package/packages/core/dist/state-schema.d.ts +0 -9
  131. package/packages/core/dist/state-schema.js +0 -54
  132. package/packages/core/dist/task-rows.d.ts +0 -47
  133. package/packages/core/dist/task-rows.js +0 -45
  134. package/packages/core/dist/tasks.d.ts +0 -128
  135. package/packages/core/dist/tasks.js +0 -835
  136. package/packages/core/dist/teams.d.ts +0 -80
  137. package/packages/core/dist/teams.js +0 -159
  138. package/packages/core/dist/template-path.d.ts +0 -2
  139. package/packages/core/dist/template-path.js +0 -10
  140. package/packages/core/dist/trust.d.ts +0 -8
  141. package/packages/core/dist/trust.js +0 -47
  142. package/packages/core/dist/types.d.ts +0 -51
  143. package/packages/core/dist/upgrade.d.ts +0 -10
  144. package/packages/core/dist/upgrade.js +0 -200
  145. package/packages/core/dist/user-settings.d.ts +0 -6
  146. package/packages/core/dist/user-settings.js +0 -72
  147. package/packages/core/dist/validation.d.ts +0 -17
  148. package/packages/core/dist/validation.js +0 -295
  149. package/packages/core/dist/verify.d.ts +0 -15
  150. package/packages/core/dist/verify.js +0 -123
  151. package/packages/core/dist/workflow.d.ts +0 -45
  152. package/packages/core/dist/workflow.js +0 -33
  153. package/templates/ALP.md +0 -52
  154. package/templates/agents/lead/AGENT.md +0 -90
  155. package/templates/agents/main/AGENT.md +0 -150
  156. package/templates/agents/oracle/AGENT.md +0 -44
  157. package/templates/agents/peer/AGENT.md +0 -55
  158. package/templates/agents/reviewer/AGENT.md +0 -80
  159. package/templates/agents/supervisor/AGENT.md +0 -72
  160. package/templates/providers/antigravity.json +0 -6
  161. package/templates/providers/opencode.json +0 -7
  162. package/templates/role-skills.json +0 -8
  163. package/templates/skills/bug-loop/SKILL.md +0 -44
  164. package/templates/skills/bug-loop/references/test-proof.md +0 -25
  165. package/templates/skills/goal-griller/SKILL.md +0 -49
  166. package/templates/skills/prompt-leverage/SKILL.md +0 -37
  167. package/templates/skills/prompt-leverage/references/brief-template.md +0 -27
  168. package/templates/skills/sequence-execution-plan/SKILL.md +0 -42
  169. package/templates/skills/sequence-execution-plan/references/plan-template.md +0 -22
  170. package/templates/skills/smart-commits/SKILL.md +0 -40
  171. package/templates/skills/xia/SKILL.md +0 -42
  172. package/templates/teams/cafe/HOUSE_RULES.md +0 -1
  173. package/templates/teams/cafe/team.json +0 -15
  174. package/templates/teams/pho/HOUSE_RULES.md +0 -1
  175. package/templates/teams/pho/team.json +0 -14
package/README.md CHANGED
@@ -1,418 +1,22 @@
1
- # ALP prototype
1
+ # @anhlp/alp
2
2
 
3
- Provider-neutral filesystem agent resolution with a server-side Paseo provider. Includes project initialization and the existing Phases 2–5 prototype. Editors that speak the Agent Client Protocol use ALP through `alp acp`.
4
-
5
- ## Install
6
-
7
- The current release is the beta `0.0.1-beta.3` (until it is on npm, `@beta` installs
8
- `0.0.0-beta.2`; check with `npm view @anhlp/alp dist-tags`): see the
9
- [release notes](docs/alp/beta-release-notes.md) for what it includes and its known issues, and
10
- [installing, upgrading and removing the beta](docs/alp/beta-install.md) for every step. Name the
11
- beta when you install (`@beta`, or exactly `@0.0.1-beta.3`); a plain `npm install -g @anhlp/alp`
12
- installs whatever npm's `latest` tag points at, which moves to a new beta only after that
13
- beta's post-publish checks.
14
-
15
- Supported: Linux or macOS, Node.js 22 (22.12 or later) or 24, and a signed-in Codex CLI or
16
- Claude Code on PATH. Windows and Node.js 26 are experimental; Node.js 20 is not supported.
3
+ The `alp` command of [ALP](https://github.com/phucanh08/alp). This package is a small launcher:
4
+ npm installs the ALP executable for your machine as one optional dependency,
5
+ `@anhlp/alp-<os>-<arch>` (darwin-arm64, darwin-x64, linux-x64, linux-arm64 (glibc), win32-x64),
6
+ and `alp` runs it with your arguments. Nothing is downloaded after install.
17
7
 
18
8
  ```sh
19
9
  npm install -g @anhlp/alp@beta
20
- alp doctor # checks Codex and Claude, settings, and leftovers; --fix repairs the safe ones
21
- alp daemon start
22
- ```
23
-
24
- Choose the models Phở and Cafe run on in a project, for Codex only, Claude only, or a mix:
25
-
26
- ```sh
27
- alp setup # select boxes: runtime, which provider first, then Save / Change a model
28
- alp setup --runtime codex # Codex only
29
- alp setup --runtime claude # Claude only
30
- alp setup --runtime mix # both, Claude first
31
- alp setup --runtime mix --prefer codex --codex-model gpt-6-sol --claude-oracle claude-fable-5-1
32
- ```
33
-
34
- Setup writes `team.pho` and `team.cafe` into the project's `.alp/settings.json`
35
- (running `alp init` first when there is none) and keeps the rest of the file. Main runs
36
- on one model, of the preferred provider. Lead and reviewer take `--claude-model` /
37
- `--codex-model`, the supervisor `--claude-supervisor` / `--codex-supervisor`, and the
38
- oracle `--claude-oracle` / `--codex-oracle`; in mix each lists both providers, the
39
- preferred one first, and an only mode lists its own. Peers get `claude:*` and/or
40
- `codex:*`: any model of the enabled providers, chosen with its effort for each task by
41
- whoever assigns it. Defaults: Opus 5.5 / GPT-6.1 Sol at high, Sonnet 5 / GPT-6 Luna at
42
- medium, Fable 5.1 at high / GPT-6 Astra at low (the only effort it takes). Setup does
43
- not install CLIs, log in or call models: run `alp doctor` for that. New sessions use
44
- the profiles; running sessions keep theirs.
45
-
46
- The package provides the `alp` command and `alpd`, the per-user daemon that runs ALP sessions. To view and drive sessions in Paseo, also install the [Paseo plugin](integrations/paseo/README.md).
47
-
48
- ## Initialize a project
49
-
50
- Run `alp init [directory]` (or `node /path/to/alp/packages/cli/dist/cli.js init [directory]` when running a source checkout after `npm run build`). The directory defaults to the current working directory.
51
-
52
- When the Paseo plugin opens an ALP session in a repository that lacks `ALP.md` or `.alp/settings.json`, it performs this initialization automatically. Initialization fills missing scaffold files and never replaces existing files, so partial and customized ALP setups are preserved.
53
-
54
- Initialization creates `ALP.md` and `.alp/settings.json`. The six agents `main`, `lead`, `peer`, `oracle`, `reviewer` and `supervisor` are built into ALP and follow its updates; a project does not copy them. To change one, make an agent of the same name in your library (`~/.alp/agents/`) or in the project (`.alp/agents/`); see [agent library](docs/alp/agent-library.md). Main owns delivery; Phở lets main implement or call peer, while Cafe assigns technical execution to lead. Oracle and reviewer provide read-only advice and review; the supervisor reviews main's process after each turn.
55
-
56
- The default settings select main with the Phở team. Main runs on Opus 5.5 with high effort and full access unless settings or the caller choose otherwise. Paseo exposes an `alp_delegate` tool that executes real child sessions and returns their evidence. See [Phở and Cafe teams](docs/alp/team-workflow.md).
57
-
58
- Six [role skills](docs/alp/role-skills.md) cover intake, research, planning, delegation briefs, bug diagnosis, and commit packaging. They live in your skill library in `$ALP_HOME` (default `~/.alp`), seeded on first use, together with `role-skills.json`, which assigns six to main, five to lead and three to peer. Edit either freely: app updates replace only library files you have not changed. Each agent selects directly from its skills and loads contents only when needed.
59
-
60
- Repeated runs fill in missing files and preserve existing file contents, including custom settings. Conflicting filesystem entry types cause an error; files already created before an error remain available for a later retry. Run `npm run test:init` to verify initialization independently.
61
-
62
- For projects created with an earlier scaffold, run `alp upgrade [directory]` (or the built CLI above). This backs up and updates recognized original instructions, adds team settings, renames the old `smart`/`supervised` profiles to `pho`/`cafe`, and archives project copies of skills that still match the shipped ones, so the library applies. Copies of the built-in agents that are still as ALP shipped them are put away too, so the project uses the built-ins; a copy you edited, or added skills, MCP servers or hooks to, stays as the project's override. The old shipped graph migrates to Cafe; custom graphs and customized instructions/skills remain unchanged.
63
-
64
- ## Development
65
-
66
- Use Node 22 (22.12 or later) or 24 and an npm version compatible with your Node installation:
67
-
68
- ```sh
69
- npm ci
70
- npm run check
71
- npm test
72
- ```
73
-
74
- `npm test` builds every workspace and runs the hermetic suites (unit and integration together, each file once) in a throwaway `ALP_HOME`. `npm run build` builds only the core CLI, daemon, ACP and runtime bundles; `npm run build:paseo` builds the optional plugin. The runtime in `packages/runtime/src` owns native harnesses, sessions, delegation, and mail without importing Paseo; `alpd` hosts it, and the plugin is a viewer over the daemon ([design](plans/reference/ALPD.md)).
75
-
76
- Tests are split by owner:
77
-
78
- | Group | Location | Run |
79
- | --- | --- | --- |
80
- | Unit and package-level contract | `packages/<name>/tests/` (plugin: `integrations/paseo/tests/`) | `npm test -w @anhlp/alp-core` (or `-runtime`, `-client`, `-daemon`, `-cli`, `-protocol`, `@anhlp/alp-paseo`); all at once: `npm run test:unit` |
81
- | Integration (several packages, CLI processes, repository checks) | `test/integration/` | `npm run test:integration` |
82
- | E2E (live providers, opt-in) | `test/e2e/` | `npm run test:e2e` or one of `test:e2e:*` |
83
- | Shared fixtures such as fake transports | `test/support/` | imported by package and integration tests |
84
-
85
- `npm test` runs unit and integration; `npm run test:all` adds E2E. A package test imports only its own package and the packages it declares as dependencies; `npm run check:boundaries` and `test/integration/layout.test.js` enforce that. The core packages (protocol, core, runtime, client, daemon, cli) test without installing Paseo.
86
-
87
- Runtime tests drive scripted agents from `test/support/fake-agent.js` instead of Codex or Claude: they call ALP tools, report context fill, hand off slowly, crash mid-turn or hit a usage limit on cue. `packages/client/tests/golden.test.js` compares what `alp log`, `alp ps` and `alp task report` print with `packages/client/tests/golden/*.txt`; after checking an intended change, accept it with `ALP_UPDATE_GOLDEN=1 npm test -w @anhlp/alp-client`.
88
-
89
- ## Project files
90
-
91
- ```text
92
- your-project/
93
- ALP.md
94
- .alp/
95
- settings.json
96
- tasks/ # one JSON file per task; commit it with the project
97
- agents/ # optional: project agents, and overrides of built-in or library ones
98
- your-agent/AGENT.md
99
- your-agent/agent.json
100
- skills/ mcp/ hooks/ # optional: project entries; each overrides a library entry of its name
101
- ```
102
-
103
- `alp agents`, `alp skills`, `alp mcp` and `alp hooks` list what a project can use: ALP's built-ins, your library in `~/.alp`, and the project's own entries, with what each overrides and which agents use it. `alp agent|team|skill|mcp|hook new|edit|cp|mv|rm` change them, in your library or with `--project` in the project, and `alp mcp test` and `alp hook test` try one out ([editing](docs/alp/agent-library.md#editing)).
104
-
105
- `alp init` now writes editable `team.pho` and `team.cafe` choices into
106
- `.alp/settings.json`, taking a snapshot of your current user team configuration.
107
- `alp upgrade` migrates the old `profile` key to `team` and adds these team settings to older projects, backs up the settings it changes,
108
- and converts earlier single-model choices to lists without changing their values.
109
- Every agent (main, lead, peer, reviewer, supervisor, oracle, or a custom member) has
110
- `profiles`: a list of `"provider:model:effort"` (the effort may be left out), or
111
- `"provider:*"` for any model of that provider. Main and oracle name their models, and
112
- main runs on exactly one. Include both providers for dual-lens oracle advice:
113
-
114
- ```json
115
- {
116
- "workflow": { "mode": "cafe", "maxPeers": 2 },
117
- "team": {
118
- "cafe": {
119
- "main": { "profiles": ["claude:claude-opus-5-5:high"], "autocompact": "auto" },
120
- "lead": { "profiles": ["claude:claude-opus-5-5:high", "codex:gpt-6.1-sol:high"] },
121
- "peer": { "profiles": ["claude:*", "codex:*"], "autocompact": 200000 },
122
- "oracle": { "profiles": ["claude:claude-fable-5-1:high", "codex:gpt-6-astra:low"] }
123
- }
124
- }
125
- }
126
- ```
127
-
128
- An agent that runs by itself (main, the supervisor) starts on its first profile, at its
129
- effort or else its team's. When main or lead assigns work to an agent with several
130
- profiles or a `provider:*`, such as a peer, it picks a model they offer and an effort
131
- that model takes for each task, by its difficulty, risk and the autonomy it needs, and
132
- says why; a listed effort is only the usual one. ALP refuses an assignment without both
133
- or with a model they do not offer. An agent with one named profile runs on it. A list
134
- does not enable automatic fallback. Oracle still requires an explicit lens selection.
135
- An effort a model does not take is refused (GPT-6 Astra takes only low). The earlier
136
- form, `{ "provider", "model", "effort" }`, still works; `alp upgrade` rewrites it as
137
- `"provider:model:effort"`.
138
-
139
- `autocompact` belongs to the agent, beside `profiles`, and applies to every model in
140
- its list. A whole number from 100000 to 1000000 requests compaction at that many tokens
141
- on Codex, or sets Claude's native auto-compact window (Claude can compact earlier to
142
- reserve response space). The model's capacity still applies. `"auto"` or omission
143
- keeps the agent's existing native/context defaults. The same field is supported in
144
- `agent.json`; a numeric project setting takes precedence. 200000 here is an example,
145
- not a shipped default. New and upgraded configurations use `"auto"`.
146
-
147
- Unspecified agents keep the team's choices. Explicit session/delegation model and
148
- effort win, then project team choices, then the older `runtime` settings, then
149
- team and agent defaults. Run `alp setup` again to rewrite Phở and Cafe's profiles, or
150
- edit the file to change one agent. Only configured oracle lenses are available, and an
151
- unavailable lens is reported rather than substituted.
152
-
153
- Example `.alp/settings.json`:
154
-
155
- ```json
156
- {
157
- "defaultAgent": "main",
158
- "workflow": { "mode": "pho" },
159
- "runtime": { "provider": "codex", "model": "gpt-5.6-sol", "reasoning": "low" }
160
- }
161
- ```
162
-
163
- `workflow.mode` names a team: `pho`, `cafe` or your own (`smart` and `supervised`, Phở and Cafe's names before 0.4, are still accepted); `workflow.supervisor: false` turns the supervisor off. `runtime.provider` selects `codex` or `claude` behind the ALP Paseo plugin; the Paseo provider ID itself is `alp`. Without `runtime.provider` and `runtime.model`, main runs on `claude:claude-opus-5-5` with `high` effort; setting either replaces that default. `runtime.model` is passed through to the selected native harness, so it accepts any model name supported by that installed Codex or Claude Code version. Core treats these strings as provider-neutral data. An agent's `agent.json` can pick its own provider, model, thinking and mode, and name the skills, MCP servers and hooks it uses ([agent library](docs/alp/agent-library.md)). ALP runs hooks itself at its own events, the same for Codex and Claude; a project's hooks run after you trust the workspace once ([hooks](docs/alp/agent-library.md#hooks)). Besides Codex and Claude, an agent can run on any agent that speaks the Agent Client Protocol, such as Gemini CLI or opencode (experimental in the beta), added to your library with `alp provider add` or in the web app's Providers settings ([ACP providers](docs/alp/agent-library.md#acp-providers)).
164
-
165
- For example, select Claude Code and one of its model aliases in `.alp/settings.json`:
166
-
167
- ```json
168
- {
169
- "defaultAgent": "main",
170
- "workflow": { "mode": "pho", "maxPeers": 2 },
171
- "runtime": { "provider": "claude", "model": "sonnet", "reasoning": "high" }
172
- }
10
+ alp --version
173
11
  ```
174
12
 
175
- The Paseo model picker shows the teams, Phở and Cafe first; a session's model and effort follow from the team and are not chosen in Paseo. `runtime.model` in `.alp/settings.json`, or `--model` and `--thinking` on `alp run`, still override them. Those take runtime-prefixed IDs such as `codex:gpt-6.1-sol` or `claude:claude-opus-5-5`, or any native model name the installed Codex or Claude Code accepts.
176
-
177
- `permissions` in `.alp/settings.json` (and in `$ALP_HOME/settings.json`) gives agents permission profiles with Claude Code-style rules. A profile caps an agent's mode, lets it run commands beyond that mode, asks you before others (`ask`, or `"beyondMode": "ask"` for anything the mode refuses), or refuses them in any mode. You answer Allow once, Always allow (written to the profile) or Deny, in Paseo or with `alp answer`. For example, a read-only reviewer may run the tests:
178
-
179
- ```json
180
- "permissions": {
181
- "profiles": { "review": { "base": "read-only", "allow": ["Bash(npm test:*)"], "deny": ["Bash(rm:*)"] } },
182
- "agents": { "reviewer": "review" }
183
- }
184
- ```
185
-
186
- On Claude, read-only and workspace-write profiles also put Bash in the OS sandbox, so a read-only reviewer can run tests that only read. With `"workdir": "copy"` an agent works in a disposable copy of your tree, where it may build and test without touching yours. `alp permissions` shows each agent's profile, and `alp permissions check <agent> "<command>"` tests a rule. Details: [Permission profiles](docs/alp/team-workflow.md#permission-profiles).
187
-
188
- Use `resolveAgent(projectRoot, { agent: 'your-agent' })` from `src/core/resolver.js` to resolve an agent without starting a runtime. Explicit selection overrides settings; missing settings default to `main`. There is no central agent registry.
189
-
190
- ## Daemon and CLI
191
-
192
- `alpd` hosts ALP sessions for all projects of a user. After `npm run build`:
193
-
194
- ```sh
195
- alp daemon start # or: status [--json] | stop | restart | install | uninstall (install: experimental)
196
- alp doctor [--fix] # what ALP needs here, what earlier runs left behind, and what alpd's log and readiness say; only --fix repairs
197
- alp backup --stop # sessions, settings, library and the projects' tasks in one file; also: verify | list
198
- alp restore FILE --stop # checks the whole backup first, keeps what it replaces; --dry-run changes nothing
199
- alp run --team cafe "Your task" # streams the agent tree; Ctrl-C interrupts
200
- alp ps [--all] # live sessions as a tree; --all adds closed ones
201
- alp top [session] # live dashboard: who runs, who waits, questions, worktrees, leases
202
- alp attach <session> # follow a running tree, or print a closed one
203
- alp send <session> "More context" # resumes a closed root first; to an agent, mail from you
204
- alp questions # questions agents asked you
205
- alp answer <question> "Your answer" # or: --dismiss [--reason R]
206
- alp log <session> # delegations, mail, handoffs, worktrees, pins and questions of a tree
207
- alp board [--project DIR] # the project board: claims, decisions and findings
208
- alp tasks [ready] # the project's tasks; ready: what nothing blocks
209
- alp task add "Title" -p 1 --after t-91c2 # also: show | edit | close | reopen | dep
210
- alp formula pour release --var version=0.4.0 # also: list | show; tasks export | import for beads
211
- alp verify [--task t-77e0] # run the project's verify commands; --task records the result
212
- alp recall <assignment|task> "Why did you…?" # ask a finished assignment about its work
213
- alp pause [codex|claude] [--now] # hold delegation; --now parks running assignments
214
- alp resume [codex|claude] # parked assignments continue
215
- alp interrupt <session>
216
- ```
217
-
218
- You talk with main. Main can ask you a question with `alp_ask` without ending its turn; `run`, `attach` and `send` show it and, in a terminal, read the answer, and `alp answer` answers from anywhere by question id or a unique prefix. In Paseo the question appears as a question prompt. Other agents do not talk to you unless you write to them first with `alp send <agent session>`; ALP then tells the agent that assigned them, and they may ask you questions too.
219
-
220
- Agents on one project share a board, even when they belong to different trees. Each one claims the paths it is about to change, and an overlapping claim from another tree is refused. Agents also pin decisions and findings, which reach other agents that are working and start every new assignment. `alp board` shows the board.
221
-
222
- Each project keeps its tasks in `.alp/tasks`, one JSON file per task, modelled on [beads](https://github.com/gastownhall/beads): types, priorities 0–4, `blockedBy`, epic parents, and a ready list of open tasks that nothing blocks. Only you and main create or change tasks: you with `alp task`, main with its `alp_task` tool. Other agents read them and report work they find to their requester. Main gives a task to lead or peer with `alp_delegate { taskId }`: the task starts, its paths are claimed, and the handoff moves it to review until main accepts it. Each of main's turns starts with what waits for review and what is ready, and Paseo shows the tasks a session worked on as a task list. When every child of an epic is closed, main closes the epic, and you get a report of what it came to: tasks, time, rework and verification. `alp task report <epic>` shows it at any time.
223
-
224
- A gate holds a task back until it clears:
225
- - `human`: you approve it with `alp task gate clear` or in Paseo.
226
- - `timer`: a time passes.
227
- - `gh:pr`: a pull request merges.
228
- - `gh:run`: a workflow run succeeds.
229
-
230
- Formulas are workflow templates, as in beads: a `<name>.formula.toml` (or `.json`) file in `.alp/formulas`, `$ALP_HOME/formulas` or `.beads/formulas` lists steps with `needs` and `{{vars}}`. `alp formula pour release --var version=0.4.0`, or main with `alp_task pour`, turns one into an epic with a task per step; a `human` step waits for your approval. `alp tasks export` and `alp tasks import` move tasks to and from beads' JSONL (`bd export` / `bd import`, default `.beads/issues.jsonl`).
231
-
232
- `alp tasks gates` and main's turns check the GitHub gates. `alp tasks compact` shrinks tasks closed more than 30 days ago. In Paseo, each workspace has a **Tasks** panel: you approve gates, add tasks, and accept or reopen them there. The CLI writes the files directly, so it works without alpd.
233
-
234
- `ALP_HOME` selects the daemon's directory (default `~/.alp`); `ALP_RUN_LOG_DIR` overrides where assignment logs go (default `$ALP_HOME/runs`). alpd records sessions, their timelines, and prompt receipts under `$ALP_HOME/state`, project boards under `$ALP_HOME/boards`, and its own location in `$ALP_HOME/alpd.json`. After a restart, or a crash, a root can be resumed with `send` or imported into Paseo; work that was running is marked `daemon_restarted`. A task an assignment held goes back to open at main's next turn, with the branch that kept the assignment's work, and main's task list shows it as interrupted.
235
-
236
- With `"verify": { "test": "npm test" }` (also `setup`, `typecheck`) in `.alp/settings.json`, alpd runs the project's checks itself:
237
- - before `alp_merge` applies a worktree change; if they fail, nothing is applied and main can fix the change with `alp_delegate { continueFrom }`;
238
- - in the checkout after a shared writer finishes;
239
- - on demand with `alp_verify` or `alp verify`.
240
-
241
- The result is recorded on the task, and an agent cannot close a task as done after a failed check without saying why. A timed-out command gets SIGTERM before SIGKILL, `idleSec` stops a silent one, and a step exiting 75 counts as could-not-run (skipped), not failed. Details: [Verification gates](docs/alp/team-workflow.md#verification-gates).
242
-
243
- `alp_delegate { continueFrom: assignmentId }` also continues a finished Codex or Claude shared assignment while its record is retained (14 days). It creates a new assignment in the same checkout, carrying the previous brief and handoff, and preserves an external `writeRoot`. It does not reopen the old conversation or restore a file snapshot. Running or parked assignments must finish or be canceled first. Older records without this context can be continued by delegating again with their handoff; shared changes need no merge or discard.
244
-
245
- When recovering an assignment saved by an older ALP version, an effort no longer accepted by the current model catalog falls back to that model's default. ALP tells the requester and records the change in the run log. Newly requested unsupported efforts are still rejected.
246
-
247
- Text one agent wrote reaches another without `<system-reminder>` tags, so a peer cannot pose as the harness.
248
-
249
- Work survives alpd: after a crash, `alp daemon stop` or `alp daemon restart`, the next alpd reopens trees that were working, by itself, and their assignments continue with their thread, worktree and task. When a Codex or Claude process dies under a session, ALP restarts it and the session continues; three restarts in a row with no progress end it. `alp daemon install` (experimental in the beta) runs alpd as a login service (launchd on macOS, systemd on Linux) that starts it again after a crash. Details: [Crashes and restarts](docs/alp/team-workflow.md#crashes-and-restarts).
250
-
251
- `alp backup` writes your sessions, timelines, receipts, settings, library and the projects' tasks to one checked file, and `alp restore` puts them back only after checking all of it, keeping what it replaced so the restore can be undone; an interrupted restore is rolled back. alpd migrates an older state when an upgrade needs it, after saving a backup, and an older ALP refuses state a newer one wrote instead of changing it. Session trees unused for 30 days are deleted, but never while one of them is live or a prompt receipt of theirs (7 days) is still honoured. Codex and Claude keep their own threads, which a backup does not hold. Details: [Backups, restores, upgrades and recovery](docs/alp/data-recovery.md).
252
-
253
- When a runtime hits its usage limit, ALP pauses it by itself. Assignments the limit stopped are parked instead of failed, and every open session shows a notice saying when the limit resets. Agents on the other runtime keep working. alpd asks the paused runtime every minute, and at once when an agent delegates to it. It resumes the runtime as soon as the runtime says the limit lifted, and the parked assignments continue. With `"limits": { "autoResume": false }` in `$ALP_HOME/settings.json`, ALP only tells you, and `alp resume codex` continues. Details: [Pause and usage limits](docs/alp/team-workflow.md#pause-and-usage-limits).
254
-
255
- Agents and ALP write to you in the language set in Settings → ALP → General → Language, or with `alp language English`; unset, it is Vietnamese. That covers replies, questions, approval requests and notices. Details: [The user's language](docs/alp/team-workflow.md#the-users-language).
256
-
257
- Finished assignments stay recallable for 14 days: main, or the agent that assigned one, asks it with `alp_recall` why it did something, and you ask with `alp recall <assignment|task> "question"`. ALP forks the assignment's session read-only, asks, and drops the fork; the question never changes the assignment's own session. ALP keeps the native threads of assignments for this, and deletes them after 14 days. The Paseo plugin keeps the same daemon running while Paseo runs: it starts alpd with Paseo and again whenever alpd goes down, except after `alp daemon stop`, and its sessions reconnect.
258
-
259
- ## The web app
260
-
261
- The optional `@anhlp/alp-paseo` package serves the ALP web app as a separate process, on this machine only (loopback). The core `@anhlp/alp` distribution includes no Paseo SDK or web app; ALP 0.10.0 and earlier bundled it. This is not the standalone app in `apps/web`, which is deferred and not published. Install the plugin once to keep using the existing web commands:
262
-
263
- ```sh
264
- npm install -g @anhlp/alp-paseo@beta
265
- alp-web-integration --register # register in the selected ALP_HOME (also discoverable on PATH)
266
- alp web # opens http://127.0.0.1:7433 with its access token
267
- alp web --print # prints the address instead
268
- ```
269
-
270
- The app is [Paseo](https://github.com/getpaseo/paseo)'s web app (Apache 2.0), built from a pinned commit with ALP's patches (`paseo-web/`) and run over alpd, so it looks and works like Paseo:
271
-
272
- - **Sidebar:** your projects and their workspaces, with their branch, pins, labels and unread marks. Add a project by typing its folder or browsing to it (Add project → Browse folders); a new workspace starts with its first agent.
273
- - **Agents:** each session is an agent of the provider ALP, its team (Phở, Cafe, …) as the model. The timeline streams main's replies and tool calls; questions from the team are cards to answer; History and Find in chat search past sessions. The team members are the agent's subagents, each with its own timeline.
274
- - **ALP's own parts:** the Tasks panel and screen, the ALP project panel and the ALP settings screen (Settings → Plugins → alp-provider) come from ALP's Paseo plugin, which alpd runs.
275
- - **Changes and Files:** the checkout's diff, uncommitted or against its base branch, and its files, read-only.
276
- - **Usage:** the sidebar Usage button and Settings → Usage show Codex and Claude account limits, plan names and reset windows. ALP's Usage workspace panel and plugin settings also show paused runtimes and parked assignment counts. Reads are cached for 30 seconds; Refresh requests fresh data. Missing provider data is shown as unavailable. These are account-wide provider limits, not ALP token costs.
277
- - What Paseo has and ALP not yet (terminals, committing and pushing, worktrees from the app, voice, …) says "Tính năng đang phát triển" (in your language) when used.
278
-
279
- The web app listens on 127.0.0.1 only. The page needs the token `alp web` puts in its address (kept in `~/.alp/web.json`), and the integration refuses requests for another host name and sockets from another site. `"web": { "port": 8000 }` in `~/.alp/settings.json` moves it, and `"web": { "enabled": false }` turns it off. In a checkout of this repository, build the app with `npm run build:web` (it clones Paseo into `.cache/paseo-web`). Details: [ALPD §62](plans/reference/ALPD.md#62-the-alp-web-app-on-paseos-app-2026-10-10).
280
-
281
- ### Paseo's app on your phone (experimental)
282
-
283
- Relay pairing is experimental in the beta and off by default. The integration can also reach alpd from [Paseo](https://paseo.sh)'s own app on another device, through Paseo's relay (`relay.paseo.sh`):
284
-
285
- ```sh
286
- alp web pair --on # turns the relay on and prints the pairing link
287
- alp web pair # the link again; --off turns the relay off
288
- ```
289
-
290
- Or in the web app: Settings → your host → Pair device → Enable relay, then scan the QR code with Paseo's app. Traffic between the device and alpd is end-to-end encrypted; the relay only passes it on. The link carries alpd's public key, not its token: when Paseo's app asks for a password, give it the token (the part after `#token=` in `alp web --print`). The relay stays off until you turn it on; `"web": { "relay": { "endpoint": "host:port" } }` points the integration at another relay. Paseo's app shows ALP's sessions and teams, and answers what ALP has not built yet as an error. Details: [ALPD §64](plans/reference/ALPD.md#64-paseos-relay-and-its-app-on-another-device-2026-10-12).
291
-
292
- ## Antigravity CLI (experimental)
293
-
294
- The Antigravity and ACP providers (OpenCode, Gemini CLI) are experimental in the beta. With `agy` installed and signed in:
295
-
296
- ```sh
297
- alp provider test antigravity
298
- alp run --model antigravity:gemini-3.8-flash-high --mode full-access "Describe this project"
299
- ```
300
-
301
- A main profile can select `antigravity:gemini-3.8-flash-high` too. By default this adapter supports full access only. To run oracle, reviewer and supervisor on Antigravity, explicitly enable instruction-based read-only behavior:
302
-
303
- ```sh
304
- alp provider edit antigravity --read-only-mode instructions
305
- ```
306
-
307
- This stores `"readOnlyMode": "instructions"` in your library's provider configuration. ALP keeps advisory sessions read-only for its own tools and tells the agent to avoid changing files or external state, while the native agy process still runs with full access. This is a prompt constraint, not an OS sandbox or filesystem protection. It applies to new sessions; ALP does not downgrade an existing full-access AGY process to read-only. Use `--read-only-mode reject` to restore the default rejection. Workspace-write, review copies and custom permission rules remain unsupported.
308
-
309
- MCP tools supplied by the project or editor are connected separately for each AGY session over stdio or streamable HTTP, then exposed through ALP's MCP bridge. The tool aliases and descriptions identify their original server. Commands, environment variables and HTTP authorization headers stay out of agy's global configuration. MCP resources and prompts are not forwarded through this tool bridge; configure a server directly in agy if you need those capabilities.
310
-
311
- OpenCode (`opencode acp`) and Antigravity (`agy`) are added to your library by default, including on upgrades. Settings → Providers shows a switch at the end of each row to enable or disable them. Install and sign in to the corresponding CLI to use it; an absent CLI is shown as not installed. Existing commands, model settings and disabled switches are preserved across upgrades. OpenCode models use `acp:opencode/<model>`. To change a default command, use `alp provider edit opencode --command /path/to/opencode --arg acp` or `alp provider edit antigravity --command /path/to/agy`.
312
-
313
- ALP installs one `alp` MCP launcher in `~/.gemini/config/mcp_config.json`, preserving other entries. Each agy process inherits its own socket and token; neither is saved in that file. The launcher remains available across sessions and exposes no ALP tools outside an ALP process. Invalid configuration and a conflicting user-owned `alp` entry cause an error without overwriting the file.
314
-
315
- ## Editors (ACP, experimental)
316
-
317
- `alp acp` is experimental in the beta. It serves ALP over the [Agent Client Protocol](https://agentclientprotocol.com) on stdio, so an editor such as Zed or a JetBrains IDE can use ALP as its agent. In Zed's `settings.json`:
318
-
319
- ```json
320
- {
321
- "agent_servers": {
322
- "ALP": { "type": "custom", "command": "alp", "args": ["acp"], "env": {} }
323
- }
324
- }
325
- ```
326
-
327
- - Each editor thread is an ALP session in its project, worked by a team. Choose the team (Phở, Cafe, or your own) and the permission mode in the thread's options before the first prompt; the team is fixed after that.
328
- - Main's replies, commands and tool calls stream into the thread, and so do the steps its team reports back after the prompt ended. Delegations show as tool calls.
329
- - When an agent asks you something, the question appears in the thread: reply with `/answer <text>`, or `/dismiss`.
330
- - Stop cancels main's turn and the work it started. Closing the editor leaves running work to finish in alpd; the thread's history lists ALP sessions of the project, and opening one shows its history.
331
- - MCP servers the editor offers are given to the agents beside their own.
332
-
333
- Details: [ALPD §60](plans/reference/ALPD.md#60-alp-as-an-acp-agent-2026-10-10).
334
-
335
- ## Paseo
336
-
337
- To use the published plugin, follow [the plugin's README](integrations/paseo/README.md) or [beta-install.md](docs/alp/beta-install.md#paseo-plugin): `paseo plugin add npm:@anhlp/alp-paseo@beta` (from `0.0.0-beta.2`; in beta.1 the `npm:` source fails, [#111](https://github.com/phucanh08/alp/issues/111), so there run `npm install -g @anhlp/alp-paseo@beta`, then `paseo plugin add "$(npm root -g)/@anhlp/alp-paseo"`). The plugin supports Paseo `>=0.11.1 <0.13.0` (`0.0.0-beta.2` and earlier: `<0.12.0`, so Paseo 0.12 needs `0.0.1-beta.3` or later). See [installation and runtime behavior](docs/alp/paseo-plugin.md) and the historical [phase acceptance evidence](docs/alp/phase-2-5-results.md). The plugin was verified with Paseo 0.11.1 and live Codex and Claude Code permission changes before the beta; the beta's own runs are in [beta-scope.md](docs/alp/beta-scope.md) §6.
338
-
339
- **Settings → ALP** manages your library of teams, agents, skills, MCP servers, hooks and ACP providers, and the **ALP project** workspace panel manages a project's overrides ([settings screen](docs/alp/paseo-plugin.md#alp-settings-and-the-project-panel)).
340
-
341
- ### Build and add the plugin to Paseo
342
-
343
- This section builds the plugin from a checkout of this repository. Prerequisites: Node.js 22 (22.12 or later) or 24, Paseo 0.11.1 or later, up to 0.12.x (the Tasks panel needs the 0.11.1 plugin client API), and a logged-in Codex CLI and/or Claude Code installation. Set `ALP_CODEX_BIN` or `ALP_CLAUDE_BIN` to an absolute native executable path when it is not available on `PATH`.
344
-
345
- Install dependencies, type-check the plugin, and build its server bundle:
346
-
347
- ```sh
348
- npm ci
349
- npm run check
350
- npm run build:paseo
351
- ```
352
-
353
- The build output is written to `integrations/paseo/server/dist/index.js`. Paseo plugins are trusted, unsandboxed code, so enable plugins only on a daemon where you trust this repository. In Paseo, open **Settings → Plugins** and turn on **Enable plugins**.
354
-
355
- For a headless daemon, run `paseo daemon status --json` to find its `home`, set the root `pluginsEnabled` field in `<home>/config.json` to `true`, preserve the rest of the file, then apply the change:
356
-
357
- ```sh
358
- paseo reload --json
359
- ```
360
-
361
- Paseo requires an absolute path when adding a local plugin. From the repository root, run one of the following commands.
362
-
363
- PowerShell:
364
-
365
- ```powershell
366
- paseo plugin add (Resolve-Path .\integrations\paseo).Path
367
- ```
368
-
369
- macOS/Linux:
370
-
371
- ```sh
372
- paseo plugin add "$(pwd)/integrations/paseo"
373
- ```
374
-
375
- Confirm that `alp-provider` is running and that the `alp` provider is available:
376
-
377
- ```sh
378
- paseo plugin ls
379
- paseo provider models alp
380
- ```
381
-
382
- After changing the plugin source, rebuild and reload the installed plugin:
383
-
384
- ```sh
385
- npm run check
386
- npm run build:paseo
387
- paseo plugin reload alp-provider
388
- ```
389
-
390
- If loading fails, inspect the plugin process output with `paseo plugin logs alp-provider`. A full daemon restart is not required for source changes.
391
-
392
- ### Phở and Cafe
393
-
394
- New projects default to Phở: main works directly or delegates to peer. Cafe
395
- keeps main as supervisor while lead implements or delegates. In both, main runs on
396
- Opus 5.5 with high effort and full access, and starts a supervisor on Sonnet 5
397
- that reviews its process after each turn and asks main about mistakes; main records
398
- lessons it follows in later sessions. Main can distill recurring lessons into a
399
- skill for the roles it chooses, and open or comment on GitHub issues of the
400
- project or of ALP; the user approves each skill and each post first. Both use read-only oracle/reviewer. Phở and Cafe
401
- are ALP's built-in teams; you can make your own, with its own main, members,
402
- delegation and house rules ([teams](docs/alp/agent-library.md#teams)). Select the
403
- team in the Paseo model picker, with `alp run --team`, or in
404
- `.alp/settings.json` → `workflow.mode`; `alp teams` lists them. The default concurrent peer limit is 2;
405
- raise `workflow.maxPeers` only at the user's request. Writers in the shared checkout
406
- run one at a time, across all sessions; writing peers can run in parallel in their own
407
- git worktrees (`isolation: "worktree"`) and are merged with `alp_merge`. Oracle runs
408
- on Fable (`claude:claude-fable-5-1`) or Astra (`codex:gpt-6-astra`); main may ask
409
- both in parallel for two opinions. Runtime catalog and available plan/usage snapshots
410
- inform coordination. See [team configuration and migration](docs/alp/team-workflow.md).
411
-
412
- ## Modular monorepo development
413
-
414
- ALP is organized into npm workspaces for protocol, core, runtime, daemon, client, CLI, a reserved TUI, and the Paseo integration. See [package boundaries, build/test/release commands and the Rust migration seam](docs/modular-monorepo.md). Existing commands and checkout entry points remain compatible.
13
+ If `alp` says the executable for this machine is not installed, npm skipped optional
14
+ dependencies (`--omit=optional`): reinstall with `--include=optional`, or install the executable
15
+ directly from [GitHub Releases](https://github.com/phucanh08/alp/releases) with `install.sh`
16
+ (`install.ps1` on Windows). Update an npm install with npm (`npm install -g @anhlp/alp@beta`);
17
+ `alp update` updates only an executable installed from GitHub Releases.
415
18
 
416
- ## License
19
+ The libraries (`@anhlp/alp-core`, `-protocol`, `-runtime`, `-client`, `-daemon`, `-cli`) and the
20
+ Paseo plugin (`@anhlp/alp-paseo`) stay JavaScript packages.
417
21
 
418
- ALP is licensed under the [Apache License 2.0](LICENSE); see [NOTICE](NOTICE). Packages that bundle third-party code ship a `THIRD-PARTY-NOTICES.txt`. The `alp web` app in `@anhlp/alp-paseo` is Paseo's web app (Apache-2.0, © Mohamed Boudra) with ALP's patches; see that package's `NOTICE`. The web app's third-party licences are in `server/dist/web-app/THIRD-PARTY-NOTICES.txt`.
22
+ Apache-2.0; see LICENSE and NOTICE.
package/bin/alp.js ADDED
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env node
2
+ // @anhlp/alp's `alp`: runs the ALP executable from @anhlp/alp-<os>-<arch> (lib/launcher.js).
3
+ import { main } from '../lib/launcher.js';
4
+
5
+ await main();
@@ -0,0 +1,136 @@
1
+ // The `alp` command of the npm package @anhlp/alp (t-cd7f): a launcher, not ALP itself. npm installs
2
+ // the ALP executable for this machine as one optional dependency, @anhlp/alp-<os>-<arch>; this file
3
+ // finds it and runs it with the same arguments, stdio, exit code and signal. Plain Node, no
4
+ // dependencies, no postinstall download.
5
+ import { spawn } from 'node:child_process';
6
+ import { existsSync } from 'node:fs';
7
+ import { createRequire } from 'node:module';
8
+ import path from 'node:path';
9
+
10
+ export const RELEASES_URL = 'https://github.com/phucanh08/alp/releases';
11
+ export const INSTALL_SH = 'curl -fsSL https://github.com/phucanh08/alp/releases/latest/download/install.sh | sh';
12
+
13
+ /** The platform packages, keyed by `${process.platform}-${process.arch}`. Linux builds are glibc only. */
14
+ export const PLATFORM_PACKAGES = Object.freeze({
15
+ 'darwin-arm64': '@anhlp/alp-darwin-arm64',
16
+ 'darwin-x64': '@anhlp/alp-darwin-x64',
17
+ 'linux-x64': '@anhlp/alp-linux-x64',
18
+ 'linux-arm64': '@anhlp/alp-linux-arm64',
19
+ 'win32-x64': '@anhlp/alp-win32-x64',
20
+ });
21
+
22
+ /** Whether this Linux runs glibc (Node reports its runtime glibc version; musl reports none). */
23
+ export function isGlibc(report = () => process.report?.getReport?.()) {
24
+ try { return Boolean(/** @type {any} */ (report())?.header?.glibcVersionRuntime); } catch { return false; }
25
+ }
26
+
27
+ /**
28
+ * The platform package for a machine, or why there is none.
29
+ * @param {{ platform?: string, arch?: string, glibc?: () => boolean }} [machine]
30
+ * @returns {{ name: string, executable: string } | { error: string }}
31
+ */
32
+ export function platformPackage({ platform = process.platform, arch = process.arch, glibc = isGlibc } = {}) {
33
+ const key = `${platform}-${arch}`;
34
+ const name = PLATFORM_PACKAGES[key];
35
+ if (!name) return { error: `ALP has no build for ${key}; supported: ${Object.keys(PLATFORM_PACKAGES).join(', ')}.` };
36
+ if (platform === 'linux' && !glibc()) return { error: `ALP's Linux builds need glibc; this ${key} system uses another C library (musl, as on Alpine).` };
37
+ return { name, executable: platform === 'win32' ? 'alp.exe' : 'alp' };
38
+ }
39
+
40
+ /**
41
+ * The ALP executable installed beside this package, or an error message for the user.
42
+ * @param {{ platform?: string, arch?: string, glibc?: () => boolean, resolve?: (request: string) => string, exists?: (file: string) => boolean }} [options]
43
+ * @returns {{ binary: string, name: string } | { error: string }}
44
+ */
45
+ export function findBinary({ resolve = createRequire(import.meta.url).resolve, exists = existsSync, ...machine } = {}) {
46
+ const wanted = platformPackage(machine);
47
+ // No executable exists for this machine: only the JavaScript CLI can run here.
48
+ if ('error' in wanted) return { error: `alp: ${wanted.error}\nRun the JavaScript CLI instead (Node 22.12 or later; it also provides \`alp\`): npm uninstall -g @anhlp/alp && npm install -g @anhlp/alp-cli@beta\n(${RELEASES_URL})` };
49
+ let manifest;
50
+ try { manifest = resolve(`${wanted.name}/package.json`); } catch {
51
+ return { error: missingMessage(`${wanted.name}, the ALP executable for this machine, is not installed (npm skips optional dependencies with --omit=optional or --no-optional).`) };
52
+ }
53
+ const binary = path.join(path.dirname(manifest), 'bin', wanted.executable);
54
+ if (!exists(binary)) return { error: missingMessage(`${wanted.name} is installed but has no ${path.join('bin', wanted.executable)}; reinstall it.`) };
55
+ return { binary, name: wanted.name };
56
+ }
57
+
58
+ /** @param {string} reason */
59
+ export function missingMessage(reason) {
60
+ return [
61
+ `alp: ${reason}`,
62
+ 'Reinstall with optional dependencies: npm install -g @anhlp/alp@beta --include=optional',
63
+ `Or install the ALP executable directly: ${INSTALL_SH}`,
64
+ `(Windows: install.ps1; every build: ${RELEASES_URL})`,
65
+ ].join('\n');
66
+ }
67
+
68
+ /** Whether a terminal is attached (any of stdin, stdout, stderr): its keyboard signals reach the whole foreground group. */
69
+ export const onTerminal = (proc = process) => Boolean(proc.stdin?.isTTY || proc.stdout?.isTTY || proc.stderr?.isTTY);
70
+
71
+ /** Signals the launcher outlives so the executable decides what they mean. */
72
+ const HANDLED = ['SIGINT', 'SIGTERM', 'SIGHUP', 'SIGQUIT', 'SIGBREAK', 'SIGUSR1', 'SIGUSR2'];
73
+ /** The terminal's keyboard signals: a terminal sends them to the whole foreground process group, the executable included. */
74
+ const KEYBOARD = new Set(['SIGINT', 'SIGQUIT', 'SIGBREAK']);
75
+
76
+ /**
77
+ * Whether a signal the launcher received must be passed on. Signals sent to the launcher alone
78
+ * (kill <pid>, a supervisor) are. The terminal's Ctrl-C/Ctrl-\ already reached the executable, so
79
+ * passing it on would deliver it twice. On Windows the console delivers Ctrl-C/Ctrl-Break to every
80
+ * process attached to it, and kill() there only terminates, so nothing is passed on.
81
+ */
82
+ export function forwards(signal, { platform = process.platform, tty = onTerminal() } = {}) {
83
+ if (platform === 'win32') return false;
84
+ return !(KEYBOARD.has(signal) && tty);
85
+ }
86
+
87
+ /**
88
+ * Runs `binary` with `args` and inherited stdio. Resolves with how it ended: `{ code }` or `{ signal }`.
89
+ * The launcher stays alive on the signals it handles and passes them on as `forwards` says; the
90
+ * executable decides what they mean, and the launcher only reports how it ended.
91
+ * @param {string} binary @param {string[]} args
92
+ * @param {{ spawn?: typeof spawn, process?: NodeJS.Process, platform?: string, tty?: boolean }} [options]
93
+ * @returns {Promise<{ code: number } | { signal: NodeJS.Signals }>}
94
+ */
95
+ export function run(binary, args, { spawn: start = spawn, process: proc = process, platform = process.platform, tty = onTerminal() } = {}) {
96
+ return new Promise(resolve => {
97
+ const child = start(binary, args, { stdio: 'inherit', windowsHide: false });
98
+ let started = false;
99
+ child.once('spawn', () => { started = true; });
100
+ const handlers = new Map();
101
+ const release = () => { for (const [signal, handler] of handlers) proc.removeListener(signal, handler); };
102
+ for (const signal of HANDLED) {
103
+ const handler = () => { if (forwards(signal, { platform, tty })) try { child.kill(signal); } catch {} };
104
+ try { proc.on(signal, handler); handlers.set(signal, handler); } catch {}
105
+ }
106
+ child.on('error', error => {
107
+ // After the executable started, an error (a failed kill) changes nothing: its exit decides.
108
+ if (started) return;
109
+ release();
110
+ proc.stderr.write(`alp: could not run ${binary}: ${error.message}\n${missingMessage('the ALP executable could not start.')}\n`);
111
+ resolve({ code: 1 });
112
+ });
113
+ child.once('exit', (code, signal) => {
114
+ release();
115
+ resolve(signal ? { signal } : { code: code ?? 1 });
116
+ });
117
+ });
118
+ }
119
+
120
+ /** The launcher's main: find the executable, run it, end as it ended. */
121
+ export async function main(args = process.argv.slice(2)) {
122
+ const found = findBinary();
123
+ if ('error' in found) {
124
+ process.stderr.write(`${found.error}\n`);
125
+ process.exitCode = 1;
126
+ return;
127
+ }
128
+ const end = await run(found.binary, args);
129
+ if ('signal' in end) {
130
+ // End by the same signal, so a shell sees what the child saw; exit 128+n if it does not kill us.
131
+ try { process.kill(process.pid, end.signal); } catch {}
132
+ const number = (await import('node:os')).constants.signals[end.signal] ?? 0;
133
+ process.exit(128 + number);
134
+ }
135
+ process.exit(end.code);
136
+ }