antigravity-mission-control 0.1.0-alpha.1
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/LICENSE +21 -0
- package/NOTICE +5 -0
- package/README.md +123 -0
- package/README.zh-CN.md +123 -0
- package/assets/logo/mission-control.svg +28 -0
- package/assets/terminal-preview.svg +23 -0
- package/npm/cli.mjs +262 -0
- package/package.json +44 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 YuxiaoMa66
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/NOTICE
ADDED
package/README.md
ADDED
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
<p align="center"><img src="assets/logo/mission-control.svg" width="720" alt="Antigravity Mission Control"></p>
|
|
2
|
+
|
|
3
|
+
<p align="center"><a href="README.md">English</a> · <a href="README.zh-CN.md">简体中文</a></p>
|
|
4
|
+
|
|
5
|
+
<p align="center">
|
|
6
|
+
<img src="https://img.shields.io/badge/Python-3.10%2B-7c3aed" alt="Python 3.10+">
|
|
7
|
+
<img src="https://img.shields.io/badge/Node.js-18%2B-0891b2" alt="Node.js 18+">
|
|
8
|
+
<img src="https://img.shields.io/badge/AGY-tested_1.1.19-22d3ee" alt="AGY tested 1.1.19">
|
|
9
|
+
<a href="LICENSE"><img src="https://img.shields.io/badge/license-MIT-a78bfa" alt="MIT License"></a>
|
|
10
|
+
</p>
|
|
11
|
+
|
|
12
|
+
Policy-aware orchestration for the Antigravity CLI (`agy`): route work to exact models, bind approvals to the task, serialize editing workers, verify cancellation, and watch live quota—while Codex remains responsible for acceptance.
|
|
13
|
+
|
|
14
|
+
<p align="center"><img src="assets/terminal-preview.svg" width="920" alt="Mission Control terminal preview"></p>
|
|
15
|
+
|
|
16
|
+
> **Alpha:** ready for public pre-release evaluation, not a stable security boundary. Independent community project; not affiliated with Google or Antigravity.
|
|
17
|
+
|
|
18
|
+
## Install
|
|
19
|
+
|
|
20
|
+
Install AGY first and verify `agy --version`. Then use the npm bootstrapper:
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
npx antigravity-mission-control install
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
It shows every target before writing, creates a private managed Python environment, installs `agy-mc`, deploys the Codex Skill, and runs without shell interpolation. For CI or agents, add `--yes`; inspect first with `--dry-run`.
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
npx antigravity-mission-control install --dry-run
|
|
30
|
+
npx antigravity-mission-control install --yes
|
|
31
|
+
npx antigravity-mission-control status
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
Direct Python installation is also supported:
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
python3 -m pip install "git+https://github.com/YuxiaoMa66/antigravity-mission-control.git@v0.1.0a1"
|
|
38
|
+
agy-mc skill install
|
|
39
|
+
agy-mc doctor
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
Full setup, upgrade, uninstall, local-source and PATH notes: [Installation guide](docs/INSTALL.md).
|
|
43
|
+
|
|
44
|
+
## What Mission Control adds
|
|
45
|
+
|
|
46
|
+
| Layer | Responsibility |
|
|
47
|
+
|---|---|
|
|
48
|
+
| Codex Skill | Roster choice, scope, permission boundaries, independent acceptance |
|
|
49
|
+
| `agy-mc` core | Model discovery, signed approvals, AGY transport, jobs, locks, evidence, quota |
|
|
50
|
+
| npm bootstrap | Managed Python environment, Skill deployment, update and recoverable uninstall |
|
|
51
|
+
| AGY | Executes the exact bounded worker assignment |
|
|
52
|
+
|
|
53
|
+
The npm layer is deliberately thin. The Python companion is the single behavioral implementation, so npm and direct Python installs cannot drift into different orchestration rules.
|
|
54
|
+
|
|
55
|
+
## Live quota
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
agy-mc usage
|
|
59
|
+
agy-mc usage --watch --interval 60
|
|
60
|
+
agy-mc usage --format json
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
The normalized `agy-mc-usage.v1` output includes model groups, quota windows, remaining percentage, reset time and disabled state. Missing data stays `unknown`; it is never rewritten as `0%`. Raw provider payloads, OAuth material and account identity are excluded.
|
|
64
|
+
|
|
65
|
+
## Bound approvals
|
|
66
|
+
|
|
67
|
+
After the user approves the exact roster and permission profile, create a short-lived manifest:
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
agy-mc approve \
|
|
71
|
+
--strategy A --role implementer --model gemini-3.7-flash-high \
|
|
72
|
+
--cwd /absolute/project --prompt-file /private/prompt.txt \
|
|
73
|
+
--mode accept-edits --expires-minutes 60 --confirmed
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
Pass the returned file to `run`. The machine-local HMAC binds strategy, role, model, canonical workspace, prompt hash, mode, permission profile, conversation and expiration. Changing any bound field invalidates the run.
|
|
77
|
+
|
|
78
|
+
```bash
|
|
79
|
+
agy-mc run \
|
|
80
|
+
--strategy A --role implementer --model gemini-3.7-flash-high \
|
|
81
|
+
--cwd /absolute/project --prompt-file /private/prompt.txt \
|
|
82
|
+
--mode accept-edits --approval-file ~/.local/state/antigravity-mission-control/approvals/<id>.json
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
Legacy boolean approval flags remain for migration in this Alpha and are deprecated.
|
|
86
|
+
|
|
87
|
+
## Background jobs
|
|
88
|
+
|
|
89
|
+
Add `--background`, then use:
|
|
90
|
+
|
|
91
|
+
```bash
|
|
92
|
+
agy-mc status [job-id]
|
|
93
|
+
agy-mc wait <job-id> --timeout 10m
|
|
94
|
+
agy-mc result <job-id>
|
|
95
|
+
agy-mc cancel <job-id>
|
|
96
|
+
agy-mc continue <job-id> --prompt-file /private/follow-up.txt
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
Editing jobs use an OS-level non-blocking lock per canonical workspace. `cancel` records `canceling`, waits after TERM, escalates to KILL if required, and reports `canceled` only after process exit is confirmed.
|
|
100
|
+
|
|
101
|
+
## Design principles
|
|
102
|
+
|
|
103
|
+
- Exact models are discovered from the current AGY session; no remembered slug is treated as truth.
|
|
104
|
+
- Workspace trust and unrestricted execution are separate user-approved mutations.
|
|
105
|
+
- Worker success is not task acceptance. Codex checks the real diff, diagnostics and tests.
|
|
106
|
+
- Prompts travel over `stream-json` stdin, never in process arguments.
|
|
107
|
+
- State directories are `0700`; prompts, manifests, locks and results are `0600`.
|
|
108
|
+
- Installation and uninstall preserve recoverable backups.
|
|
109
|
+
|
|
110
|
+
See [Reference](docs/REFERENCE.md), [Security](SECURITY.md), [Contributing](CONTRIBUTING.md), and [Release process](docs/RELEASING.md).
|
|
111
|
+
|
|
112
|
+
## Validate
|
|
113
|
+
|
|
114
|
+
```bash
|
|
115
|
+
python3 -m unittest discover -s tests -v
|
|
116
|
+
npm test
|
|
117
|
+
npm pack --dry-run
|
|
118
|
+
python3 -m compileall -q antigravity_mission_control scripts tests
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
## License
|
|
122
|
+
|
|
123
|
+
MIT. Role-contract patterns were adapted from [keli-wen/agy-staff](https://github.com/keli-wen/agy-staff) under its MIT license; see [NOTICE](NOTICE).
|
package/README.zh-CN.md
ADDED
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
<p align="center"><img src="assets/logo/mission-control.svg" width="720" alt="Antigravity Mission Control"></p>
|
|
2
|
+
|
|
3
|
+
<p align="center"><a href="README.md">English</a> · <a href="README.zh-CN.md">简体中文</a></p>
|
|
4
|
+
|
|
5
|
+
<p align="center">
|
|
6
|
+
<img src="https://img.shields.io/badge/Python-3.10%2B-7c3aed" alt="Python 3.10+">
|
|
7
|
+
<img src="https://img.shields.io/badge/Node.js-18%2B-0891b2" alt="Node.js 18+">
|
|
8
|
+
<img src="https://img.shields.io/badge/AGY-tested_1.1.19-22d3ee" alt="AGY tested 1.1.19">
|
|
9
|
+
<a href="LICENSE"><img src="https://img.shields.io/badge/license-MIT-a78bfa" alt="MIT License"></a>
|
|
10
|
+
</p>
|
|
11
|
+
|
|
12
|
+
面向 Antigravity CLI(`agy`)的策略化任务控制层:将工作路由到精确模型,把批准绑定到具体任务,串行保护编辑 worker,确认取消结果并实时查看额度;最终验收始终由 Codex 负责。
|
|
13
|
+
|
|
14
|
+
<p align="center"><img src="assets/terminal-preview.svg" width="920" alt="Mission Control 终端界面预览"></p>
|
|
15
|
+
|
|
16
|
+
> **Alpha:** 已适合公开预发布测试,但还不是稳定安全边界。本项目为独立社区项目,与 Google 或 Antigravity 无官方隶属关系。
|
|
17
|
+
|
|
18
|
+
## 安装
|
|
19
|
+
|
|
20
|
+
先安装 AGY,并确认 `agy --version` 可用。然后运行 npm 一键安装器:
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
npx antigravity-mission-control install
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
安装器会先展示所有目标,再创建私有 Python 运行环境、安装 `agy-mc`、部署 Codex Skill。全程使用参数数组,不使用 shell 拼接。CI 或 agent 环境需要加 `--yes`;可以先用 `--dry-run` 查看影响。
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
npx antigravity-mission-control install --dry-run --lang zh
|
|
30
|
+
npx antigravity-mission-control install --yes --lang zh
|
|
31
|
+
npx antigravity-mission-control status --lang zh
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
也支持直接使用 Python:
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
python3 -m pip install "git+https://github.com/YuxiaoMa66/antigravity-mission-control.git@v0.1.0a1"
|
|
38
|
+
agy-mc skill install --lang zh
|
|
39
|
+
agy-mc doctor
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
完整安装、更新、卸载、本地来源和 PATH 说明见[安装指南](docs/INSTALL.zh-CN.md)。
|
|
43
|
+
|
|
44
|
+
## 分层结构
|
|
45
|
+
|
|
46
|
+
| 层 | 职责 |
|
|
47
|
+
|---|---|
|
|
48
|
+
| Codex Skill | 阵容选择、范围、权限边界和独立验收 |
|
|
49
|
+
| `agy-mc` 核心 | 模型发现、签名批准、AGY 传输、任务、锁、证据和额度 |
|
|
50
|
+
| npm bootstrap | 托管 Python 环境、Skill 部署、更新与可恢复卸载 |
|
|
51
|
+
| AGY | 执行精确且有边界的 worker 任务 |
|
|
52
|
+
|
|
53
|
+
npm 层刻意保持轻量。全部行为规则只在 Python companion 中实现,避免 npm 与 Python 安装方式产生两套逻辑。
|
|
54
|
+
|
|
55
|
+
## 实时额度
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
agy-mc usage
|
|
59
|
+
agy-mc usage --watch --interval 60
|
|
60
|
+
agy-mc usage --format json
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
规范化的 `agy-mc-usage.v1` 会显示模型组、额度窗口、剩余百分比、重置时间和禁用状态。缺失值保持 `unknown`,绝不会改写成误导性的 `0%`;输出不包含 OAuth、原始账户载荷或账户身份。
|
|
64
|
+
|
|
65
|
+
## 绑定批准
|
|
66
|
+
|
|
67
|
+
用户确认精确阵容和权限后,生成短期有效的批准清单:
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
agy-mc approve \
|
|
71
|
+
--strategy A --role implementer --model gemini-3.7-flash-high \
|
|
72
|
+
--cwd /absolute/project --prompt-file /private/prompt.txt \
|
|
73
|
+
--mode accept-edits --expires-minutes 60 --confirmed
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
将返回的文件传给 `run`。本机 HMAC 会绑定策略、角色、模型、规范工作区、prompt 哈希、模式、权限配置、会话和过期时间;任何字段变化都会拒绝执行。
|
|
77
|
+
|
|
78
|
+
```bash
|
|
79
|
+
agy-mc run \
|
|
80
|
+
--strategy A --role implementer --model gemini-3.7-flash-high \
|
|
81
|
+
--cwd /absolute/project --prompt-file /private/prompt.txt \
|
|
82
|
+
--mode accept-edits --approval-file ~/.local/state/antigravity-mission-control/approvals/<id>.json
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
旧的布尔批准参数仅用于 Alpha 迁移兼容,已经弃用。
|
|
86
|
+
|
|
87
|
+
## 后台任务
|
|
88
|
+
|
|
89
|
+
运行时增加 `--background`,然后使用:
|
|
90
|
+
|
|
91
|
+
```bash
|
|
92
|
+
agy-mc status [job-id]
|
|
93
|
+
agy-mc wait <job-id> --timeout 10m
|
|
94
|
+
agy-mc result <job-id>
|
|
95
|
+
agy-mc cancel <job-id>
|
|
96
|
+
agy-mc continue <job-id> --prompt-file /private/follow-up.txt
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
编辑任务按规范工作区使用操作系统级非阻塞锁。`cancel` 会先写入 `canceling`,发送 TERM 并等待,必要时升级到 KILL;只有确认进程退出后才报告 `canceled`。
|
|
100
|
+
|
|
101
|
+
## 核心原则
|
|
102
|
+
|
|
103
|
+
- 每次从当前 AGY 会话发现精确模型,不把历史 slug 当成事实。
|
|
104
|
+
- 工作区信任与 unrestricted 执行是两项独立授权。
|
|
105
|
+
- worker 成功不等于任务通过;Codex 必须检查真实 diff、诊断和测试。
|
|
106
|
+
- prompt 通过 `stream-json` stdin 发送,不进入进程参数。
|
|
107
|
+
- 状态目录权限为 `0700`,prompt、批准、锁和结果为 `0600`。
|
|
108
|
+
- 安装、更新和卸载均保留可恢复备份。
|
|
109
|
+
|
|
110
|
+
继续阅读:[完整参考](docs/REFERENCE.zh-CN.md)、[安全说明](SECURITY.md)、[贡献指南](CONTRIBUTING.md)和[发布流程](docs/RELEASING.zh-CN.md)。
|
|
111
|
+
|
|
112
|
+
## 验证
|
|
113
|
+
|
|
114
|
+
```bash
|
|
115
|
+
python3 -m unittest discover -s tests -v
|
|
116
|
+
npm test
|
|
117
|
+
npm pack --dry-run
|
|
118
|
+
python3 -m compileall -q antigravity_mission_control scripts tests
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
## 许可证
|
|
122
|
+
|
|
123
|
+
MIT。角色合同模式基于 MIT 许可参考了 [keli-wen/agy-staff](https://github.com/keli-wen/agy-staff),详见 [NOTICE](NOTICE)。
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="920" height="260" viewBox="0 0 920 260" role="img" aria-labelledby="title desc">
|
|
2
|
+
<title id="title">Antigravity Mission Control</title>
|
|
3
|
+
<desc id="desc">An orbital mission control mark in violet and cyan.</desc>
|
|
4
|
+
<defs>
|
|
5
|
+
<linearGradient id="bg" x1="0" y1="0" x2="1" y2="1">
|
|
6
|
+
<stop offset="0" stop-color="#111027"/>
|
|
7
|
+
<stop offset="1" stop-color="#071827"/>
|
|
8
|
+
</linearGradient>
|
|
9
|
+
<linearGradient id="orbit" x1="0" y1="0" x2="1" y2="1">
|
|
10
|
+
<stop offset="0" stop-color="#a78bfa"/>
|
|
11
|
+
<stop offset="1" stop-color="#22d3ee"/>
|
|
12
|
+
</linearGradient>
|
|
13
|
+
<filter id="glow" x="-80%" y="-80%" width="260%" height="260%">
|
|
14
|
+
<feGaussianBlur stdDeviation="7" result="blur"/>
|
|
15
|
+
<feMerge><feMergeNode in="blur"/><feMergeNode in="SourceGraphic"/></feMerge>
|
|
16
|
+
</filter>
|
|
17
|
+
</defs>
|
|
18
|
+
<rect x="1" y="1" width="918" height="258" rx="42" fill="url(#bg)" stroke="#2f3159" stroke-width="2"/>
|
|
19
|
+
<g transform="translate(130 130)" fill="none" stroke="url(#orbit)" filter="url(#glow)">
|
|
20
|
+
<ellipse rx="78" ry="31" stroke-width="5" transform="rotate(-17)"/>
|
|
21
|
+
<ellipse rx="78" ry="31" stroke-width="3" opacity=".55" transform="rotate(57)"/>
|
|
22
|
+
<circle r="18" fill="#111027" stroke-width="5"/>
|
|
23
|
+
<circle cx="73" cy="-22" r="8" fill="#22d3ee" stroke="none"/>
|
|
24
|
+
</g>
|
|
25
|
+
<text x="245" y="116" fill="#f5f3ff" font-family="Inter, ui-sans-serif, system-ui" font-size="48" font-weight="750" letter-spacing="1.5">ANTIGRAVITY</text>
|
|
26
|
+
<text x="245" y="169" fill="url(#orbit)" font-family="Inter, ui-sans-serif, system-ui" font-size="42" font-weight="650" letter-spacing="6">MISSION CONTROL</text>
|
|
27
|
+
<text x="249" y="207" fill="#9095b7" font-family="Inter, ui-sans-serif, system-ui" font-size="18" letter-spacing="4">ROUTE · GUARD · VERIFY</text>
|
|
28
|
+
</svg>
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="1120" height="540" viewBox="0 0 1120 540" role="img" aria-labelledby="title desc">
|
|
2
|
+
<title id="title">Mission Control terminal interface</title>
|
|
3
|
+
<desc id="desc">A terminal preview showing installation and live AGY quota.</desc>
|
|
4
|
+
<defs>
|
|
5
|
+
<linearGradient id="shell" x1="0" y1="0" x2="1" y2="1"><stop stop-color="#111027"/><stop offset="1" stop-color="#061820"/></linearGradient>
|
|
6
|
+
<filter id="shadow" x="-20%" y="-20%" width="140%" height="160%"><feDropShadow dx="0" dy="18" stdDeviation="18" flood-color="#020617" flood-opacity=".48"/></filter>
|
|
7
|
+
</defs>
|
|
8
|
+
<rect width="1120" height="540" rx="28" fill="#f3f1ff"/>
|
|
9
|
+
<rect x="45" y="35" width="1030" height="470" rx="22" fill="url(#shell)" filter="url(#shadow)"/>
|
|
10
|
+
<circle cx="78" cy="67" r="7" fill="#fb7185"/><circle cx="102" cy="67" r="7" fill="#fbbf24"/><circle cx="126" cy="67" r="7" fill="#4ade80"/>
|
|
11
|
+
<text x="560" y="72" text-anchor="middle" fill="#6f779b" font-family="ui-monospace, SFMono-Regular, Menlo" font-size="15">antigravity-mission-control</text>
|
|
12
|
+
<g font-family="ui-monospace, SFMono-Regular, Menlo" font-size="20">
|
|
13
|
+
<text x="85" y="120" fill="#a78bfa">╭────────────────────────────────────────────────────────────╮</text>
|
|
14
|
+
<text x="85" y="151" fill="#a78bfa">│</text><text x="110" y="151" fill="#f5f3ff" font-weight="700">ANTIGRAVITY MISSION CONTROL</text><text x="475" y="151" fill="#22d3ee">◉</text><text x="515" y="151" fill="#c4b5fd">v0.1.0-alpha.1</text><text x="817" y="151" fill="#a78bfa">│</text>
|
|
15
|
+
<text x="85" y="182" fill="#a78bfa">│</text><text x="110" y="182" fill="#7f89a9">Route · Guard · Verify</text><text x="817" y="182" fill="#a78bfa">│</text>
|
|
16
|
+
<text x="85" y="213" fill="#a78bfa">╰────────────────────────────────────────────────────────────╯</text>
|
|
17
|
+
<text x="85" y="261" fill="#4ade80">✓</text><text x="120" y="261" fill="#f5f3ff" font-weight="700">AGY 1.1.19 · runtime ready</text>
|
|
18
|
+
<text x="85" y="307" fill="#22d3ee">◆</text><text x="120" y="307" fill="#f5f3ff">Gemini Models</text><text x="430" y="307" fill="#7f89a9">weekly</text><text x="590" y="307" fill="#22d3ee">68.40%</text><text x="730" y="307" fill="#7f89a9">reset 2026-08-25</text>
|
|
19
|
+
<text x="85" y="350" fill="#a78bfa">◆</text><text x="120" y="350" fill="#f5f3ff">Claude & GPT</text><text x="430" y="350" fill="#7f89a9">5 hours</text><text x="590" y="350" fill="#a78bfa">92.10%</text><text x="730" y="350" fill="#7f89a9">reset 22:30</text>
|
|
20
|
+
<text x="85" y="408" fill="#7f89a9">$</text><text x="110" y="408" fill="#f5f3ff">npx antigravity-mission-control status</text>
|
|
21
|
+
<text x="85" y="452" fill="#4ade80">✓</text><text x="120" y="452" fill="#f5f3ff">Mission accomplished</text>
|
|
22
|
+
</g>
|
|
23
|
+
</svg>
|
package/npm/cli.mjs
ADDED
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { spawnSync } from 'node:child_process';
|
|
4
|
+
import { existsSync, lstatSync, mkdirSync, readlinkSync, realpathSync, renameSync, symlinkSync, unlinkSync } from 'node:fs';
|
|
5
|
+
import { homedir } from 'node:os';
|
|
6
|
+
import { basename, dirname, resolve, sep } from 'node:path';
|
|
7
|
+
import process from 'node:process';
|
|
8
|
+
import { createInterface } from 'node:readline/promises';
|
|
9
|
+
import { fileURLToPath } from 'node:url';
|
|
10
|
+
|
|
11
|
+
const VERSION = '0.1.0-alpha.1';
|
|
12
|
+
const PYTHON_VERSION = '0.1.0a1';
|
|
13
|
+
const REPOSITORY = 'https://github.com/YuxiaoMa66/antigravity-mission-control.git';
|
|
14
|
+
const DEFAULT_SOURCE = `git+${REPOSITORY}@v${PYTHON_VERSION}`;
|
|
15
|
+
const colorEnabled = process.stdout.isTTY && !process.env.NO_COLOR;
|
|
16
|
+
|
|
17
|
+
const c = {
|
|
18
|
+
reset: colorEnabled ? '\u001b[0m' : '',
|
|
19
|
+
violet: colorEnabled ? '\u001b[38;5;141m' : '',
|
|
20
|
+
cyan: colorEnabled ? '\u001b[38;5;81m' : '',
|
|
21
|
+
green: colorEnabled ? '\u001b[38;5;114m' : '',
|
|
22
|
+
amber: colorEnabled ? '\u001b[38;5;221m' : '',
|
|
23
|
+
muted: colorEnabled ? '\u001b[38;5;245m' : '',
|
|
24
|
+
bold: colorEnabled ? '\u001b[1m' : '',
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
function parseArgs(argv) {
|
|
28
|
+
const args = { command: 'help', lang: 'auto', yes: false, dryRun: false, force: false, source: DEFAULT_SOURCE };
|
|
29
|
+
const rest = [...argv];
|
|
30
|
+
if (rest[0] && !rest[0].startsWith('-')) args.command = rest.shift();
|
|
31
|
+
while (rest.length) {
|
|
32
|
+
const flag = rest.shift();
|
|
33
|
+
if (flag === '--yes' || flag === '-y') args.yes = true;
|
|
34
|
+
else if (flag === '--dry-run') args.dryRun = true;
|
|
35
|
+
else if (flag === '--force') args.force = true;
|
|
36
|
+
else if (flag === '--lang') {
|
|
37
|
+
if (!rest.length) throw new Error('--lang requires a value');
|
|
38
|
+
args.lang = rest.shift();
|
|
39
|
+
}
|
|
40
|
+
else if (flag === '--source') {
|
|
41
|
+
if (!rest.length) throw new Error('--source requires a value');
|
|
42
|
+
args.source = rest.shift();
|
|
43
|
+
}
|
|
44
|
+
else if (flag === '--help' || flag === '-h') args.command = 'help';
|
|
45
|
+
else if (flag === '--version' || flag === '-v') args.command = 'version';
|
|
46
|
+
else throw new Error(`Unknown option: ${flag}`);
|
|
47
|
+
}
|
|
48
|
+
if (!['auto', 'en', 'zh'].includes(args.lang)) throw new Error('--lang must be auto, en, or zh');
|
|
49
|
+
return args;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function language(requested) {
|
|
53
|
+
if (requested !== 'auto') return requested;
|
|
54
|
+
return /(^|[_.-])zh([_.-]|$)/i.test(process.env.LANG || '') ? 'zh' : 'en';
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const messages = {
|
|
58
|
+
en: {
|
|
59
|
+
subtitle: 'Route · Guard · Verify', install: 'Install', update: 'Update', uninstall: 'Uninstall',
|
|
60
|
+
status: 'Status', doctor: 'Doctor', target: 'Skill target', runtime: 'Managed runtime',
|
|
61
|
+
source: 'Package source', confirm: 'Continue with these exact changes?', canceled: 'Canceled; nothing changed.',
|
|
62
|
+
complete: 'Mission accomplished', missingPython: 'Python 3.10+ is required.', missingAgy: 'AGY was not found; install it before running workers.',
|
|
63
|
+
pathWarning: 'Add this directory to PATH to call agy-mc directly', dryRun: 'Dry run — no files will change',
|
|
64
|
+
},
|
|
65
|
+
zh: {
|
|
66
|
+
subtitle: '调度 · 守界 · 验收', install: '安装', update: '更新', uninstall: '卸载',
|
|
67
|
+
status: '状态', doctor: '诊断', target: 'Skill 目标', runtime: '托管运行环境',
|
|
68
|
+
source: '安装来源', confirm: '确认执行以上精确修改吗?', canceled: '已取消,未修改任何文件。',
|
|
69
|
+
complete: '任务完成', missingPython: '需要 Python 3.10 或更高版本。', missingAgy: '未找到 AGY;运行 worker 前请先安装。',
|
|
70
|
+
pathWarning: '请将此目录加入 PATH,以便直接调用 agy-mc', dryRun: '预演模式——不会修改文件',
|
|
71
|
+
},
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
function paths() {
|
|
75
|
+
const home = resolve(process.env.HOME || homedir());
|
|
76
|
+
const dataRoot = resolve(process.env.AGY_MC_INSTALL_ROOT || `${home}/.local/share/antigravity-mission-control`);
|
|
77
|
+
const binRoot = resolve(process.env.AGY_MC_BIN_DIR || `${home}/.local/bin`);
|
|
78
|
+
const skillTarget = resolve(process.env.AGY_MC_SKILL_TARGET || `${process.env.CODEX_HOME || `${home}/.codex`}/skills/antigravity-mission-control`);
|
|
79
|
+
return { home, dataRoot, venv: `${dataRoot}/venv`, binRoot, shim: `${binRoot}/agy-mc`, skillTarget };
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function assertSafePath(path, home) {
|
|
83
|
+
const resolved = resolve(path);
|
|
84
|
+
if (resolved === '/' || resolved === home || !resolved.startsWith(`${home}${sep}`)) {
|
|
85
|
+
throw new Error(`Refusing unsafe managed path: ${resolved}`);
|
|
86
|
+
}
|
|
87
|
+
const realHome = realpathSync(home);
|
|
88
|
+
let ancestor = resolved;
|
|
89
|
+
const suffix = [];
|
|
90
|
+
while (!lstatSafe(ancestor)) {
|
|
91
|
+
suffix.unshift(basename(ancestor));
|
|
92
|
+
const parent = dirname(ancestor);
|
|
93
|
+
if (parent === ancestor) throw new Error(`Cannot resolve managed path: ${resolved}`);
|
|
94
|
+
ancestor = parent;
|
|
95
|
+
}
|
|
96
|
+
const prospective = resolve(realpathSync(ancestor), ...suffix);
|
|
97
|
+
if (prospective === realHome || !prospective.startsWith(`${realHome}${sep}`)) {
|
|
98
|
+
throw new Error(`Refusing managed path that escapes HOME through a symlink: ${resolved}`);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function banner() {
|
|
103
|
+
console.log(`${c.violet}╭────────────────────────────────────────────────────────────╮${c.reset}`);
|
|
104
|
+
console.log(`${c.violet}│${c.reset} ${c.bold}ANTIGRAVITY MISSION CONTROL${c.reset} ${c.cyan}◉${c.reset} v${VERSION} ${c.violet}│${c.reset}`);
|
|
105
|
+
console.log(`${c.violet}│${c.reset} ${c.muted}Route · Guard · Verify${c.reset} ${c.violet}│${c.reset}`);
|
|
106
|
+
console.log(`${c.violet}╰────────────────────────────────────────────────────────────╯${c.reset}`);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function row(icon, label, value, tone = c.cyan) {
|
|
110
|
+
console.log(` ${tone}${icon}${c.reset} ${c.bold}${label}${c.reset}`);
|
|
111
|
+
console.log(` ${c.muted}${value}${c.reset}`);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function commandExists(command, args = ['--version']) {
|
|
115
|
+
const result = spawnSync(command, args, { encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] });
|
|
116
|
+
return result.status === 0 ? (result.stdout || '').trim() : null;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function pythonCommand() {
|
|
120
|
+
for (const candidate of ['python3', 'python']) {
|
|
121
|
+
const output = commandExists(candidate, ['-c', 'import sys; print(".".join(map(str, sys.version_info[:3])))']);
|
|
122
|
+
if (output) {
|
|
123
|
+
const [major, minor] = output.split('.').map(Number);
|
|
124
|
+
if (major > 3 || (major === 3 && minor >= 10)) return { command: candidate, version: output };
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
return null;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function run(file, args, options = {}) {
|
|
131
|
+
const result = spawnSync(file, args, { encoding: 'utf8', stdio: options.capture ? ['ignore', 'pipe', 'pipe'] : 'inherit', ...options });
|
|
132
|
+
if (result.error) throw result.error;
|
|
133
|
+
if (result.status !== 0) {
|
|
134
|
+
const detail = options.capture ? (result.stderr || result.stdout || '').trim() : '';
|
|
135
|
+
throw new Error(`${basename(file)} exited ${result.status}${detail ? `: ${detail}` : ''}`);
|
|
136
|
+
}
|
|
137
|
+
return result;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
async function confirmChange(args, text) {
|
|
141
|
+
if (args.dryRun || args.yes) return true;
|
|
142
|
+
if (!process.stdin.isTTY) throw new Error('Non-interactive installation requires --yes');
|
|
143
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
144
|
+
const answer = await rl.question(`${c.amber}?${c.reset} ${text} [y/N] `);
|
|
145
|
+
rl.close();
|
|
146
|
+
return /^y(es)?$/i.test(answer.trim());
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function managedAgyMc(p) {
|
|
150
|
+
return process.platform === 'win32' ? `${p.venv}/Scripts/agy-mc.exe` : `${p.venv}/bin/agy-mc`;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function showPlan(args, p, msg) {
|
|
154
|
+
banner();
|
|
155
|
+
const action = msg[args.command] || args.command;
|
|
156
|
+
row('◆', action, args.dryRun ? msg.dryRun : `v${VERSION}`, c.violet);
|
|
157
|
+
row('◇', msg.target, p.skillTarget);
|
|
158
|
+
row('◇', msg.runtime, p.venv);
|
|
159
|
+
if (['install', 'update'].includes(args.command)) row('◇', msg.source, args.source);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function managedShim(p) {
|
|
163
|
+
const stat = lstatSafe(p.shim);
|
|
164
|
+
if (!stat?.isSymbolicLink()) return false;
|
|
165
|
+
try { return resolve(dirname(p.shim), readlinkSync(p.shim)) === resolve(managedAgyMc(p)); } catch { return false; }
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
function ensureShim(p, force) {
|
|
169
|
+
mkdirSync(p.binRoot, { recursive: true, mode: 0o700 });
|
|
170
|
+
if (lstatSafe(p.shim)) {
|
|
171
|
+
if (managedShim(p)) unlinkSync(p.shim);
|
|
172
|
+
else if (force) renameSync(p.shim, `${p.shim}.backup-${Date.now()}`);
|
|
173
|
+
else throw new Error(`Refusing to replace unmanaged CLI path: ${p.shim}; inspect it or rerun with --force`);
|
|
174
|
+
}
|
|
175
|
+
symlinkSync(managedAgyMc(p), p.shim);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
function lstatSafe(path) {
|
|
179
|
+
try { return lstatSync(path); } catch { return null; }
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
async function installOrUpdate(args, p, msg) {
|
|
183
|
+
showPlan(args, p, msg);
|
|
184
|
+
const python = pythonCommand();
|
|
185
|
+
if (!python) throw new Error(msg.missingPython);
|
|
186
|
+
row('✓', 'Python', `${python.command} ${python.version}`, c.green);
|
|
187
|
+
const agyVersion = commandExists('agy');
|
|
188
|
+
row(agyVersion ? '✓' : '!', 'AGY', agyVersion || msg.missingAgy, agyVersion ? c.green : c.amber);
|
|
189
|
+
if (!(await confirmChange(args, msg.confirm))) {
|
|
190
|
+
console.log(msg.canceled);
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
193
|
+
if (args.dryRun) return;
|
|
194
|
+
mkdirSync(p.dataRoot, { recursive: true, mode: 0o700 });
|
|
195
|
+
if (!existsSync(p.venv)) run(python.command, ['-m', 'venv', p.venv]);
|
|
196
|
+
const venvPython = process.platform === 'win32' ? `${p.venv}/Scripts/python.exe` : `${p.venv}/bin/python`;
|
|
197
|
+
run(venvPython, ['-m', 'pip', 'install', '--disable-pip-version-check', '--no-deps', '--upgrade', '--force-reinstall', args.source]);
|
|
198
|
+
const skillAction = args.command === 'install' ? 'install' : 'update';
|
|
199
|
+
const skillArgs = ['skill', skillAction, '--target', p.skillTarget, '--lang', args.lang, '--format', 'pretty'];
|
|
200
|
+
if (args.force) skillArgs.push('--force');
|
|
201
|
+
run(managedAgyMc(p), skillArgs);
|
|
202
|
+
ensureShim(p, args.force);
|
|
203
|
+
console.log(`\n${c.green}✓ ${msg.complete}${c.reset}`);
|
|
204
|
+
if (!(process.env.PATH || '').split(':').includes(p.binRoot)) console.log(`${c.amber}! ${msg.pathWarning}: ${p.binRoot}${c.reset}`);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
function showStatus(p, msg) {
|
|
208
|
+
banner();
|
|
209
|
+
row(existsSync(managedAgyMc(p)) ? '✓' : '○', msg.runtime, existsSync(managedAgyMc(p)) ? managedAgyMc(p) : 'not installed', existsSync(managedAgyMc(p)) ? c.green : c.muted);
|
|
210
|
+
row(existsSync(`${p.skillTarget}/SKILL.md`) ? '✓' : '○', msg.target, p.skillTarget, existsSync(`${p.skillTarget}/SKILL.md`) ? c.green : c.muted);
|
|
211
|
+
const agy = commandExists('agy');
|
|
212
|
+
row(agy ? '✓' : '!', 'AGY', agy || msg.missingAgy, agy ? c.green : c.amber);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
async function uninstall(args, p, msg) {
|
|
216
|
+
showPlan(args, p, msg);
|
|
217
|
+
if (!(await confirmChange(args, msg.confirm))) {
|
|
218
|
+
console.log(msg.canceled);
|
|
219
|
+
return;
|
|
220
|
+
}
|
|
221
|
+
if (args.dryRun) return;
|
|
222
|
+
const executable = managedAgyMc(p);
|
|
223
|
+
if (existsSync(executable) && existsSync(p.skillTarget)) run(executable, ['skill', 'uninstall', '--target', p.skillTarget, '--lang', args.lang]);
|
|
224
|
+
if (managedShim(p)) unlinkSync(p.shim);
|
|
225
|
+
if (existsSync(p.dataRoot)) {
|
|
226
|
+
const backup = `${p.dataRoot}.backup-${new Date().toISOString().replace(/[:.]/g, '-')}`;
|
|
227
|
+
renameSync(p.dataRoot, backup);
|
|
228
|
+
row('↪', 'Backup', backup, c.green);
|
|
229
|
+
}
|
|
230
|
+
console.log(`\n${c.green}✓ ${msg.complete}${c.reset}`);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
function help() {
|
|
234
|
+
banner();
|
|
235
|
+
console.log(`\n${c.bold}Usage${c.reset}\n npx antigravity-mission-control <command> [options]\n`);
|
|
236
|
+
console.log(`${c.bold}Commands${c.reset}\n install Install the managed Python CLI and Codex Skill\n update Upgrade both layers and preserve a backup\n status Show AGY, runtime, and Skill status\n doctor Run the installed Mission Control doctor\n uninstall Recoverably remove managed files\n`);
|
|
237
|
+
console.log(`${c.bold}Options${c.reset}\n --lang auto|en|zh Interface language\n --source PATH|URL Python package source\n --dry-run Show exact targets without writing\n --yes, -y Confirm non-interactively\n --force Back up and replace an unmanaged Skill target\n --version, -v Print version\n`);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
async function main() {
|
|
241
|
+
const args = parseArgs(process.argv.slice(2));
|
|
242
|
+
if (args.command === 'help') return help();
|
|
243
|
+
if (args.command === 'version') return console.log(VERSION);
|
|
244
|
+
if (!['install', 'update', 'status', 'doctor', 'uninstall'].includes(args.command)) throw new Error(`Unknown command: ${args.command}`);
|
|
245
|
+
const p = paths();
|
|
246
|
+
assertSafePath(p.dataRoot, p.home);
|
|
247
|
+
assertSafePath(p.binRoot, p.home);
|
|
248
|
+
assertSafePath(p.skillTarget, p.home);
|
|
249
|
+
const msg = messages[language(args.lang)];
|
|
250
|
+
if (args.command === 'status') return showStatus(p, msg);
|
|
251
|
+
if (args.command === 'doctor') {
|
|
252
|
+
if (!existsSync(managedAgyMc(p))) throw new Error('Mission Control is not installed');
|
|
253
|
+
return run(managedAgyMc(p), ['doctor']);
|
|
254
|
+
}
|
|
255
|
+
if (args.command === 'uninstall') return uninstall(args, p, msg);
|
|
256
|
+
return installOrUpdate(args, p, msg);
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
main().catch((error) => {
|
|
260
|
+
console.error(`\n${c.amber}✗ ${error.message}${c.reset}`);
|
|
261
|
+
process.exitCode = 1;
|
|
262
|
+
});
|
package/package.json
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "antigravity-mission-control",
|
|
3
|
+
"version": "0.1.0-alpha.1",
|
|
4
|
+
"description": "Install and operate Antigravity Mission Control for Codex and the AGY CLI",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"author": "YuxiaoMa66",
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "git+https://github.com/YuxiaoMa66/antigravity-mission-control.git"
|
|
11
|
+
},
|
|
12
|
+
"homepage": "https://github.com/YuxiaoMa66/antigravity-mission-control#readme",
|
|
13
|
+
"bugs": "https://github.com/YuxiaoMa66/antigravity-mission-control/issues",
|
|
14
|
+
"bin": {
|
|
15
|
+
"antigravity-mission-control": "npm/cli.mjs",
|
|
16
|
+
"agy-mc-install": "npm/cli.mjs"
|
|
17
|
+
},
|
|
18
|
+
"files": [
|
|
19
|
+
"npm/",
|
|
20
|
+
"assets/",
|
|
21
|
+
"README.md",
|
|
22
|
+
"README.zh-CN.md",
|
|
23
|
+
"LICENSE",
|
|
24
|
+
"NOTICE"
|
|
25
|
+
],
|
|
26
|
+
"engines": {
|
|
27
|
+
"node": ">=18"
|
|
28
|
+
},
|
|
29
|
+
"os": [
|
|
30
|
+
"darwin",
|
|
31
|
+
"linux"
|
|
32
|
+
],
|
|
33
|
+
"scripts": {
|
|
34
|
+
"test": "node --test tests/*.test.mjs",
|
|
35
|
+
"pack:check": "npm pack --dry-run"
|
|
36
|
+
},
|
|
37
|
+
"keywords": [
|
|
38
|
+
"antigravity",
|
|
39
|
+
"agy",
|
|
40
|
+
"codex",
|
|
41
|
+
"ai-agents",
|
|
42
|
+
"orchestration"
|
|
43
|
+
]
|
|
44
|
+
}
|