codo-ai 2.23.13 → 2.23.15
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/README.md +225 -119
- package/bin/codo.js +307 -0
- package/package.json +96 -23
- package/bin/codo +0 -10
- package/postinstall.mjs +0 -222
package/README.md
CHANGED
|
@@ -1,119 +1,225 @@
|
|
|
1
|
-
<div align="center">
|
|
2
|
-
|
|
3
|
-
# \>_ COdo
|
|
4
|
-
|
|
5
|
-
**
|
|
6
|
-
|
|
7
|
-
Spec it. Code it. Ship it. **Secure it.**
|
|
8
|
-
|
|
9
|
-
[Website](https://codo-ai.vercel.app/)
|
|
10
|
-
|
|
11
|
-
`npm install -g codo-ai`
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
```
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
```
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
1
|
+
<div align="center">
|
|
2
|
+
|
|
3
|
+
# \>_ COdo
|
|
4
|
+
|
|
5
|
+
**Spec-Driven Software Development with Security-Guided Multi-Agent Orchestration**
|
|
6
|
+
|
|
7
|
+
Spec it. Code it. Ship it. **Secure it.**
|
|
8
|
+
|
|
9
|
+
[Website](https://codo-ai.vercel.app/) | [Documentation](https://codo-ai.vercel.app/) | [GitHub](https://github.com/Mosalah4351/COdo) | [Report Issue](https://github.com/Mosalah4351/COdo/issues)
|
|
10
|
+
|
|
11
|
+
`npm install -g codo-ai`
|
|
12
|
+
|
|
13
|
+
**Latest release: 2.23.15**
|
|
14
|
+
|
|
15
|
+
</div>
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
## What's new in 2.23.15
|
|
20
|
+
|
|
21
|
+
- **Sensei** - a senior technical advisor you can consult mid-task. Read-only, evidence-first, and it ends every verdict with a decisive recommendation.
|
|
22
|
+
- **Checker** - a quality and hygiene auditor that verifies findings against real evidence and records them durably instead of guessing.
|
|
23
|
+
- **Business Agent** - an orchestrator with hands that turns requests into real `.xlsx`, `.docx`, `.pptx`, `.pdf`, and research deliverables.
|
|
24
|
+
- **`/ask-sensei`, `/quality-check`, `/hygiene-check`** - three new commands that dispatch those specialists in one keystroke.
|
|
25
|
+
- **`sensei_model` / `judge_model` / `subagent_model`** - put your strongest model where judgment matters and a cheap one where it does not.
|
|
26
|
+
- **Free to start** - OpenCode Zen free models, OpenRouter `:free` variants, and NVIDIA's free tier all work out of the box.
|
|
27
|
+
|
|
28
|
+
## What is COdo?
|
|
29
|
+
|
|
30
|
+
COdo is a **terminal-native, spec-driven AI coding agent** - a fork of [OpenCode](https://github.com/anomalyco/opencode) with structured workflow orchestration, an autonomous multi-agent compose engine, a built-in advisory layer, and the all-new **Sec-Test** red security engine.
|
|
31
|
+
|
|
32
|
+
No browser tab. No separate app. No context-switching. Just you, your terminal, and an AI that knows what phase of the project you're in.
|
|
33
|
+
|
|
34
|
+
```
|
|
35
|
+
$ npm install -g codo-ai
|
|
36
|
+
$ cd my-project
|
|
37
|
+
$ codo
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## Sensei - the second opinion your agent was missing
|
|
41
|
+
|
|
42
|
+
Most agents grade their own homework. COdo ships a **Sensei**: a read-only senior advisor that reads the actual conversation - the request, every dispatch, every tool result - and tells the agent what it would do differently.
|
|
43
|
+
|
|
44
|
+
```
|
|
45
|
+
/ask-sensei "is this migration safe to ship?"
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
The Sensei does not guess. It inspects files, git history, configs, and command output first, cites what it saw, names the trade-offs, and commits to a call. Every response ends in a fixed verdict block:
|
|
49
|
+
|
|
50
|
+
```
|
|
51
|
+
## SENSEI
|
|
52
|
+
- Assessment: the migration is reversible but the backfill is not
|
|
53
|
+
- Risks: 40k rows rewritten in one transaction; lock held for ~90s
|
|
54
|
+
- Recommendation: batch by 5k with a resumable cursor before shipping
|
|
55
|
+
- Confidence: high - measured against the actual migration file
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
It is also wired in as **automatic escalation**: primary agents are instructed to consult it before committing to an approach, when stuck on a repeated failure, before dropping an agreed plan, and before declaring work done.
|
|
59
|
+
|
|
60
|
+
## Choose your models
|
|
61
|
+
|
|
62
|
+
You are not stuck with one model. COdo exposes three independent knobs, so you can spend your strongest model only where judgment matters:
|
|
63
|
+
|
|
64
|
+
| Key | Drives | A sensible fit |
|
|
65
|
+
|---|---|---|
|
|
66
|
+
| `sensei_model` | the Sensei (`/ask-sensei`) **and** the Checker (`/quality-check`, `/hygiene-check`) | your strongest reasoning model |
|
|
67
|
+
| `judge_model` | the Goal Judge that verifies stop-conditions | a solid mid-tier model |
|
|
68
|
+
| `subagent_model` | every dispatched subagent and parallel compose worker | a fast, cheap model |
|
|
69
|
+
|
|
70
|
+
```jsonc
|
|
71
|
+
{
|
|
72
|
+
"sensei_model": "anthropic/claude-opus-4#high",
|
|
73
|
+
"judge_model": "anthropic/claude-sonnet-4",
|
|
74
|
+
"subagent_model": "anthropic/claude-haiku-4"
|
|
75
|
+
}
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
`sensei_model` falls back to `judge_model`, then to the standard subagent chain. Any of the three can point at a free model, so a fully free setup is a valid configuration.
|
|
79
|
+
|
|
80
|
+
### Free models - run COdo without a bill
|
|
81
|
+
|
|
82
|
+
COdo is provider-agnostic, and several providers give you usable capacity for nothing:
|
|
83
|
+
|
|
84
|
+
- **OpenCode Zen free models** - sign in once and pick a free-tier model such as `Big Pickle`, `Ox Alpha Free`, `MiMo-V2.5 Free`, `Hy3 Free`, `Nemotron 3 Ultra Free`, `Nemotron 3.5 Lightning Free`, or `Muse Spark 1.2 Contributor Free`. Good enough to drive a real coding session, and the fastest way to try COdo.
|
|
85
|
+
- **OpenRouter** - use any `:free` model variant with your OpenRouter key. Great for mixing providers behind one endpoint.
|
|
86
|
+
- **NVIDIA** - a free-tier API key from `build.nvidia.com` gives you NVIDIA-hosted models at no cost.
|
|
87
|
+
|
|
88
|
+
Point `subagent_model` at a free model to keep the fan-out cheap, and reserve a stronger `sensei_model` for the moments that decide how the work turns out.
|
|
89
|
+
|
|
90
|
+
## Checker - ship-readiness, verified
|
|
91
|
+
|
|
92
|
+
`/quality-check` and `/hygiene-check` dispatch the **Checker**, a read-only auditor for bugs, security issues, code-quality problems, and repo hygiene: committed secrets, tracked junk, dead weight, risky code with no tests.
|
|
93
|
+
|
|
94
|
+
The Checker refuses to report what it cannot prove. Every finding needs a file and line or a tool-output snippet, and confirmed findings are written to a durable findings store so they survive the session, feed posture reports, and can be exported as SARIF.
|
|
95
|
+
|
|
96
|
+
```bash
|
|
97
|
+
$ /quality-check "review the last commit"
|
|
98
|
+
[high] src/db/query.ts:42 - raw string concatenation in SQL builder (CWE-89)
|
|
99
|
+
[low] tracked artifact: server.log committed at repo root
|
|
100
|
+
summary: 1 high, 1 low - fix the query builder first
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
## Why COdo?
|
|
104
|
+
|
|
105
|
+
| | Static linters | CI scanners | **COdo Sec-Test** |
|
|
106
|
+
|---|---|---|---|
|
|
107
|
+
| Finds design flaws pre-code | no | no | **yes - STRIDE threat modeling** |
|
|
108
|
+
| Noise level | false-positive flood | late-stage flood | **evidence-first findings with PoC** |
|
|
109
|
+
| Runs where you work | no | CI only | **your terminal** |
|
|
110
|
+
| Fixes vulnerabilities | warnings only | warnings only | **remediation diffs + mutation-backed tests** |
|
|
111
|
+
|
|
112
|
+
## Highlights
|
|
113
|
+
|
|
114
|
+
### Sec-Test - Autonomous Security Orchestrator
|
|
115
|
+
|
|
116
|
+
Six specialist personas guarding the full lifecycle:
|
|
117
|
+
|
|
118
|
+
| Persona | Role |
|
|
119
|
+
|---|---|
|
|
120
|
+
| **Security Architect** | Pre-code STRIDE/DREAD threat modeling (OWASP ASVS mapped) |
|
|
121
|
+
| **AppSec Engineer** | Real-time SAST on diffs - OWASP Top 10:2025 / CWE Top 25, secrets, CVEs |
|
|
122
|
+
| **DevSecOps Engineer** | Pipeline hardening (NIST SSDF), SBOM (CycloneDX/SPDX), SLSA + Sigstore |
|
|
123
|
+
| **Penetration Tester** | Scope-gated live probing (PTES/WSTG) - refuses targets outside `.codo/security-scope.json` |
|
|
124
|
+
| **Quality Engineer** | Adversarial tests verified by mutation kill-rate |
|
|
125
|
+
| **Security Engineer** | Audits the agent itself; posture drift tracking |
|
|
126
|
+
|
|
127
|
+
```bash
|
|
128
|
+
$ /sec-test "Audit git diff for SQL injection and hardcoded secrets"
|
|
129
|
+
[1] Raw string concatenation in query builder
|
|
130
|
+
[2] Hardcoded fallback JWT secret ("dev-secret-key-12345")
|
|
131
|
+
-> Parameterized query fix applied; secret moved to env with startup validation
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
### Workflow Engines
|
|
135
|
+
|
|
136
|
+
Switch methodology per project scale:
|
|
137
|
+
|
|
138
|
+
- `/workflow gsd` - autonomous milestone planning, persistent `.planning/` state, survives context resets
|
|
139
|
+
- `/workflow gstack` - Garry Tan's 23-role virtual engineering team
|
|
140
|
+
- `/workflow speckit` - GitHub SDD spec-first pipeline
|
|
141
|
+
- `/workflow vibemode` - pure flow, zero overhead
|
|
142
|
+
|
|
143
|
+
### Multi-Agent Compose
|
|
144
|
+
|
|
145
|
+
Complex tasks decompose into DAGs routed to specialist agents executing in parallel waves:
|
|
146
|
+
|
|
147
|
+
```
|
|
148
|
+
/compose "Build auth service with JWT, rate limiting & Sec-Test"
|
|
149
|
+
[A] Security Architect -> threat model | parallel
|
|
150
|
+
[B] Builder -> implementation | wave
|
|
151
|
+
[C] AppSec Engineer -> real-time SAST audit |
|
|
152
|
+
[D] Quality Engineer -> mutation tests |
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
### Goal Judge
|
|
156
|
+
|
|
157
|
+
Declare outcomes, not tasks. An independent judge model verifies stop-conditions - the builder never self-approves.
|
|
158
|
+
|
|
159
|
+
```
|
|
160
|
+
/goal "All tests pass with >80% coverage and zero HIGH/CVEs"
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
## Business Agent - deliverables, not chat
|
|
164
|
+
|
|
165
|
+
COdo ships a dedicated **Business Agent**: an orchestrator with hands. It routes each request to the right `business:*` skill and then *does the work itself* - writing the scripts, running them, and handing you real files on disk, verified before handoff.
|
|
166
|
+
|
|
167
|
+
```bash
|
|
168
|
+
$ codo
|
|
169
|
+
> research the 2026 AI agent market and give me a deck plus a one-pager PDF
|
|
170
|
+
Plan: research -> deck -> one-pager PDF
|
|
171
|
+
[deep-research] 14 sources, cited
|
|
172
|
+
[pptx-official] market-overview.pptx (12 slides)
|
|
173
|
+
[pdf-official] market-overview.pdf (2 pages)
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
It chains skills for multi-deliverable requests and picks the most probable one when a request is ambiguous, stating its choice in a line instead of stalling.
|
|
177
|
+
|
|
178
|
+
| Ask for | It produces |
|
|
179
|
+
|---|---|
|
|
180
|
+
| Spreadsheet models, cleanup, formulas, exports | `.xlsx` / `.csv` via `xlsx-official` |
|
|
181
|
+
| Reports, letters, contracts, templates | `.docx` via `docx-official` |
|
|
182
|
+
| Slide decks and presentations | `.pptx` via `pptx-official` |
|
|
183
|
+
| PDF creation, form filling, OCR, merge/split | `.pdf` via `pdf-official` |
|
|
184
|
+
| Multi-source investigation with citations | cited report via `deep-research` |
|
|
185
|
+
| Long-running autonomous research and experiments | evidence-backed study via `super-research` |
|
|
186
|
+
| Academic papers, LaTeX, citation audits | publication-ready paper via `research-paper-writing` |
|
|
187
|
+
| KPIs, dashboards, charts, metric diagnostics | analysis + charts via `data-analytics` |
|
|
188
|
+
| Design specs, UI and product design | `design-blueprint`, `frontend-design`, `product-design` |
|
|
189
|
+
| Courses, sales material, video renders, skill authoring | `learn-everything`, `sales`, `html-to-video-pipeline`, `skill-creator` |
|
|
190
|
+
|
|
191
|
+
## Compliance-First Scraper
|
|
192
|
+
|
|
193
|
+
`/scraper <url> <what to extract>` - robots.txt honored, ToS checked, crawl-delays enforced, escalation ladder from static fetch to authenticated sessions.
|
|
194
|
+
|
|
195
|
+
## Install
|
|
196
|
+
|
|
197
|
+
```bash
|
|
198
|
+
# Recommended
|
|
199
|
+
npm install -g codo-ai
|
|
200
|
+
|
|
201
|
+
# One-time run
|
|
202
|
+
npx codo-ai
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
Requirements: Node 18+ or Bun | macOS / Linux / Windows | any provider - bring an Anthropic, OpenAI, Google, or OpenAI-compatible key, or start free on OpenCode Zen, OpenRouter, or NVIDIA.
|
|
206
|
+
|
|
207
|
+
> Migrating from `@codo-ai/cli`? That package is deprecated - `codo-ai` replaces it.
|
|
208
|
+
|
|
209
|
+
## Documentation
|
|
210
|
+
|
|
211
|
+
Full developer docs - every persona, skill, workflow, permission mode, and best practice - live at **[codo-ai.vercel.app](https://codo-ai.vercel.app/)**.
|
|
212
|
+
|
|
213
|
+
## Contributing
|
|
214
|
+
|
|
215
|
+
PRs welcome on [GitHub](https://github.com/Mosalah4351/COdo). Default branch: `dev`. Conventional commits.
|
|
216
|
+
|
|
217
|
+
## License
|
|
218
|
+
|
|
219
|
+
MIT (c) [Mohamed Salah](https://www.linkedin.com/in/mohamed-salah-59bb41300/)
|
|
220
|
+
|
|
221
|
+
Built on [OpenCode](https://github.com/anomalyco/opencode).
|
|
222
|
+
|
|
223
|
+
---
|
|
224
|
+
|
|
225
|
+
<sub>**Search terms:** spec-driven development, security-guided multi-agent orchestration, AI coding agent, terminal AI agent, CLI coding assistant, agentic coding, autonomous coding agent, multi-agent orchestration, subagents, sensei, second opinion, AI code review, AI security scanner, AppSec, SAST, DAST, threat modeling, penetration testing, OWASP Top 10, CWE Top 25, SBOM, SLSA, Sigstore, secrets scanning, mutation testing, SDD, GSD workflow, Spec Kit, business agent, deep research agent, web scraper, xlsx, docx, pptx, pdf, MCP, Model Context Protocol, provider agnostic, free AI coding agent, free models, OpenCode Zen, OpenRouter, OpenAI, Anthropic, Claude, Gemini, NVIDIA, Ollama, OpenCode alternative, Claude Code alternative, Cursor alternative, Copilot alternative, Node.js, Bun, TypeScript, Windows, macOS, Linux, latest release.</sub>
|
package/bin/codo.js
ADDED
|
@@ -0,0 +1,307 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const childProcess = require("child_process")
|
|
4
|
+
const fs = require("fs")
|
|
5
|
+
const path = require("path")
|
|
6
|
+
const os = require("os")
|
|
7
|
+
|
|
8
|
+
const forwardedSignals = ["SIGINT", "SIGTERM", "SIGHUP"]
|
|
9
|
+
|
|
10
|
+
function run(target) {
|
|
11
|
+
const child = childProcess.spawn(target, process.argv.slice(2), {
|
|
12
|
+
stdio: "inherit",
|
|
13
|
+
})
|
|
14
|
+
|
|
15
|
+
child.on("error", (error) => {
|
|
16
|
+
console.error(error.message)
|
|
17
|
+
process.exit(1)
|
|
18
|
+
})
|
|
19
|
+
|
|
20
|
+
const forwarders = {}
|
|
21
|
+
for (const signal of forwardedSignals) {
|
|
22
|
+
forwarders[signal] = () => {
|
|
23
|
+
try {
|
|
24
|
+
child.kill(signal)
|
|
25
|
+
} catch {
|
|
26
|
+
// The child may have already exited.
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
process.on(signal, forwarders[signal])
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
child.on("exit", (code, signal) => {
|
|
33
|
+
for (const forwardedSignal of forwardedSignals) {
|
|
34
|
+
process.removeListener(forwardedSignal, forwarders[forwardedSignal])
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
if (signal) {
|
|
38
|
+
process.kill(process.pid, signal)
|
|
39
|
+
return
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
process.exit(typeof code === "number" ? code : 0)
|
|
43
|
+
})
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const envPath = process.env.CODO_BIN_PATH
|
|
47
|
+
|
|
48
|
+
const scriptPath = fs.realpathSync(__filename)
|
|
49
|
+
const scriptDir = path.dirname(scriptPath)
|
|
50
|
+
|
|
51
|
+
const platformMap = {
|
|
52
|
+
darwin: "darwin",
|
|
53
|
+
linux: "linux",
|
|
54
|
+
win32: "windows",
|
|
55
|
+
}
|
|
56
|
+
const archMap = {
|
|
57
|
+
x64: "x64",
|
|
58
|
+
arm64: "arm64",
|
|
59
|
+
arm: "arm",
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
let platform = platformMap[os.platform()]
|
|
63
|
+
if (!platform) {
|
|
64
|
+
platform = os.platform()
|
|
65
|
+
}
|
|
66
|
+
let arch = archMap[os.arch()]
|
|
67
|
+
if (!arch) {
|
|
68
|
+
arch = os.arch()
|
|
69
|
+
}
|
|
70
|
+
const base = "codo-" + platform + "-" + arch
|
|
71
|
+
const binary = platform === "windows" ? "codo.exe" : "codo"
|
|
72
|
+
|
|
73
|
+
function supportsAvx2() {
|
|
74
|
+
if (arch !== "x64") return false
|
|
75
|
+
|
|
76
|
+
if (platform === "linux") {
|
|
77
|
+
try {
|
|
78
|
+
return /(^|\s)avx2(\s|$)/i.test(fs.readFileSync("/proc/cpuinfo", "utf8"))
|
|
79
|
+
} catch {
|
|
80
|
+
return false
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
if (platform === "darwin") {
|
|
85
|
+
try {
|
|
86
|
+
const result = childProcess.spawnSync("sysctl", ["-n", "hw.optional.avx2_0"], {
|
|
87
|
+
encoding: "utf8",
|
|
88
|
+
timeout: 1500,
|
|
89
|
+
})
|
|
90
|
+
if (result.status !== 0) return false
|
|
91
|
+
return (result.stdout || "").trim() === "1"
|
|
92
|
+
} catch {
|
|
93
|
+
return false
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
if (platform === "windows") {
|
|
98
|
+
const cmd =
|
|
99
|
+
'(Add-Type -MemberDefinition "[DllImport(""kernel32.dll"")] public static extern bool IsProcessorFeaturePresent(int ProcessorFeature);" -Name Kernel32 -Namespace Win32 -PassThru)::IsProcessorFeaturePresent(40)'
|
|
100
|
+
|
|
101
|
+
for (const exe of ["powershell.exe", "pwsh.exe", "pwsh", "powershell"]) {
|
|
102
|
+
try {
|
|
103
|
+
const result = childProcess.spawnSync(exe, ["-NoProfile", "-NonInteractive", "-Command", cmd], {
|
|
104
|
+
encoding: "utf8",
|
|
105
|
+
timeout: 3000,
|
|
106
|
+
windowsHide: true,
|
|
107
|
+
})
|
|
108
|
+
if (result.status !== 0) continue
|
|
109
|
+
const out = (result.stdout || "").trim().toLowerCase()
|
|
110
|
+
if (out === "true" || out === "1") return true
|
|
111
|
+
if (out === "false" || out === "0") return false
|
|
112
|
+
} catch {
|
|
113
|
+
continue
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
return false
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
return false
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const names = (() => {
|
|
124
|
+
const avx2 = supportsAvx2()
|
|
125
|
+
const baseline = arch === "x64" && !avx2
|
|
126
|
+
|
|
127
|
+
if (platform === "linux") {
|
|
128
|
+
const musl = (() => {
|
|
129
|
+
try {
|
|
130
|
+
if (fs.existsSync("/etc/alpine-release")) return true
|
|
131
|
+
} catch {
|
|
132
|
+
// ignore
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
try {
|
|
136
|
+
const result = childProcess.spawnSync("ldd", ["--version"], { encoding: "utf8" })
|
|
137
|
+
const text = ((result.stdout || "") + (result.stderr || "")).toLowerCase()
|
|
138
|
+
if (text.includes("musl")) return true
|
|
139
|
+
} catch {
|
|
140
|
+
// ignore
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
return false
|
|
144
|
+
})()
|
|
145
|
+
|
|
146
|
+
if (musl) {
|
|
147
|
+
if (arch === "x64") {
|
|
148
|
+
if (baseline) return [`${base}-baseline-musl`, `${base}-musl`, `${base}-baseline`, base]
|
|
149
|
+
return [`${base}-musl`, `${base}-baseline-musl`, base, `${base}-baseline`]
|
|
150
|
+
}
|
|
151
|
+
return [`${base}-musl`, base]
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
if (arch === "x64") {
|
|
155
|
+
if (baseline) return [`${base}-baseline`, base, `${base}-baseline-musl`, `${base}-musl`]
|
|
156
|
+
return [base, `${base}-baseline`, `${base}-musl`, `${base}-baseline-musl`]
|
|
157
|
+
}
|
|
158
|
+
return [base, `${base}-musl`]
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
if (arch === "x64") {
|
|
162
|
+
if (baseline) return [`${base}-baseline`, base]
|
|
163
|
+
return [base, `${base}-baseline`]
|
|
164
|
+
}
|
|
165
|
+
return [base]
|
|
166
|
+
})()
|
|
167
|
+
|
|
168
|
+
// Published platform packages name the binary with a version suffix
|
|
169
|
+
// ("codo-2.23.12" / "codo-2.23.12.exe"), so resolve by pattern rather than
|
|
170
|
+
// by a single hardcoded filename. Prefer an exact (unsuffixed) match so a
|
|
171
|
+
// binary staged by postinstall wins over a stale suffixed one.
|
|
172
|
+
function findBinaryIn(packageDir) {
|
|
173
|
+
let entries
|
|
174
|
+
try {
|
|
175
|
+
entries = fs.readdirSync(path.join(packageDir, "bin"))
|
|
176
|
+
} catch {
|
|
177
|
+
return undefined
|
|
178
|
+
}
|
|
179
|
+
if (entries.includes(binary)) return path.join(packageDir, "bin", binary)
|
|
180
|
+
const suffix = platform === "windows" ? ".exe" : ""
|
|
181
|
+
const match = entries
|
|
182
|
+
.filter((entry) => entry.startsWith("codo-") && (suffix === "" ? !entry.endsWith(".exe") : entry.endsWith(suffix)))
|
|
183
|
+
.sort()
|
|
184
|
+
.at(-1)
|
|
185
|
+
if (!match) return undefined
|
|
186
|
+
return path.join(packageDir, "bin", match)
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
function findBinary(startDir) {
|
|
190
|
+
let current = startDir
|
|
191
|
+
for (;;) {
|
|
192
|
+
const modules = path.join(current, "node_modules")
|
|
193
|
+
if (fs.existsSync(modules)) {
|
|
194
|
+
for (const name of names) {
|
|
195
|
+
const candidate = findBinaryIn(path.join(modules, `@codo-ai/${name}`))
|
|
196
|
+
if (candidate && fs.existsSync(candidate)) return candidate
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
const parent = path.dirname(current)
|
|
200
|
+
if (parent === current) {
|
|
201
|
+
return
|
|
202
|
+
}
|
|
203
|
+
current = parent
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// A fetched binary is staged next to this launcher so it can be found
|
|
208
|
+
// without walking node_modules on later runs.
|
|
209
|
+
const stagedName = platform === "windows" ? "codo.exe" : "codo"
|
|
210
|
+
const staged = path.join(scriptDir, stagedName)
|
|
211
|
+
const stagedBinary = (() => {
|
|
212
|
+
try {
|
|
213
|
+
if (fs.existsSync(staged)) return staged
|
|
214
|
+
} catch {
|
|
215
|
+
// ignore
|
|
216
|
+
}
|
|
217
|
+
return undefined
|
|
218
|
+
})()
|
|
219
|
+
|
|
220
|
+
const wrapperPkg = (() => {
|
|
221
|
+
try {
|
|
222
|
+
return JSON.parse(fs.readFileSync(path.join(scriptDir, "..", "package.json"), "utf8"))
|
|
223
|
+
} catch {
|
|
224
|
+
return {}
|
|
225
|
+
}
|
|
226
|
+
})()
|
|
227
|
+
|
|
228
|
+
// Last resort: download the platform package on demand with npm and stage
|
|
229
|
+
// its binary next to this launcher. Runs only when the package manager
|
|
230
|
+
// skipped the optional platform packages at install time.
|
|
231
|
+
function fetchBinary() {
|
|
232
|
+
const deps = wrapperPkg.optionalDependencies || {}
|
|
233
|
+
for (const name of names) {
|
|
234
|
+
const scoped = `@codo-ai/${name}`
|
|
235
|
+
const version = deps[scoped]
|
|
236
|
+
if (!version) continue
|
|
237
|
+
let tmp
|
|
238
|
+
try {
|
|
239
|
+
tmp = fs.mkdtempSync(path.join(os.tmpdir(), "codo-bin-"))
|
|
240
|
+
} catch {
|
|
241
|
+
continue
|
|
242
|
+
}
|
|
243
|
+
try {
|
|
244
|
+
// `npm` is a shell script on Windows, so plain spawn fails with
|
|
245
|
+
// ENOENT. Prefer node + npm-cli.js (no shell, no quoting hazards);
|
|
246
|
+
// fall back to a shell spawn only when the CLI file is missing.
|
|
247
|
+
let cmd = "npm"
|
|
248
|
+
const args = ["install", "--ignore-scripts", "--no-save", "--loglevel=error", "--prefix", tmp, `${scoped}@${version}`]
|
|
249
|
+
const opts = { encoding: "utf8", timeout: 300000, windowsHide: true }
|
|
250
|
+
if (process.platform === "win32") {
|
|
251
|
+
const cli = path.join(path.dirname(process.execPath), "node_modules", "npm", "bin", "npm-cli.js")
|
|
252
|
+
let cliExists = false
|
|
253
|
+
try {
|
|
254
|
+
cliExists = fs.existsSync(cli)
|
|
255
|
+
} catch {
|
|
256
|
+
cliExists = false
|
|
257
|
+
}
|
|
258
|
+
if (cliExists) {
|
|
259
|
+
cmd = process.execPath
|
|
260
|
+
args.unshift(cli)
|
|
261
|
+
} else {
|
|
262
|
+
opts.shell = true
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
const result = childProcess.spawnSync(cmd, args, opts)
|
|
266
|
+
if (result.status !== 0) continue
|
|
267
|
+
const found = findBinaryIn(path.join(tmp, "node_modules", scoped))
|
|
268
|
+
if (!found) continue
|
|
269
|
+
try {
|
|
270
|
+
if (!fs.existsSync(found)) continue
|
|
271
|
+
} catch {
|
|
272
|
+
continue
|
|
273
|
+
}
|
|
274
|
+
try {
|
|
275
|
+
fs.copyFileSync(found, staged)
|
|
276
|
+
try {
|
|
277
|
+
fs.chmodSync(staged, 0o755)
|
|
278
|
+
} catch {
|
|
279
|
+
// chmod is best-effort on Windows.
|
|
280
|
+
}
|
|
281
|
+
return staged
|
|
282
|
+
} catch {
|
|
283
|
+
return found
|
|
284
|
+
}
|
|
285
|
+
} catch {
|
|
286
|
+
continue
|
|
287
|
+
} finally {
|
|
288
|
+
try {
|
|
289
|
+
fs.rmSync(tmp, { recursive: true, force: true })
|
|
290
|
+
} catch {
|
|
291
|
+
// ignore
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
return undefined
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
const resolved = envPath || stagedBinary || findBinary(scriptDir) || fetchBinary()
|
|
299
|
+
if (!resolved) {
|
|
300
|
+
console.error(
|
|
301
|
+
"Could not find the codo native binary. Your package manager may have skipped the optional platform packages. Try installing manually: " +
|
|
302
|
+
names.map((n) => `"@codo-ai/${n}"`).join(" or "),
|
|
303
|
+
)
|
|
304
|
+
process.exit(1)
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
run(resolved)
|
package/package.json
CHANGED
|
@@ -1,23 +1,99 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "codo-ai",
|
|
3
|
-
"description": "
|
|
3
|
+
"description": "Spec-driven software development with security-guided multi-agent orchestration. Terminal AI coding agent with a Sensei second opinion, a Checker, six Sec-Test security personas, a Business Agent for xlsx/docx/pptx/pdf deliverables, and free-model support.",
|
|
4
4
|
"keywords": [
|
|
5
5
|
"ai",
|
|
6
|
+
"ai-agent",
|
|
7
|
+
"ai-coding-agent",
|
|
6
8
|
"coding-agent",
|
|
7
|
-
"
|
|
9
|
+
"coding-assistant",
|
|
10
|
+
"agentic",
|
|
11
|
+
"agentic-coding",
|
|
12
|
+
"autonomous-agent",
|
|
13
|
+
"multi-agent",
|
|
14
|
+
"multi-agent-orchestration",
|
|
15
|
+
"orchestration",
|
|
16
|
+
"subagents",
|
|
17
|
+
"sensei",
|
|
18
|
+
"second-opinion",
|
|
19
|
+
"judge",
|
|
20
|
+
"checker",
|
|
21
|
+
"business-agent",
|
|
22
|
+
"spec-driven",
|
|
23
|
+
"spec-driven-development",
|
|
24
|
+
"sdd",
|
|
25
|
+
"gsd",
|
|
26
|
+
"speckit",
|
|
27
|
+
"workflow",
|
|
8
28
|
"terminal",
|
|
29
|
+
"terminal-ui",
|
|
9
30
|
"tui",
|
|
31
|
+
"cli",
|
|
10
32
|
"llm",
|
|
33
|
+
"code-generation",
|
|
34
|
+
"code-review",
|
|
35
|
+
"pair-programming",
|
|
36
|
+
"developer-tools",
|
|
37
|
+
"devtools",
|
|
38
|
+
"devtool",
|
|
11
39
|
"security",
|
|
12
|
-
"
|
|
40
|
+
"security-scanner",
|
|
41
|
+
"security-audit",
|
|
13
42
|
"appsec",
|
|
14
43
|
"devsecops",
|
|
44
|
+
"sast",
|
|
45
|
+
"dast",
|
|
15
46
|
"threat-modeling",
|
|
16
47
|
"pentest",
|
|
17
|
-
"
|
|
18
|
-
"
|
|
19
|
-
"
|
|
48
|
+
"penetration-testing",
|
|
49
|
+
"vulnerability-scanner",
|
|
50
|
+
"owasp",
|
|
51
|
+
"cwe",
|
|
52
|
+
"sbom",
|
|
53
|
+
"slsa",
|
|
54
|
+
"sigstore",
|
|
55
|
+
"secrets-scanning",
|
|
56
|
+
"compliance",
|
|
57
|
+
"quality-assurance",
|
|
58
|
+
"testing",
|
|
59
|
+
"mutation-testing",
|
|
60
|
+
"research",
|
|
61
|
+
"deep-research",
|
|
62
|
+
"scraper",
|
|
63
|
+
"web-scraping",
|
|
64
|
+
"xlsx",
|
|
65
|
+
"docx",
|
|
66
|
+
"pptx",
|
|
67
|
+
"pdf",
|
|
68
|
+
"mcp",
|
|
69
|
+
"model-context-protocol",
|
|
70
|
+
"provider-agnostic",
|
|
71
|
+
"free-models",
|
|
72
|
+
"free-ai",
|
|
73
|
+
"opencode-zen",
|
|
74
|
+
"openrouter",
|
|
75
|
+
"openai",
|
|
76
|
+
"anthropic",
|
|
77
|
+
"claude",
|
|
78
|
+
"gemini",
|
|
79
|
+
"nvidia",
|
|
80
|
+
"ollama",
|
|
81
|
+
"opencode",
|
|
82
|
+
"opencode-alternative",
|
|
83
|
+
"claude-code-alternative",
|
|
84
|
+
"cursor-alternative",
|
|
85
|
+
"copilot-alternative",
|
|
86
|
+
"nodejs",
|
|
87
|
+
"bun",
|
|
88
|
+
"typescript",
|
|
89
|
+
"windows",
|
|
90
|
+
"macos",
|
|
91
|
+
"linux",
|
|
92
|
+
"latest"
|
|
20
93
|
],
|
|
94
|
+
"engines": {
|
|
95
|
+
"node": ">=18"
|
|
96
|
+
},
|
|
21
97
|
"homepage": "https://codo-ai.vercel.app/",
|
|
22
98
|
"repository": {
|
|
23
99
|
"type": "git",
|
|
@@ -27,12 +103,9 @@
|
|
|
27
103
|
"url": "https://github.com/Mosalah4351/COdo/issues"
|
|
28
104
|
},
|
|
29
105
|
"bin": {
|
|
30
|
-
"codo": "./bin/codo"
|
|
31
|
-
},
|
|
32
|
-
"scripts": {
|
|
33
|
-
"postinstall": "node ./postinstall.mjs"
|
|
106
|
+
"codo": "./bin/codo.js"
|
|
34
107
|
},
|
|
35
|
-
"version": "2.23.
|
|
108
|
+
"version": "2.23.15",
|
|
36
109
|
"license": "MIT",
|
|
37
110
|
"os": [
|
|
38
111
|
"darwin",
|
|
@@ -44,17 +117,17 @@
|
|
|
44
117
|
"x64"
|
|
45
118
|
],
|
|
46
119
|
"optionalDependencies": {
|
|
47
|
-
"@codo-ai/codo-windows-x64-baseline": "2.23.
|
|
48
|
-
"@codo-ai/codo-windows-x64": "2.23.
|
|
49
|
-
"@codo-ai/codo-windows-arm64": "2.23.
|
|
50
|
-
"@codo-ai/codo-linux-x64-musl": "2.23.
|
|
51
|
-
"@codo-ai/codo-linux-x64-baseline-musl": "2.23.
|
|
52
|
-
"@codo-ai/codo-linux-x64-baseline": "2.23.
|
|
53
|
-
"@codo-ai/codo-linux-x64": "2.23.
|
|
54
|
-
"@codo-ai/codo-linux-arm64-musl": "2.23.
|
|
55
|
-
"@codo-ai/codo-linux-arm64": "2.23.
|
|
56
|
-
"@codo-ai/codo-darwin-x64-baseline": "2.23.
|
|
57
|
-
"@codo-ai/codo-darwin-x64": "2.23.
|
|
58
|
-
"@codo-ai/codo-darwin-arm64": "2.23.
|
|
120
|
+
"@codo-ai/codo-windows-x64-baseline": "2.23.15",
|
|
121
|
+
"@codo-ai/codo-windows-x64": "2.23.15",
|
|
122
|
+
"@codo-ai/codo-windows-arm64": "2.23.15",
|
|
123
|
+
"@codo-ai/codo-linux-x64-musl": "2.23.15",
|
|
124
|
+
"@codo-ai/codo-linux-x64-baseline-musl": "2.23.15",
|
|
125
|
+
"@codo-ai/codo-linux-x64-baseline": "2.23.15",
|
|
126
|
+
"@codo-ai/codo-linux-x64": "2.23.15",
|
|
127
|
+
"@codo-ai/codo-linux-arm64-musl": "2.23.15",
|
|
128
|
+
"@codo-ai/codo-linux-arm64": "2.23.15",
|
|
129
|
+
"@codo-ai/codo-darwin-x64-baseline": "2.23.15",
|
|
130
|
+
"@codo-ai/codo-darwin-x64": "2.23.15",
|
|
131
|
+
"@codo-ai/codo-darwin-arm64": "2.23.15"
|
|
59
132
|
}
|
|
60
133
|
}
|
package/bin/codo
DELETED
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
echo "Error: codo-ai's postinstall script was not run." >&2
|
|
2
|
-
echo "" >&2
|
|
3
|
-
echo "This occurs when using --ignore-scripts during installation, or when using a" >&2
|
|
4
|
-
echo "package manager like pnpm that does not run postinstall scripts by default." >&2
|
|
5
|
-
echo "" >&2
|
|
6
|
-
echo "To fix this, run the postinstall script manually:" >&2
|
|
7
|
-
echo " cd node_modules/codo-ai && node postinstall.mjs" >&2
|
|
8
|
-
echo "" >&2
|
|
9
|
-
echo "Or reinstall codo-ai without the --ignore-scripts flag." >&2
|
|
10
|
-
exit 1
|
package/postinstall.mjs
DELETED
|
@@ -1,222 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
import childProcess from "child_process"
|
|
4
|
-
import fs from "fs"
|
|
5
|
-
import os from "os"
|
|
6
|
-
import path from "path"
|
|
7
|
-
import { createRequire } from "module"
|
|
8
|
-
import { fileURLToPath } from "url"
|
|
9
|
-
|
|
10
|
-
const __dirname = path.dirname(fileURLToPath(import.meta.url))
|
|
11
|
-
const require = createRequire(import.meta.url)
|
|
12
|
-
const packageJson = JSON.parse(fs.readFileSync(path.join(__dirname, "package.json"), "utf8"))
|
|
13
|
-
|
|
14
|
-
const platformMap = {
|
|
15
|
-
darwin: "darwin",
|
|
16
|
-
linux: "linux",
|
|
17
|
-
win32: "windows",
|
|
18
|
-
}
|
|
19
|
-
const archMap = {
|
|
20
|
-
x64: "x64",
|
|
21
|
-
arm64: "arm64",
|
|
22
|
-
arm: "arm",
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
const platform = platformMap[os.platform()] ?? os.platform()
|
|
26
|
-
const arch = archMap[os.arch()] ?? os.arch()
|
|
27
|
-
const base = `codo-${platform}-${arch}`
|
|
28
|
-
const sourceBinary = platform === "windows" ? "codo.exe" : "codo"
|
|
29
|
-
const targetBinary = path.join(__dirname, "bin", "codo.exe")
|
|
30
|
-
|
|
31
|
-
function supportsAvx2() {
|
|
32
|
-
if (arch !== "x64") return false
|
|
33
|
-
|
|
34
|
-
if (platform === "linux") {
|
|
35
|
-
try {
|
|
36
|
-
return /(^|\s)avx2(\s|$)/i.test(fs.readFileSync("/proc/cpuinfo", "utf8"))
|
|
37
|
-
} catch {
|
|
38
|
-
return false
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
if (platform === "darwin") {
|
|
43
|
-
try {
|
|
44
|
-
const result = childProcess.spawnSync("sysctl", ["-n", "hw.optional.avx2_0"], {
|
|
45
|
-
encoding: "utf8",
|
|
46
|
-
timeout: 1500,
|
|
47
|
-
})
|
|
48
|
-
if (result.status !== 0) return false
|
|
49
|
-
return (result.stdout || "").trim() === "1"
|
|
50
|
-
} catch {
|
|
51
|
-
return false
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
if (platform === "windows") {
|
|
56
|
-
const command =
|
|
57
|
-
'(Add-Type -MemberDefinition "[DllImport(""kernel32.dll"")] public static extern bool IsProcessorFeaturePresent(int ProcessorFeature);" -Name Kernel32 -Namespace Win32 -PassThru)::IsProcessorFeaturePresent(40)'
|
|
58
|
-
|
|
59
|
-
for (const executable of ["powershell.exe", "pwsh.exe", "pwsh", "powershell"]) {
|
|
60
|
-
try {
|
|
61
|
-
const result = childProcess.spawnSync(executable, ["-NoProfile", "-NonInteractive", "-Command", command], {
|
|
62
|
-
encoding: "utf8",
|
|
63
|
-
timeout: 3000,
|
|
64
|
-
windowsHide: true,
|
|
65
|
-
})
|
|
66
|
-
if (result.status !== 0) continue
|
|
67
|
-
const output = (result.stdout || "").trim().toLowerCase()
|
|
68
|
-
if (output === "true" || output === "1") return true
|
|
69
|
-
if (output === "false" || output === "0") return false
|
|
70
|
-
} catch {
|
|
71
|
-
continue
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
return false
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
function isMusl() {
|
|
80
|
-
if (platform !== "linux") return false
|
|
81
|
-
|
|
82
|
-
try {
|
|
83
|
-
if (fs.existsSync("/etc/alpine-release")) return true
|
|
84
|
-
} catch {
|
|
85
|
-
// Ignore filesystem probes that are blocked by the host.
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
try {
|
|
89
|
-
const result = childProcess.spawnSync("ldd", ["--version"], { encoding: "utf8" })
|
|
90
|
-
return `${result.stdout || ""}${result.stderr || ""}`.toLowerCase().includes("musl")
|
|
91
|
-
} catch {
|
|
92
|
-
return false
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
function packageNames() {
|
|
97
|
-
const baseline = arch === "x64" && !supportsAvx2()
|
|
98
|
-
|
|
99
|
-
if (platform === "linux") {
|
|
100
|
-
if (isMusl()) {
|
|
101
|
-
if (arch === "x64")
|
|
102
|
-
return baseline
|
|
103
|
-
? [`${base}-baseline-musl`, `${base}-musl`, `${base}-baseline`, base]
|
|
104
|
-
: [`${base}-musl`, `${base}-baseline-musl`, base, `${base}-baseline`]
|
|
105
|
-
return [`${base}-musl`, base]
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
if (arch === "x64")
|
|
109
|
-
return baseline
|
|
110
|
-
? [`${base}-baseline`, base, `${base}-baseline-musl`, `${base}-musl`]
|
|
111
|
-
: [base, `${base}-baseline`, `${base}-musl`, `${base}-baseline-musl`]
|
|
112
|
-
return [base, `${base}-musl`]
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
if (arch === "x64") return baseline ? [`${base}-baseline`, base] : [base, `${base}-baseline`]
|
|
116
|
-
return [base]
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
// Published packages may name the binary exactly ("codo.exe" / "codo") or with
|
|
120
|
-
// a version suffix ("codo-2.23.5.exe"), so resolve by pattern rather than by
|
|
121
|
-
// a single hardcoded filename.
|
|
122
|
-
function findBinary(dir) {
|
|
123
|
-
const entries = fs.readdirSync(path.join(dir, "bin"))
|
|
124
|
-
const match =
|
|
125
|
-
entries.find((entry) => entry === sourceBinary) ??
|
|
126
|
-
(platform === "windows"
|
|
127
|
-
? entries.find((entry) => entry.startsWith("codo") && entry.endsWith(".exe"))
|
|
128
|
-
: undefined) ??
|
|
129
|
-
entries.find((entry) => entry.startsWith("codo"))
|
|
130
|
-
if (!match) throw new Error(`Binary not found in ${path.join(dir, "bin")}`)
|
|
131
|
-
return path.join(dir, "bin", match)
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
// Platform binaries live in scoped packages (@codo-ai/codo-<platform>-<arch>),
|
|
135
|
-
// while packageNames() works with the unscoped suffix.
|
|
136
|
-
const scope = "@codo-ai/"
|
|
137
|
-
|
|
138
|
-
function resolveBinary(name) {
|
|
139
|
-
return findBinary(path.dirname(require.resolve(`${scope}${name}/package.json`)))
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
function installPackage(name) {
|
|
143
|
-
const version = packageJson.optionalDependencies?.[`${scope}${name}`]
|
|
144
|
-
if (!version) return
|
|
145
|
-
|
|
146
|
-
const temp = fs.mkdtempSync(path.join(os.tmpdir(), "codo-install-"))
|
|
147
|
-
try {
|
|
148
|
-
const result = childProcess.spawnSync(
|
|
149
|
-
"npm",
|
|
150
|
-
["install", "--ignore-scripts", "--no-save", "--loglevel=error", "--prefix", temp, `${scope}${name}@${version}`],
|
|
151
|
-
{ stdio: "inherit", windowsHide: true },
|
|
152
|
-
)
|
|
153
|
-
if (result.status !== 0) return
|
|
154
|
-
const packageDir = path.join(temp, "node_modules", scope, name)
|
|
155
|
-
copyBinary(findBinary(packageDir), targetBinary)
|
|
156
|
-
return true
|
|
157
|
-
} finally {
|
|
158
|
-
fs.rmSync(temp, { recursive: true, force: true })
|
|
159
|
-
}
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
function copyBinary(source, target) {
|
|
163
|
-
if (!fs.existsSync(source)) throw new Error(`Binary not found at ${source}`)
|
|
164
|
-
fs.mkdirSync(path.dirname(target), { recursive: true })
|
|
165
|
-
if (fs.existsSync(target)) fs.unlinkSync(target)
|
|
166
|
-
try {
|
|
167
|
-
fs.linkSync(source, target)
|
|
168
|
-
} catch {
|
|
169
|
-
fs.copyFileSync(source, target)
|
|
170
|
-
}
|
|
171
|
-
fs.chmodSync(target, 0o755)
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
function verifyBinary(target = targetBinary) {
|
|
175
|
-
try {
|
|
176
|
-
const result = childProcess.spawnSync(target, ["--version"], {
|
|
177
|
-
encoding: "utf8",
|
|
178
|
-
stdio: "ignore",
|
|
179
|
-
windowsHide: true,
|
|
180
|
-
})
|
|
181
|
-
return result.status === 0
|
|
182
|
-
} catch {
|
|
183
|
-
return false
|
|
184
|
-
}
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
function main() {
|
|
188
|
-
// Fast path: npm installs optionalDependencies by default, so the platform
|
|
189
|
-
// binary is usually already on disk. The launcher resolves it at runtime,
|
|
190
|
-
// so there is nothing to copy — just validate it actually runs.
|
|
191
|
-
for (const name of packageNames()) {
|
|
192
|
-
try {
|
|
193
|
-
if (verifyBinary(resolveBinary(name))) return
|
|
194
|
-
} catch {
|
|
195
|
-
// Package not installed; try the next candidate, then the fallback below.
|
|
196
|
-
}
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
// Fallback: the platform package is missing (isolated pnpm layouts,
|
|
200
|
-
// --no-optional installs). Fetch it into a temp dir and stage the binary
|
|
201
|
-
// next to the launcher, where the launcher looks for it.
|
|
202
|
-
for (const name of packageNames()) {
|
|
203
|
-
try {
|
|
204
|
-
if (installPackage(name) && verifyBinary()) return
|
|
205
|
-
} catch {
|
|
206
|
-
// Try the next candidate.
|
|
207
|
-
}
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
throw new Error(
|
|
211
|
-
`It seems your package manager failed to install the right codo CLI package. Try manually installing ${packageNames()
|
|
212
|
-
.map((name) => JSON.stringify(`@codo-ai/${name}`))
|
|
213
|
-
.join(" or ")}.`,
|
|
214
|
-
)
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
try {
|
|
218
|
-
main()
|
|
219
|
-
} catch (error) {
|
|
220
|
-
console.error(error.message)
|
|
221
|
-
process.exit(1)
|
|
222
|
-
}
|