@hybridlabor-api/aos 4.7.2 → 4.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.agents/agents.md +1 -1
- package/.agents/nodes.json +1 -1
- package/.agents/vendor-manifest.json +23 -1
- package/.claude/agents/godmode-media-eventtech.md +1 -1
- package/.claude/hooks/conventional-commits.mjs +125 -0
- package/.claude/hooks/env-file-protection.mjs +105 -0
- package/.claude/hooks/go-gate.mjs +101 -81
- package/.claude/settings.json +13 -0
- package/.opencode/agents/godmode-media-eventtech.md +1 -1
- package/.opencode/plugins/bdb-aos.js +31 -4
- package/README.de.md +1 -1
- package/README.md +1 -1
- package/README.pt.md +1 -1
- package/THIRD_PARTY_NOTICES.md +88 -0
- package/docs/skills_table.md +1 -1
- package/installer.js +309 -101
- package/package.json +6 -2
- package/packages/aos-cli/README.md +80 -0
- package/packages/aos-cli/bin/aos-cli.mjs +134 -0
- package/packages/aos-cli/core-skills.json +12 -0
- package/packages/aos-cli/extensions/aos.ts +321 -0
- package/packages/aos-cli/package-lock.json +1923 -0
- package/packages/aos-cli/package.json +29 -0
- package/packages/aos-cli/scripts/check-theme.mjs +63 -0
- package/packages/aos-cli/themes/aos.json +97 -0
- package/scripts/build-plugin-manifest.mjs +131 -0
- package/scripts/validate-skills.mjs +81 -6
- package/skills/basic/ao-orchestrator/SKILL.md +116 -0
- package/skills/global_config/agenttrail/SKILL.md +6 -1
- package/skills/global_config/ask-tim/SKILL.md +4 -4
- package/skills/global_config/bash-script-generator/SKILL.md +201 -0
- package/skills/global_config/bash-script-generator/assets/templates/standard-template.sh +96 -0
- package/skills/global_config/bash-script-generator/docs/bash-scripting-guide.md +729 -0
- package/skills/global_config/bash-script-generator/docs/generation-best-practices.md +193 -0
- package/skills/global_config/bash-script-generator/docs/script-patterns.md +566 -0
- package/skills/global_config/bash-script-generator/docs/text-processing-guide.md +437 -0
- package/skills/global_config/bash-script-generator/examples/log-analyzer.sh +92 -0
- package/skills/global_config/bash-script-generator/scripts/generate_script_template.sh +123 -0
- package/skills/global_config/bash-script-generator/scripts/run_ci_checks.sh +172 -0
- package/skills/global_config/bash-script-generator/scripts/test_generator.sh +413 -0
- package/skills/global_config/bash-script-validator/SKILL.md +249 -0
- package/skills/global_config/bash-script-validator/docs/awk-reference.md +449 -0
- package/skills/global_config/bash-script-validator/docs/bash-reference.md +468 -0
- package/skills/global_config/bash-script-validator/docs/common-mistakes.md +623 -0
- package/skills/global_config/bash-script-validator/docs/grep-reference.md +395 -0
- package/skills/global_config/bash-script-validator/docs/regex-reference.md +391 -0
- package/skills/global_config/bash-script-validator/docs/sed-reference.md +454 -0
- package/skills/global_config/bash-script-validator/docs/shell-reference.md +463 -0
- package/skills/global_config/bash-script-validator/docs/shellcheck-reference.md +399 -0
- package/skills/global_config/bash-script-validator/examples/bad-bash.sh +55 -0
- package/skills/global_config/bash-script-validator/examples/bad-shell.sh +54 -0
- package/skills/global_config/bash-script-validator/examples/good-bash.sh +71 -0
- package/skills/global_config/bash-script-validator/examples/good-shell.sh +69 -0
- package/skills/global_config/bash-script-validator/scripts/run_ci_checks.sh +23 -0
- package/skills/global_config/bash-script-validator/scripts/shellcheck_wrapper.sh +174 -0
- package/skills/global_config/bash-script-validator/scripts/test_validate.sh +446 -0
- package/skills/global_config/bash-script-validator/scripts/validate.sh +512 -0
- package/skills/global_config/ci-pipeline/SKILL.md +135 -0
- package/skills/global_config/dispatching-parallel-agents/SKILL.md +170 -0
- package/skills/global_config/dockerfile-generator/SKILL.md +1038 -0
- package/skills/global_config/dockerfile-generator/examples/example.dockerignore +95 -0
- package/skills/global_config/dockerfile-generator/examples/golang-distroless.Dockerfile +34 -0
- package/skills/global_config/dockerfile-generator/examples/java-springboot.Dockerfile +45 -0
- package/skills/global_config/dockerfile-generator/examples/nextjs-production.Dockerfile +49 -0
- package/skills/global_config/dockerfile-generator/examples/nodejs-multistage.Dockerfile +55 -0
- package/skills/global_config/dockerfile-generator/examples/python-fastapi.Dockerfile +48 -0
- package/skills/global_config/dockerfile-generator/references/language_specific_guides.md +510 -0
- package/skills/global_config/dockerfile-generator/references/multistage_builds.md +570 -0
- package/skills/global_config/dockerfile-generator/references/optimization_patterns.md +492 -0
- package/skills/global_config/dockerfile-generator/references/security_best_practices.md +375 -0
- package/skills/global_config/dockerfile-generator/scripts/generate_dockerignore.sh +199 -0
- package/skills/global_config/dockerfile-generator/scripts/generate_golang.sh +172 -0
- package/skills/global_config/dockerfile-generator/scripts/generate_java.sh +185 -0
- package/skills/global_config/dockerfile-generator/scripts/generate_nodejs.sh +279 -0
- package/skills/global_config/dockerfile-generator/scripts/generate_python.sh +218 -0
- package/skills/global_config/dockerfile-generator/scripts/test_generator.sh +210 -0
- package/skills/global_config/dockerfile-validator/SKILL.md +300 -0
- package/skills/global_config/dockerfile-validator/examples/.dockerignore.example +34 -0
- package/skills/global_config/dockerfile-validator/examples/bad-example.Dockerfile +37 -0
- package/skills/global_config/dockerfile-validator/examples/golang-distroless.Dockerfile +50 -0
- package/skills/global_config/dockerfile-validator/examples/good-example.Dockerfile +52 -0
- package/skills/global_config/dockerfile-validator/examples/python-optimized.Dockerfile +53 -0
- package/skills/global_config/dockerfile-validator/examples/security-issues.Dockerfile +42 -0
- package/skills/global_config/dockerfile-validator/references/docker_best_practices.md +348 -0
- package/skills/global_config/dockerfile-validator/references/optimization_guide.md +473 -0
- package/skills/global_config/dockerfile-validator/references/security_checklist.md +208 -0
- package/skills/global_config/dockerfile-validator/scripts/dockerfile-validate.sh +699 -0
- package/skills/global_config/dockerfile-validator/scripts/test_validate.sh +35 -0
- package/skills/global_config/dockerfile-validator/tests/fixtures/copy-before-yarn-lock-read.Dockerfile +6 -0
- package/skills/global_config/dockerfile-validator/tests/fixtures/copy-before-yarn.Dockerfile +6 -0
- package/skills/global_config/dockerfile-validator/tests/fixtures/from-platform-nonroot.Dockerfile +7 -0
- package/skills/global_config/dockerfile-validator/tests/test_regression.sh +164 -0
- package/skills/global_config/finishing-a-development-branch/SKILL.md +228 -0
- package/skills/global_config/github-actions-generator/SKILL.md +353 -0
- package/skills/global_config/github-actions-generator/assets/templates/action/composite/action.yml +82 -0
- package/skills/global_config/github-actions-generator/assets/templates/action/docker/Dockerfile +25 -0
- package/skills/global_config/github-actions-generator/assets/templates/action/docker/action.yml +42 -0
- package/skills/global_config/github-actions-generator/assets/templates/action/docker/entrypoint.sh +27 -0
- package/skills/global_config/github-actions-generator/assets/templates/action/javascript/action.yml +33 -0
- package/skills/global_config/github-actions-generator/assets/templates/action/javascript/index.js +50 -0
- package/skills/global_config/github-actions-generator/assets/templates/action/javascript/package.json +27 -0
- package/skills/global_config/github-actions-generator/assets/templates/workflow/basic_workflow.yml +242 -0
- package/skills/global_config/github-actions-generator/assets/templates/workflow/reusable_workflow.yml +106 -0
- package/skills/global_config/github-actions-generator/examples/README.md +147 -0
- package/skills/global_config/github-actions-generator/examples/actions/setup-node-cached/action.yml +93 -0
- package/skills/global_config/github-actions-generator/examples/caching/docker-buildkit.yml +256 -0
- package/skills/global_config/github-actions-generator/examples/security/dependency-review.yml +62 -0
- package/skills/global_config/github-actions-generator/examples/security/sbom-attestation.yml +119 -0
- package/skills/global_config/github-actions-generator/examples/triggers/chatops-commands.yml +475 -0
- package/skills/global_config/github-actions-generator/examples/triggers/repository-dispatch.yml +418 -0
- package/skills/global_config/github-actions-generator/examples/triggers/workflow-orchestration.yml +404 -0
- package/skills/global_config/github-actions-generator/examples/workflows/docker-build-push.yml +68 -0
- package/skills/global_config/github-actions-generator/examples/workflows/go-ci.yml +161 -0
- package/skills/global_config/github-actions-generator/examples/workflows/monorepo-ci.yml +340 -0
- package/skills/global_config/github-actions-generator/examples/workflows/multi-environment-deploy.yml +406 -0
- package/skills/global_config/github-actions-generator/examples/workflows/nodejs-ci.yml +122 -0
- package/skills/global_config/github-actions-generator/examples/workflows/python-ci.yml +157 -0
- package/skills/global_config/github-actions-generator/examples/workflows/scheduled-tasks.yml +376 -0
- package/skills/global_config/github-actions-generator/references/advanced-triggers.md +917 -0
- package/skills/global_config/github-actions-generator/references/best-practices.md +755 -0
- package/skills/global_config/github-actions-generator/references/common-actions.md +715 -0
- package/skills/global_config/github-actions-generator/references/custom-actions.md +320 -0
- package/skills/global_config/github-actions-generator/references/expressions-and-contexts.md +688 -0
- package/skills/global_config/github-actions-generator/references/modern-features.md +421 -0
- package/skills/global_config/github-actions-generator/scripts/test_generator.sh +344 -0
- package/skills/global_config/github-actions-templates/SKILL.md +7 -0
- package/skills/global_config/github-actions-validator/SKILL.md +576 -0
- package/skills/global_config/github-actions-validator/examples/README.md +88 -0
- package/skills/global_config/github-actions-validator/examples/outdated-versions.yml +76 -0
- package/skills/global_config/github-actions-validator/examples/valid-ci.yml +79 -0
- package/skills/global_config/github-actions-validator/examples/with-errors.yml +47 -0
- package/skills/global_config/github-actions-validator/references/act_usage.md +233 -0
- package/skills/global_config/github-actions-validator/references/action_versions.md +122 -0
- package/skills/global_config/github-actions-validator/references/actionlint_usage.md +343 -0
- package/skills/global_config/github-actions-validator/references/common_errors.md +512 -0
- package/skills/global_config/github-actions-validator/references/modern_features.md +384 -0
- package/skills/global_config/github-actions-validator/references/runners.md +317 -0
- package/skills/global_config/github-actions-validator/scripts/install_tools.sh +113 -0
- package/skills/global_config/github-actions-validator/scripts/validate_workflow.sh +910 -0
- package/skills/global_config/github-actions-validator/tests/test_validate_workflow.sh +237 -0
- package/skills/global_config/makefile-generator/SKILL.md +614 -0
- package/skills/global_config/makefile-generator/assets/templates/.gitkeep +1 -0
- package/skills/global_config/makefile-generator/docs/makefile-structure.md +530 -0
- package/skills/global_config/makefile-generator/docs/optimization-guide.md +784 -0
- package/skills/global_config/makefile-generator/docs/patterns-guide.md +642 -0
- package/skills/global_config/makefile-generator/docs/security-guide.md +361 -0
- package/skills/global_config/makefile-generator/docs/targets-guide.md +642 -0
- package/skills/global_config/makefile-generator/docs/variables-guide.md +596 -0
- package/skills/global_config/makefile-generator/scripts/add_standard_targets.sh +539 -0
- package/skills/global_config/makefile-generator/scripts/generate_makefile_template.sh +690 -0
- package/skills/global_config/makefile-generator/test/test_helper_scripts.sh +190 -0
- package/skills/global_config/makefile-validator/SKILL.md +244 -0
- package/skills/global_config/makefile-validator/docs/bake-tool.md +1000 -0
- package/skills/global_config/makefile-validator/docs/best-practices.md +858 -0
- package/skills/global_config/makefile-validator/docs/common-mistakes.md +944 -0
- package/skills/global_config/makefile-validator/examples/bad-makefile.mk +77 -0
- package/skills/global_config/makefile-validator/examples/good-makefile.mk +103 -0
- package/skills/global_config/makefile-validator/scripts/test_validate.sh +382 -0
- package/skills/global_config/makefile-validator/scripts/validate_makefile.sh +712 -0
- package/skills/global_config/{MCP_Manage → mcp-manage}/SKILL.md +3 -3
- package/skills/global_config/requesting-code-review/SKILL.md +98 -0
- package/skills/global_config/requesting-code-review/code-reviewer.md +198 -0
- package/skills/global_config/using-git-worktrees/SKILL.md +170 -0
- package/skills/global_config/verification-before-completion/SKILL.md +123 -0
- package/skills/global_config/writing-plans/SKILL.md +126 -46
- package/skills/global_config/writing-plans-legacy/SKILL.md +139 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hybridlabor-api/aos",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.9.0",
|
|
4
4
|
"description": "AOS — A Curated AI AGENT OS. Optimized agent skills and add-ons like memB, OpenWiki, Heimdall Token Saver, and Godmode architectures.",
|
|
5
5
|
"main": "installer.js",
|
|
6
6
|
"engines": {
|
|
@@ -24,8 +24,10 @@
|
|
|
24
24
|
"validate": "node scripts/validate-skills.mjs",
|
|
25
25
|
"validate:strict": "node scripts/validate-skills.mjs --strict",
|
|
26
26
|
"store:index": "node scripts/build-ecc-store-index.mjs",
|
|
27
|
+
"plugin:build": "node scripts/build-plugin-manifest.mjs",
|
|
28
|
+
"plugin:check": "node scripts/build-plugin-manifest.mjs --check",
|
|
27
29
|
"doctor": "node bin/aos-doctor.mjs",
|
|
28
|
-
"test": "node scripts/validate-skills.mjs --selftest && node scripts/validate-skills.mjs && node tests/aos-store.test.mjs && node tests/aos-doctor.test.mjs"
|
|
30
|
+
"test": "node scripts/validate-skills.mjs --selftest && node scripts/validate-skills.mjs && node scripts/build-plugin-manifest.mjs --check && node tests/aos-store.test.mjs && node tests/aos-doctor.test.mjs && node tests/build-plugin-manifest.test.mjs"
|
|
29
31
|
},
|
|
30
32
|
"publishConfig": {
|
|
31
33
|
"access": "public"
|
|
@@ -58,6 +60,8 @@
|
|
|
58
60
|
"mcps/",
|
|
59
61
|
"scripts/",
|
|
60
62
|
"vendor/",
|
|
63
|
+
"packages/aos-cli/",
|
|
64
|
+
"!packages/aos-cli/node_modules",
|
|
61
65
|
"!mcps/**/*.png",
|
|
62
66
|
"!mcps/**/*.jpg",
|
|
63
67
|
"!mcps/**/*.jpeg",
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
# AOS CLI
|
|
2
|
+
|
|
3
|
+
The AOS dispatcher contract and AOS skills, on the [pi](https://github.com/earendil-works/pi) coding agent.
|
|
4
|
+
|
|
5
|
+
```
|
|
6
|
+
aos-cli
|
|
7
|
+
aos-cli "why is the go build failing"
|
|
8
|
+
aos-cli --continue
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Which skills are loaded
|
|
12
|
+
|
|
13
|
+
**Ten, not all of them.** pi advertises every discovered skill by name and description in *every* system prompt. Measured on this machine: 210 skills, **~11,150 tokens per request**, of which the four `firecrawl` entries alone are ~2,250. That is a permanent tax on every turn.
|
|
14
|
+
|
|
15
|
+
`core-skills.json` narrows that to the handful worth having always:
|
|
16
|
+
|
|
17
|
+
```
|
|
18
|
+
ask-tim aos-setup aos-project-init mcp-manage systematic-debugging
|
|
19
|
+
planning-with-files token-saver-config archify deja-memory grill-me
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
Edit the file. It is a flat list of directory names under `~/.agents/skills/`, nothing more.
|
|
23
|
+
|
|
24
|
+
**The tradeoff, stated plainly:** a skill outside that list is not merely unadvertised — it is unreachable, `/skill:name` included. That is the cost of `--no-skills`, and it is why `aos-store` is how one gets added. If you want a skill in every session, put it in the list.
|
|
25
|
+
|
|
26
|
+
Names that are not installed are skipped with a note rather than being fatal: the list should quietly shrink when a skill is pruned, not stop pi from starting.
|
|
27
|
+
|
|
28
|
+
## In-session commands
|
|
29
|
+
|
|
30
|
+
| | |
|
|
31
|
+
|---|---|
|
|
32
|
+
| `/aos-status` | Runs `aos-doctor --json` and shows every check, with the failing ones marked |
|
|
33
|
+
| `/aos` | Menu: status, or the command to run outside for install / update / uninstall |
|
|
34
|
+
|
|
35
|
+
Both are **read-only**. `aos-doctor --json` exits 1 whenever a check is not ok, which is the normal "something needs attention" answer, so the exit code is ignored and the report's own `ok` field decides. The install, update and uninstall entries do not spawn the installer: it animates and prompts, and nesting it inside pi's TUI corrupts both renderers. They report the command to run in a separate terminal instead.
|
|
36
|
+
|
|
37
|
+
## What it actually is
|
|
38
|
+
|
|
39
|
+
A launcher, not a reimplementation. It adds three things to a plain `pi`:
|
|
40
|
+
|
|
41
|
+
| | |
|
|
42
|
+
|---|---|
|
|
43
|
+
| **AOS theme** | `themes/aos.json` — pi's `dark`, with the AOS palette (purple, amethyst, gold, forge, emerald, beige) |
|
|
44
|
+
| **Dispatcher graph** | `~/.agents/AGENTS.md` appended to the system prompt |
|
|
45
|
+
| **AOS skills** | nothing to do — pi already discovers `~/.agents/skills` (Agent Skills spec) |
|
|
46
|
+
|
|
47
|
+
Everything else — the agent loop, the model providers, the TUI, sessions, MCP-less tool use — is pi's. AOS CLI does not fork pi and does not wrap it; it starts it with three flags.
|
|
48
|
+
|
|
49
|
+
## What it deliberately does not do
|
|
50
|
+
|
|
51
|
+
**It does not depend on `@hybridlabor-api/aos`.** It reads `~/.agents/` at runtime, which the `aos` installer writes. That is the whole contract:
|
|
52
|
+
|
|
53
|
+
- AOS CLI and the AOS installer version and update independently. A pi update never drags in a 98 MB AOS reinstall.
|
|
54
|
+
- AOS CLI runs without AOS installed — it just starts with no AOS skills and says so.
|
|
55
|
+
- **It is not a pipeline executor.** The 7-node dispatcher graph reaches pi as instructions in `~/.agents/AGENTS.md`, not as machinery: the nodes are agent definitions, and the main session acts as the dispatcher, which is what `.agents/graph.md` specifies anyway. There is no `state.json` loop.
|
|
56
|
+
- **No MCP.** pi has no MCP client, so memB, deja and mcsc are not available here. AOS CLI is a chat + skills harness; the other seven harnesses remain the machinery.
|
|
57
|
+
|
|
58
|
+
## Install
|
|
59
|
+
|
|
60
|
+
Through the AOS installer (picks this up automatically once the menu entry lands), or directly:
|
|
61
|
+
|
|
62
|
+
```sh
|
|
63
|
+
npm i -g @earendil-works/pi-coding-agent # if you do not have pi yet
|
|
64
|
+
cd packages/aos-cli && npm i && npm link
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
Requires Node >= 22.19 — pi's floor. The AOS installer itself still supports Node >= 20, because AOS CLI is a separate install step.
|
|
68
|
+
|
|
69
|
+
## Layout
|
|
70
|
+
|
|
71
|
+
```
|
|
72
|
+
bin/aos-cli.mjs the launcher (~40 lines)
|
|
73
|
+
themes/aos.json pi dark theme, AOS palette
|
|
74
|
+
extensions/aos.ts /aos and /aos-status
|
|
75
|
+
scripts/check-theme.mjs theme contract guard, run by `npm test`
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
## Licence
|
|
79
|
+
|
|
80
|
+
MIT, same as AOS. pi is MIT, © the pi authors.
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// AOS CLI -- the AOS dispatcher contract on top of the pi coding agent.
|
|
3
|
+
//
|
|
4
|
+
// What this is: a launcher. Everything it provides already exists --
|
|
5
|
+
// * the AOS skills live in ~/.agents/skills, which the `aos` installer
|
|
6
|
+
// writes and which pi discovers natively (Agent Skills spec);
|
|
7
|
+
// * the 7-node dispatcher graph lives in ~/.agents/AGENTS.md.
|
|
8
|
+
//
|
|
9
|
+
// What this is not: a second copy of the AOS payload. It reads ~/.agents at
|
|
10
|
+
// runtime and never imports @hybridlabor-api/aos, so AOS CLI and the installer
|
|
11
|
+
// version and update independently. That is why pi updates are cheap here and
|
|
12
|
+
// AOS updates are cheap there.
|
|
13
|
+
//
|
|
14
|
+
// aos-cli open the chat in the current directory
|
|
15
|
+
// aos-cli "fix the failing go build" same, with a first prompt
|
|
16
|
+
// aos-cli --continue resume the last session for this project
|
|
17
|
+
// any other flag is handed to pi unchanged
|
|
18
|
+
|
|
19
|
+
import { spawn } from 'node:child_process';
|
|
20
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
21
|
+
import os from 'node:os';
|
|
22
|
+
import path from 'node:path';
|
|
23
|
+
import { fileURLToPath } from 'node:url';
|
|
24
|
+
|
|
25
|
+
const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
|
|
26
|
+
const agents = path.join(os.homedir(), '.agents');
|
|
27
|
+
|
|
28
|
+
const args = [
|
|
29
|
+
'--theme', path.join(root, 'themes', 'aos.json'),
|
|
30
|
+
'--use-theme', 'aos',
|
|
31
|
+
'--extension', path.join(root, 'extensions', 'aos.ts'),
|
|
32
|
+
];
|
|
33
|
+
|
|
34
|
+
// pi discovers AGENTS.md from the working directory and its ancestors only, and
|
|
35
|
+
// ~/.agents/AGENTS.md is neither. Without this flag the dispatcher graph -- the
|
|
36
|
+
// one thing a bare pi genuinely cannot see -- is silently missing.
|
|
37
|
+
const dispatch = path.join(agents, 'AGENTS.md');
|
|
38
|
+
if (existsSync(dispatch)) {
|
|
39
|
+
args.push('--append-system-prompt', dispatch);
|
|
40
|
+
} else {
|
|
41
|
+
console.error('AOS CLI: ~/.agents not found -- run `npx @hybridlabor-api/aos` first.');
|
|
42
|
+
console.error(' Starting without the AOS skills and the dispatcher graph.');
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// pi advertises every discovered skill by name and description in EVERY system
|
|
46
|
+
// prompt. Measured on this machine: 210 skills, ~11.150 tokens per request, of
|
|
47
|
+
// which the four firecrawl entries alone are ~2.250. That is a permanent tax on
|
|
48
|
+
// every turn to make one skill discoverable.
|
|
49
|
+
//
|
|
50
|
+
// core-skills.json narrows it to the handful worth having always. pi does the
|
|
51
|
+
// work natively: --no-skills stops the discovery walk, and each --skill is added
|
|
52
|
+
// back explicitly. --skill alone would not do it -- it appends to discovery
|
|
53
|
+
// rather than replacing it.
|
|
54
|
+
//
|
|
55
|
+
// The tradeoff is that a skill outside this list is not merely unadvertised but
|
|
56
|
+
// unreachable, not even via /skill:name. `aos-store` is how one gets added.
|
|
57
|
+
const core = coreSkills();
|
|
58
|
+
if (core.length) {
|
|
59
|
+
args.push('--no-skills');
|
|
60
|
+
for (const name of core) args.push('--skill', path.join(agents, 'skills', name));
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* The core skill list, skipping names that are not installed.
|
|
65
|
+
*
|
|
66
|
+
* A missing entry is not fatal: the point of the list is to cut the prompt
|
|
67
|
+
* tax, and a skill that was pruned or renamed upstream should quietly reduce
|
|
68
|
+
* the set rather than stop pi from starting.
|
|
69
|
+
*/
|
|
70
|
+
function coreSkills() {
|
|
71
|
+
const file = path.join(root, 'core-skills.json');
|
|
72
|
+
let names;
|
|
73
|
+
try {
|
|
74
|
+
names = JSON.parse(readFileSync(file, 'utf8'));
|
|
75
|
+
} catch (err) {
|
|
76
|
+
console.error(`AOS CLI: core-skills.json unreadable (${err.message}) -- loading all skills.`);
|
|
77
|
+
return [];
|
|
78
|
+
}
|
|
79
|
+
if (!Array.isArray(names)) {
|
|
80
|
+
console.error('AOS CLI: core-skills.json is not a list -- loading all skills.');
|
|
81
|
+
return [];
|
|
82
|
+
}
|
|
83
|
+
const found = names.filter((n) => typeof n === 'string' && existsSync(path.join(agents, 'skills', n)));
|
|
84
|
+
const absent = names.length - found.length;
|
|
85
|
+
if (absent) console.error(`AOS CLI: ${absent} core skill(s) not installed, skipped.`);
|
|
86
|
+
return found;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// Find pi's CLI entry by walking up to the package root that declares it.
|
|
90
|
+
// Neither require.resolve nor a fixed depth works: pi's `exports` map exports
|
|
91
|
+
// only its library entry, so `@earendil-works/pi-coding-agent/package.json`
|
|
92
|
+
// and `/dist/bundle/cli.js` both throw ERR_PACKAGE_PATH_NOT_EXPORTED, and
|
|
93
|
+
// npm leaves no node_modules/.bin/pi behind for a transitive dependency.
|
|
94
|
+
// The resolved import specifier points inside the package, so from there the
|
|
95
|
+
// root is at most a couple of levels up.
|
|
96
|
+
function findPiEntry(startDir) {
|
|
97
|
+
let dir = startDir;
|
|
98
|
+
for (;;) {
|
|
99
|
+
const manifest = path.join(dir, 'package.json');
|
|
100
|
+
if (existsSync(manifest)) {
|
|
101
|
+
try {
|
|
102
|
+
const parsed = JSON.parse(readFileSync(manifest, 'utf8'));
|
|
103
|
+
if (parsed.bin && parsed.bin.pi) return path.resolve(dir, parsed.bin.pi);
|
|
104
|
+
} catch {
|
|
105
|
+
// unreadable or malformed -- keep walking, a parent's manifest may still be pi's
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
const parent = path.dirname(dir);
|
|
109
|
+
if (parent === dir) return null;
|
|
110
|
+
dir = parent;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function piInvocation() {
|
|
115
|
+
try {
|
|
116
|
+
const entry = findPiEntry(
|
|
117
|
+
path.dirname(fileURLToPath(import.meta.resolve('@earendil-works/pi-coding-agent')))
|
|
118
|
+
);
|
|
119
|
+
if (entry) return { command: process.execPath, prefix: [entry] };
|
|
120
|
+
} catch {
|
|
121
|
+
// pi is not installed next to us -- fall through to PATH
|
|
122
|
+
}
|
|
123
|
+
return { command: 'pi', prefix: [] };
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const { command, prefix } = piInvocation();
|
|
127
|
+
const child = spawn(command, [...prefix, ...args, ...process.argv.slice(2)], { stdio: 'inherit' });
|
|
128
|
+
|
|
129
|
+
child.on('error', (err) => {
|
|
130
|
+
console.error(`AOS CLI: cannot start pi (${err.code ?? err.message}).`);
|
|
131
|
+
console.error(' Install it with: npm i -g @earendil-works/pi-coding-agent');
|
|
132
|
+
process.exit(1);
|
|
133
|
+
});
|
|
134
|
+
child.on('exit', (code, signal) => process.exit(signal ? 1 : code ?? 0));
|
|
@@ -0,0 +1,321 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AOS CLI commands -- `/aos` and `/aos-status`.
|
|
3
|
+
*
|
|
4
|
+
* Read-only by default. The AOS installer owns the terminal: it animates, it
|
|
5
|
+
* prompts, and pi's own docs say an extension must not create a second
|
|
6
|
+
* terminal renderer. So anything that needs a real TTY (install, update,
|
|
7
|
+
* uninstall) is reported as a command to run outside the session rather than
|
|
8
|
+
* spawned here -- a nested installer inside pi's TUI corrupts both.
|
|
9
|
+
*
|
|
10
|
+
* `aos-doctor --json` is the only thing this executes, and it is read-only.
|
|
11
|
+
* The shape it returns is stable and is asserted by tests/aos-doctor.test.mjs:
|
|
12
|
+
* { ok, platform, arch, results: [{ area, name, ok, detail, warningOnly }] }
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import { spawn } from "node:child_process";
|
|
16
|
+
import { connect } from "node:net";
|
|
17
|
+
import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
18
|
+
// DynamicBorder is re-exported by the agent package, not by pi-tui -- the
|
|
19
|
+
// examples/extensions/preset.ts reference splits them the same way.
|
|
20
|
+
import { DynamicBorder } from "@earendil-works/pi-coding-agent";
|
|
21
|
+
import { Container, SelectItem, SelectList, Text } from "@earendil-works/pi-tui";
|
|
22
|
+
|
|
23
|
+
const DASHBOARD_PORT = 7900;
|
|
24
|
+
const DOCTOR_TIMEOUT_MS = 20_000;
|
|
25
|
+
const AUTH_TIMEOUT_MS = 8_000;
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Providers to check for credentials, in priority order.
|
|
29
|
+
*
|
|
30
|
+
* A second provider slots in here and nowhere else -- the check, the status
|
|
31
|
+
* row and the startup notice all read this list. pi owns provider auth, not the
|
|
32
|
+
* AOS installer: it already has a `/login` command, and the AOS installer has no
|
|
33
|
+
* business asking for an Anthropic key it will never use itself. AOS CLI only
|
|
34
|
+
* has to notice when nothing is configured and point at `/login`.
|
|
35
|
+
*
|
|
36
|
+
* Deliberately short. Reporting "no configured provider (checked: ...)" with an
|
|
37
|
+
* honest list beats claiming a global state this process cannot actually see.
|
|
38
|
+
*/
|
|
39
|
+
const PROVIDERS = ["anthropic", "google"] as const;
|
|
40
|
+
|
|
41
|
+
interface DoctorResult {
|
|
42
|
+
area: string;
|
|
43
|
+
name: string;
|
|
44
|
+
ok: boolean;
|
|
45
|
+
detail: string;
|
|
46
|
+
warningOnly: boolean;
|
|
47
|
+
}
|
|
48
|
+
interface DoctorReport {
|
|
49
|
+
ok: boolean;
|
|
50
|
+
results: DoctorResult[];
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Run aos-doctor and return its report whatever the exit code.
|
|
55
|
+
*
|
|
56
|
+
* aos-doctor exits 1 whenever any single check is not ok -- that is the normal
|
|
57
|
+
* "something needs attention" answer, not a failure to run. Rejecting on a
|
|
58
|
+
* non-zero exit threw away a valid 27-check report and rendered as
|
|
59
|
+
* "aos-doctor could not be run", which is the worst possible reading: it
|
|
60
|
+
* reports the absence of a check as if the check had passed. The report's own
|
|
61
|
+
* `ok` field is the verdict; the exit code carries no extra information.
|
|
62
|
+
*/
|
|
63
|
+
function runDoctor(): Promise<DoctorReport> {
|
|
64
|
+
return new Promise((resolve, reject) => {
|
|
65
|
+
// shell on Windows only: the global shims are .cmd there, and spawn
|
|
66
|
+
// does not resolve those without it.
|
|
67
|
+
const child = spawn("aos-doctor", ["--json"], {
|
|
68
|
+
timeout: DOCTOR_TIMEOUT_MS,
|
|
69
|
+
shell: process.platform === "win32",
|
|
70
|
+
});
|
|
71
|
+
let out = "";
|
|
72
|
+
let err = "";
|
|
73
|
+
child.stdout?.on("data", (c) => {
|
|
74
|
+
out += c;
|
|
75
|
+
});
|
|
76
|
+
child.stderr?.on("data", (c) => {
|
|
77
|
+
err += c;
|
|
78
|
+
});
|
|
79
|
+
child.once("error", reject);
|
|
80
|
+
child.once("close", (code) => {
|
|
81
|
+
try {
|
|
82
|
+
resolve(JSON.parse(out) as DoctorReport);
|
|
83
|
+
} catch {
|
|
84
|
+
reject(new Error(err.trim() || `aos-doctor produced no JSON (exit ${code})`));
|
|
85
|
+
}
|
|
86
|
+
});
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Ask pi whether one provider has usable credentials.
|
|
92
|
+
*
|
|
93
|
+
* `pi auth check --provider <name> --json` is a documented CLI surface and
|
|
94
|
+
* exits 0 for every verdict, including not_ready, so the JSON is the answer and
|
|
95
|
+
* the exit code carries nothing. `pi --list-models` was the tempting shortcut
|
|
96
|
+
* and is wrong: it lists every provider pi knows about whether or not a key is
|
|
97
|
+
* present, so a model in the list says nothing about being able to use it.
|
|
98
|
+
*
|
|
99
|
+
* A provider this check cannot reach at all is "unknown", never "not ready" --
|
|
100
|
+
* those are different states and reporting one as the other is how a health
|
|
101
|
+
* check ends up lying.
|
|
102
|
+
*/
|
|
103
|
+
function checkProvider(provider: string): Promise<"ready" | "not_ready" | "unknown"> {
|
|
104
|
+
return new Promise((resolve) => {
|
|
105
|
+
// argv[1] is pi's own bundle, because pi is the process running this
|
|
106
|
+
// extension. PATH is the fallback for a launch that re-execs a bare `pi`.
|
|
107
|
+
const entry = process.argv[1];
|
|
108
|
+
const [command, prefix] =
|
|
109
|
+
entry && /pi/i.test(entry) ? [process.execPath, [entry]] : ["pi", []];
|
|
110
|
+
|
|
111
|
+
const child = spawn(command, [...prefix, "auth", "check", "--provider", provider, "--json"], {
|
|
112
|
+
timeout: AUTH_TIMEOUT_MS,
|
|
113
|
+
shell: process.platform === "win32",
|
|
114
|
+
});
|
|
115
|
+
let out = "";
|
|
116
|
+
child.stdout?.on("data", (c) => {
|
|
117
|
+
out += c;
|
|
118
|
+
});
|
|
119
|
+
child.once("error", () => resolve("unknown"));
|
|
120
|
+
child.once("close", () => {
|
|
121
|
+
try {
|
|
122
|
+
const parsed = JSON.parse(out) as { status?: string };
|
|
123
|
+
resolve(parsed.status === "ready" ? "ready" : "not_ready");
|
|
124
|
+
} catch {
|
|
125
|
+
resolve("unknown");
|
|
126
|
+
}
|
|
127
|
+
});
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/** One row per provider, ready ones first, for the status overlay. */
|
|
132
|
+
async function providerStatus(): Promise<{ provider: string; state: string }[]> {
|
|
133
|
+
return Promise.all(
|
|
134
|
+
PROVIDERS.map(async (provider) => ({ provider, state: await checkProvider(provider) })),
|
|
135
|
+
);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function portOpen(port: number, timeoutMs = 500): Promise<boolean> {
|
|
139
|
+
return new Promise((resolve) => {
|
|
140
|
+
const socket = connect({ port, host: "127.0.0.1" });
|
|
141
|
+
const done = (up: boolean) => {
|
|
142
|
+
socket.destroy();
|
|
143
|
+
resolve(up);
|
|
144
|
+
};
|
|
145
|
+
socket.setTimeout(timeoutMs);
|
|
146
|
+
socket.once("connect", () => done(true));
|
|
147
|
+
socket.once("timeout", () => done(false));
|
|
148
|
+
socket.once("error", () => done(false));
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
async function showStatus(ctx: ExtensionContext): Promise<void> {
|
|
153
|
+
let report: DoctorReport | null = null;
|
|
154
|
+
let failure: string | null = null;
|
|
155
|
+
try {
|
|
156
|
+
report = await runDoctor();
|
|
157
|
+
} catch (err) {
|
|
158
|
+
failure = err instanceof Error ? err.message : String(err);
|
|
159
|
+
}
|
|
160
|
+
const dashboardUp = await portOpen(DASHBOARD_PORT);
|
|
161
|
+
const providers = await providerStatus();
|
|
162
|
+
const anyReady = providers.some((p) => p.state === "ready");
|
|
163
|
+
|
|
164
|
+
await ctx.ui.custom<void>((tui, theme, _kb, done) => {
|
|
165
|
+
const container = new Container();
|
|
166
|
+
container.addChild(new DynamicBorder((s) => theme.fg("accent", s)));
|
|
167
|
+
|
|
168
|
+
const mark = (ok: boolean) => theme.fg(ok ? "success" : "error", ok ? "●" : "○");
|
|
169
|
+
|
|
170
|
+
if (failure) {
|
|
171
|
+
container.addChild(new Text(theme.fg("error", "aos-doctor could not be run")));
|
|
172
|
+
container.addChild(new Text(theme.fg("muted", failure)));
|
|
173
|
+
container.addChild(new Text(theme.fg("dim", "It is installed by `npx @hybridlabor-api/aos`.")));
|
|
174
|
+
} else if (report) {
|
|
175
|
+
const failing = report.results.filter((r) => !r.ok);
|
|
176
|
+
container.addChild(
|
|
177
|
+
new Text(
|
|
178
|
+
theme.bold(
|
|
179
|
+
`AOS ${failing.length === 0 ? theme.fg("success", "healthy") : theme.fg("error", `${failing.length} failing check(s)`)}`,
|
|
180
|
+
),
|
|
181
|
+
),
|
|
182
|
+
);
|
|
183
|
+
container.addChild(new Text(""));
|
|
184
|
+
for (const r of report.results) {
|
|
185
|
+
container.addChild(new Text(`${mark(r.ok)} ${r.name}${r.ok ? "" : theme.fg("error", " ← needs attention")}`));
|
|
186
|
+
if (r.detail) container.addChild(new Text(theme.fg("muted", ` ${r.detail}`)));
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
container.addChild(new Text(""));
|
|
191
|
+
container.addChild(
|
|
192
|
+
new Text(
|
|
193
|
+
`${mark(dashboardUp)} Dashboard :${DASHBOARD_PORT} ${
|
|
194
|
+
dashboardUp ? theme.fg("success", "running") : theme.fg("dim", "stopped -- run `npx aos-dashboard`")
|
|
195
|
+
}`,
|
|
196
|
+
),
|
|
197
|
+
);
|
|
198
|
+
container.addChild(
|
|
199
|
+
new Text(
|
|
200
|
+
`${mark(anyReady)} Model ${
|
|
201
|
+
anyReady
|
|
202
|
+
? theme.fg("success", providers.filter((p) => p.state === "ready").map((p) => p.provider).join(", "))
|
|
203
|
+
: theme.fg("error", "no provider configured -- run /login in pi")
|
|
204
|
+
}`,
|
|
205
|
+
),
|
|
206
|
+
);
|
|
207
|
+
container.addChild(new Text(""));
|
|
208
|
+
container.addChild(new Text(theme.fg("dim", "esc or enter to close")));
|
|
209
|
+
container.addChild(new DynamicBorder((s) => theme.fg("accent", s)));
|
|
210
|
+
|
|
211
|
+
return {
|
|
212
|
+
render(width: number) {
|
|
213
|
+
return container.render(width);
|
|
214
|
+
},
|
|
215
|
+
invalidate() {
|
|
216
|
+
container.invalidate();
|
|
217
|
+
},
|
|
218
|
+
handleInput(data: string) {
|
|
219
|
+
if (data === "" || data === "\r" || data === "\n" || data === "") done();
|
|
220
|
+
tui.requestRender();
|
|
221
|
+
},
|
|
222
|
+
};
|
|
223
|
+
});
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
export default function aosCommands(pi: ExtensionAPI) {
|
|
227
|
+
// Fire and forget on session_start: never block startup on it, and never
|
|
228
|
+
// repeat. A user without credentials can still read skills, run
|
|
229
|
+
// /aos-status and browse -- only sending a message needs a model, so this is
|
|
230
|
+
// a notice, not a gate.
|
|
231
|
+
//
|
|
232
|
+
// It says "at startup" on purpose. This handler runs once, so a /login
|
|
233
|
+
// completed inside the session leaves the warning standing next to a working
|
|
234
|
+
// model -- and a notice that is visible after you acted on it is worse than
|
|
235
|
+
// none, because it reads as a current fact. pi has no event this extension
|
|
236
|
+
// can rely on for "a login just landed", so rather than invent a poll or
|
|
237
|
+
// guess at an API, the wording states when it was true and /aos-status is
|
|
238
|
+
// the live reading. Measured: `pi auth check` returns ready/oauth once the
|
|
239
|
+
// browser flow completes, so that command and nothing else is the truth.
|
|
240
|
+
let warnedNoProvider = false;
|
|
241
|
+
pi.on("session_start", async (_event, ctx) => {
|
|
242
|
+
if (warnedNoProvider) return;
|
|
243
|
+
const providers = await providerStatus();
|
|
244
|
+
if (providers.some((p) => p.state === "ready")) return;
|
|
245
|
+
warnedNoProvider = true;
|
|
246
|
+
const unknown = providers.filter((p) => p.state === "unknown").map((p) => p.provider);
|
|
247
|
+
ctx.ui.notify(
|
|
248
|
+
`No model provider was configured at startup (checked: ${providers.map((p) => p.provider).join(", ")}). ` +
|
|
249
|
+
`Run /login to add one -- or /aos-status for the current state.`,
|
|
250
|
+
"warning",
|
|
251
|
+
);
|
|
252
|
+
if (unknown.length) {
|
|
253
|
+
ctx.ui.notify(`Could not reach pi's auth check for: ${unknown.join(", ")} -- treated as unverified, not as unconfigured.`, "info");
|
|
254
|
+
}
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
pi.registerCommand("aos-status", {
|
|
258
|
+
description: "AOS health, failing checks and daemon status",
|
|
259
|
+
handler: async (_args, ctx) => {
|
|
260
|
+
await showStatus(ctx);
|
|
261
|
+
},
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
pi.registerCommand("aos", {
|
|
265
|
+
description: "AOS menu: status, dashboard, or the command for a terminal action",
|
|
266
|
+
handler: async (_args, ctx) => {
|
|
267
|
+
const items: SelectItem[] = [
|
|
268
|
+
{ value: "status", label: "Status", description: "run aos-doctor and show failing checks" },
|
|
269
|
+
{ value: "update", label: "Skills & MCPs aktualisieren", description: "runs outside this session" },
|
|
270
|
+
{ value: "install", label: "AOS CLI nachinstallieren", description: "runs outside this session" },
|
|
271
|
+
{ value: "uninstall", label: "AOS deinstallieren", description: "runs outside this session" },
|
|
272
|
+
];
|
|
273
|
+
|
|
274
|
+
const choice = await ctx.ui.custom<string | null>((tui, theme, _kb, done) => {
|
|
275
|
+
const container = new Container();
|
|
276
|
+
container.addChild(new DynamicBorder((s) => theme.fg("accent", s)));
|
|
277
|
+
container.addChild(new Text(theme.fg("accent", theme.bold("AOS"))));
|
|
278
|
+
|
|
279
|
+
const list = new SelectList(items, items.length, {
|
|
280
|
+
selectedPrefix: (t) => theme.fg("accent", t),
|
|
281
|
+
selectedText: (t) => theme.fg("accent", t),
|
|
282
|
+
description: (t) => theme.fg("muted", t),
|
|
283
|
+
});
|
|
284
|
+
list.onSelect = (item) => done(item.value);
|
|
285
|
+
list.onCancel = () => done(null);
|
|
286
|
+
container.addChild(list);
|
|
287
|
+
|
|
288
|
+
container.addChild(new Text(theme.fg("dim", "↑↓ navigate • enter select • esc cancel")));
|
|
289
|
+
container.addChild(new DynamicBorder((s) => theme.fg("accent", s)));
|
|
290
|
+
|
|
291
|
+
return {
|
|
292
|
+
render(width: number) {
|
|
293
|
+
return container.render(width);
|
|
294
|
+
},
|
|
295
|
+
invalidate() {
|
|
296
|
+
container.invalidate();
|
|
297
|
+
},
|
|
298
|
+
handleInput(data: string) {
|
|
299
|
+
list.handleInput(data);
|
|
300
|
+
tui.requestRender();
|
|
301
|
+
},
|
|
302
|
+
};
|
|
303
|
+
});
|
|
304
|
+
|
|
305
|
+
if (choice === "status") {
|
|
306
|
+
await showStatus(ctx);
|
|
307
|
+
return;
|
|
308
|
+
}
|
|
309
|
+
if (!choice) return;
|
|
310
|
+
|
|
311
|
+
// The installer animates and prompts; running it with inherited stdio
|
|
312
|
+
// inside pi's TUI corrupts both renderers. Report the command instead.
|
|
313
|
+
const commands: Record<string, string> = {
|
|
314
|
+
update: "aos",
|
|
315
|
+
install: "aos",
|
|
316
|
+
uninstall: "npx @hybridlabor-api/aos-uninstall",
|
|
317
|
+
};
|
|
318
|
+
ctx.ui.notify(`Run this in a separate terminal: ${commands[choice]}`, "info");
|
|
319
|
+
},
|
|
320
|
+
});
|
|
321
|
+
}
|