apphx 1.0.0 → 1.1.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/README.md CHANGED
@@ -1,23 +1,23 @@
1
- # Apple App Factory
1
+ # apphx
2
2
 
3
3
  给现有 AI agent 接入 Flutter / iOS 开发、验收与交付流程。复用宿主的模型、会话和工具,不启动另一套 coding agent。
4
4
 
5
5
  ## 1. 安装 CLI
6
6
 
7
- 需要 Node.js **22.18+**。npm 包名为 **`apphx`**,命令仍是 `app-factory`,Skill 名仍是 `apple-app-factory`。
7
+ 需要 Node.js **22.18+**。从 **1.1.0** 起,npm 包、命令和 Skill 统一为 **`apphx`**。已有 1.0 安装时,先按第 4 节迁移,**不要先升级 CLI**。
8
8
 
9
- | 包管理器 | 安装或升级 CLI | 运行命令 | 卸载 CLI |
9
+ | 包管理器 | 安装 CLI | 运行命令 | 卸载 CLI |
10
10
  | --- | --- | --- | --- |
11
- | npm | `npm install -g apphx` | `app-factory` | `npm uninstall -g apphx` |
12
- | pnpm | `pnpm add -g apphx` | `app-factory` | `pnpm remove -g apphx` |
13
- | Yarn Classic 1.x | `yarn global add apphx` | `app-factory` | `yarn global remove apphx` |
14
- | Yarn 2+ | 在目标项目中 `yarn add -D apphx` | `yarn exec app-factory` | `yarn remove apphx` |
11
+ | npm | `npm install -g apphx` | `apphx` | `npm uninstall -g apphx` |
12
+ | pnpm | `pnpm add -g apphx` | `apphx` | `pnpm remove -g apphx` |
13
+ | Yarn Classic 1.x | `yarn global add apphx` | `apphx` | `yarn global remove apphx` |
14
+ | Yarn 2+ | 在目标项目中 `yarn add -D apphx` | `yarn exec apphx` | `yarn remove apphx` |
15
15
 
16
- 本地试用时将表格中的包名换成安装包的绝对路径,例如 `$PWD/apphx-1.0.0.tgz`;Yarn Classic 全局安装不能使用相对 tarball 路径。
16
+ 先确认目标版本已在 npm 发布;本地试用或发布前,将表格中的包名换成安装包的绝对路径,例如 `$PWD/apphx-1.1.0.tgz`。Yarn Classic 全局安装不能使用相对 tarball 路径。
17
17
 
18
- pnpm 提示缺少全局 bin 目录时,先运行 `pnpm setup` 并重新打开终端。Yarn 2+ 没有 `yarn global`:后文的 `app-factory ...` 全部改用 `yarn exec app-factory ...`,并从安装了依赖的控制项目目录执行;宿主 Skill 也支持这个本地入口。
18
+ pnpm 提示缺少全局 bin 目录时,先运行 `pnpm setup` 并重新打开终端。Yarn 2+ 没有 `yarn global`:后文的 `apphx ...` 全部改用 `yarn exec apphx ...`,并从安装了依赖的控制项目目录执行。
19
19
 
20
- 安装过旧的本地预览包 `apple-app-factory` 时,先用原包管理器卸载那个 CLI 包,再安装 `apphx`,避免同名命令冲突。不要删除已有 Skill 或 `.factory`;随后用 `app-factory update <host>` 更新原安装。
20
+ npm / pnpm 也可先在控制项目中执行 `npm install -D apphx` / `pnpm add -D apphx`,之后分别用 `npm exec --offline --package=apphx -- apphx ...` / `pnpm exec apphx ...`。Skill 只使用已安装的依赖,不会代为下载 CLI。
21
21
 
22
22
  ## 2. 接入你的 agent
23
23
 
@@ -25,21 +25,21 @@ pnpm 提示缺少全局 bin 目录时,先运行 `pnpm setup` 并重新打开
25
25
 
26
26
  | Agent | 安装工作流 | 在 agent 中启动 |
27
27
  | --- | --- | --- |
28
- | Codex App / CLI | `app-factory install codex` | `$apple-app-factory <需求>` |
29
- | Claude Code | `app-factory install claude` | `/apple-app-factory <需求>` |
30
- | Cursor | `app-factory install cursor` | `/apple-app-factory <需求>` |
31
- | Pi | `app-factory install pi` | `/skill:apple-app-factory <需求>` |
32
- | Gemini CLI | `app-factory install gemini` | `请启用 apple-app-factory skill,<需求>` |
33
- | OpenCode | `app-factory install opencode` | `请加载 apple-app-factory skill,<需求>` |
34
- | DeepSeek Harness | `app-factory install deepseek` | `请加载 apple-app-factory skill,<需求>` |
35
- | Hermes | `app-factory install hermes --global` | `/apple-app-factory <需求>` |
28
+ | Codex App / CLI | `apphx install codex` | `$apphx <需求>` |
29
+ | Claude Code | `apphx install claude` | `/apphx <需求>` |
30
+ | Cursor | `apphx install cursor` | `/apphx <需求>` |
31
+ | Pi | `apphx install pi` | `/skill:apphx <需求>` |
32
+ | Gemini CLI | `apphx install gemini` | `请启用 apphx skill,<需求>` |
33
+ | OpenCode | `apphx install opencode` | `请加载 apphx skill,<需求>` |
34
+ | DeepSeek Harness | `apphx install deepseek` | `请加载 apphx skill,<需求>` |
35
+ | Hermes | `apphx install hermes --global` | `/apphx <需求>` |
36
36
 
37
- 其他项目加 `--project /path/to/project`;用户级安装加 `--global`。Hermes 的全局目录跟随 `HERMES_HOME`,默认 `~/.hermes/skills`。也可在 Git 项目内使用 `app-factory install hermes`,然后由你执行 `hermes skills trust` 授予项目信任;安装器不会代为授权。见 [Hermes Skills](https://hermes-agent.nousresearch.com/docs/user-guide/features/skills)。
37
+ 其他项目加 `--project /path/to/project`;用户级安装加 `--global`。Hermes 的全局目录跟随 `HERMES_HOME`,默认 `~/.hermes/skills`。也可在 Git 项目内使用 `apphx install hermes`,然后由你执行 `hermes skills trust` 授予项目信任;安装器不会代为授权。见 [Hermes Skills](https://hermes-agent.nousresearch.com/docs/user-guide/features/skills)。
38
38
 
39
39
  例如在 Codex 中输入:
40
40
 
41
41
  ```text
42
- $apple-app-factory 开发行李配重应用:离线记录物品重量、检查超重、支持备份恢复;完成模拟器验收,只做本地构建,不上传。
42
+ $apphx 开发行李配重应用:离线记录物品重量、检查超重、支持备份恢复;完成模拟器验收,只做本地构建,不上传。
43
43
  ```
44
44
 
45
45
  ## 3. 准备环境并确认接入
@@ -47,15 +47,15 @@ $apple-app-factory 开发行李配重应用:离线记录物品重量、检查
47
47
  实际 iOS 验收需要 **macOS、完整 Xcode、Flutter 和模拟器**。仅在允许本机执行构建与验收命令时运行:
48
48
 
49
49
  ```sh
50
- app-factory init --allow-local-execution
50
+ apphx init --allow-local-execution
51
51
  ```
52
52
 
53
53
  用 `flutter devices` 查询目标并设置真实的 `IOS_DEVICE_ID`;按 `factory.json` 配置验收命令,指定 SDK 时设置 `toolchain.flutterSdk`。然后检查:
54
54
 
55
55
  ```sh
56
- app-factory doctor --build-only
57
- app-factory doctor --host codex
58
- app-factory status
56
+ apphx doctor --build-only
57
+ apphx doctor --host codex
58
+ apphx status
59
59
  ```
60
60
 
61
61
  宿主 ID、`--project` / `--global` 与安装时保持一致。宿主检查只证明安装文件完整,实际加载看宿主的技能列表;Hermes 可用 `hermes skills list`。
@@ -64,11 +64,20 @@ app-factory status
64
64
 
65
65
  ## 4. 更新与卸载
66
66
 
67
- 先按安装表升级 `apphx`(本地试用则安装新的 tarball),再显式更新宿主工作流:
67
+ 以下适用于已经使用 `apphx` 命令和 Skill 的安装;1.0 用户先完成下方迁移。先升级 CLI(本地试用则安装新的 tarball),再显式更新宿主工作流:
68
+
69
+ | 包管理器 | 升级 CLI |
70
+ | --- | --- |
71
+ | npm | `npm install -g apphx@latest` |
72
+ | pnpm | `pnpm add -g apphx@latest` |
73
+ | Yarn Classic 1.x | `yarn global add apphx@latest` |
74
+ | Yarn 2+ | 在控制项目中 `yarn add -D apphx@latest` |
75
+
76
+ npm / pnpm 项目本地依赖分别用 `npm install -D apphx@latest` / `pnpm add -D apphx@latest`。升级后执行:
68
77
 
69
78
  ```sh
70
- app-factory update codex
71
- app-factory update hermes --global
79
+ apphx update codex
80
+ apphx update hermes --global
72
81
  ```
73
82
 
74
83
  `install` 只安装;重复安装相同内容不做改动,已有不同版本会提示使用 `update`。`update` 不创建缺失的安装,也不会覆盖你修改过的文件。更新使用原来的 `--project` / `--global` 作用域。
@@ -76,24 +85,68 @@ app-factory update hermes --global
76
85
  卸载时先移除工作流,再按安装表卸载 CLI:
77
86
 
78
87
  ```sh
79
- app-factory uninstall codex
80
- app-factory uninstall hermes --global
88
+ apphx uninstall codex
89
+ apphx uninstall hermes --global
81
90
  ```
82
91
 
83
- 不会删除应用代码或 `.factory` 任务数据。
92
+ 不会删除应用代码、`factory.json`、`remote.json` 或 `.factory` 任务数据。
93
+
94
+ <details>
95
+ <summary>已有 1.0 安装:迁移到统一的 apphx 入口</summary>
96
+
97
+ 1.0 的 npm 包已经叫 `apphx`,但可执行文件是 `app-factory`,Skill 是 `apple-app-factory`。1.1 只支持新的 `apphx` 入口;新版不会识别、接管或自动改名旧 Skill。
98
+
99
+ 1. **暂停所有定时工作**,例如 `hermes cron pause CRON_ID`。保存原 `JOB_ID`、配置绝对路径、每个宿主的安装作用域与 profile;确认先前执行已结束,状态不明时停止,不抢占任务。
100
+ 2. **升级 npm 包之前**,用仍已安装的 **1.0 CLI** 卸载每一个未修改的旧 Skill。下面是项目级和全局示例;将宿主、路径换成原安装值,对所有安装逐一执行,保持原 `--project` / `--global` 及 `HERMES_HOME` 等环境:
101
+
102
+ ```sh
103
+ app-factory uninstall codex --project /absolute/project
104
+ app-factory uninstall hermes --global
105
+ ```
106
+
107
+ 若旧 CLI 是控制项目的本地依赖,使用原项目的 `pnpm exec app-factory`、`yarn exec app-factory` 或 `npm exec --offline --package=apphx -- app-factory` 前缀执行同样的卸载。
108
+
109
+ **如果 CLI 已升级**,由你明确手动调用精确的旧版本作为卸载工具(可能需要下载并确认),而不是让新版兼容或让 Skill 自动获取旧 CLI:
110
+
111
+ ```sh
112
+ npm exec --package=apphx@1.0.0 -- app-factory uninstall codex --project /absolute/project
113
+ npm exec --package=apphx@1.0.0 -- app-factory uninstall hermes --global
114
+ ```
115
+
116
+ 旧卸载器只移除归其所有且未修改的安装。若发现编辑过、添加或缺失的文件而拒绝,**停止自动迁移**:由你备份、审阅并决定如何保留;如需退出旧 Skill,由你明确将完整目录归档到宿主发现范围之外。不要覆盖、自动删除或篡改所有权标记来绕过拒绝。更早的本地预览包 `apple-app-factory` 也应先用其原 CLI 安全卸载 Skill,再用原包管理器卸载旧包。
117
+ 3. 确认 1.1.0 可获取后,按上表升级 `apphx`(或安装已验证的 1.1.0 tarball),运行 `apphx --version` 确认版本,再用 **`install`,不是 `update`** 安装新 Skill:
118
+
119
+ ```sh
120
+ apphx install codex --project /absolute/project
121
+ apphx install hermes --global
122
+ apphx status JOB_ID --config /absolute/project/factory.json
123
+ ```
124
+
125
+ 保留原 `factory.json`、`remote.json`、`.factory` 及其任务数据,不重新 `init` 或 `enqueue`。使用 SSH 时,也在 Mac 主机升级 CLI,并将 `remote.json` 的 `executable` 改为该主机 `apphx` 的绝对路径,其他字段保持不变。重新打开宿主,确认加载的是 `apphx`;按第 2 节的新名称调用,继续同一个 `JOB_ID`。
126
+ 4. 在恢复调度前,编辑**原定时任务**的命令、Skill 和名称为 `apphx` / `apphx` / `apphx-continue`,保留原任务 ID、配置、工作目录和防重叠设置,不另建并行调度。Hermes 可先替换所附 Skill(保留其他 Skill):
127
+
128
+ ```sh
129
+ hermes cron edit CRON_ID --remove-skill apple-app-factory --add-skill apphx
130
+ hermes cron list
131
+ ```
132
+
133
+ 同时按第 5 节修改原任务提示词与名称;核对配置和任务状态后,才执行 `hermes cron resume CRON_ID` 或恢复原调度器。不要用恢复调度替代人工授权。
134
+
135
+ </details>
136
+
84
137
 
85
138
  ## 5. Hermes 与定时任务
86
139
 
87
- 先在 Hermes 中完成一次正常接入,取得 `app-factory status` 中的任务 ID。定时任务**唤醒 Hermes 并续跑这个任务**,不是重复 `enqueue`,也不是只运行 `app-factory next`。
140
+ 先在 Hermes 中完成一次正常接入,取得 `apphx status` 中的任务 ID。定时任务**唤醒 Hermes 并续跑这个任务**,不是重复 `enqueue`,也不是只运行 `apphx next`。
88
141
 
89
142
  将 `JOB_ID`、`/absolute/project` 换成真实值,使用相同的 Hermes profile:
90
143
 
91
144
  ```sh
92
145
  hermes cron create "every 30m" \
93
- "继续 Factory 任务 JOB_ID,配置 /absolute/project/factory.json。先检查 status;若 running 且原执行是否结束不明,则报告并停止。否则读取 next,只处理一个返回 action;wait/done 或需要人工授权时停止。所有 Factory 操作使用 --build-only,不重复 enqueue,不上传。" \
94
- --skill apple-app-factory \
146
+ "继续 apphx 任务 JOB_ID,配置 /absolute/project/factory.json。先检查 status;若 running 且原执行是否结束不明,则报告并停止。否则读取 next,只处理一个返回 action;wait/done 或需要人工授权时停止。所有 apphx 操作使用 --build-only,不重复 enqueue,不上传。" \
147
+ --skill apphx \
95
148
  --workdir /absolute/project \
96
- --name app-factory-continue \
149
+ --name apphx-continue \
97
150
  --paused
98
151
  hermes cron list
99
152
  ```
@@ -106,8 +159,8 @@ hermes cron status
106
159
  ```
107
160
 
108
161
  - **Hermes gateway 必须运行**:可前台运行 `hermes gateway`,或按官方方式安装服务。`hermes cron tick` 不会替你启动 gateway。
109
- - gateway 的执行环境必须能访问 CLI、同一配置/工作目录和 Mac 工具链;使用本地 Yarn 依赖时还需在控制项目中使用 `yarn exec app-factory`。
110
- - 同一 Factory 任务只交给一个调度入口;不要同时手工编辑。Hermes 的定时任务去重不是 Factory 跨宿主的文件编辑锁。需要人工处理时先 `hermes cron pause CRON_ID`。
162
+ - gateway 的执行环境必须能访问 CLI、同一配置/工作目录和 Mac 工具链;使用本地 Yarn 依赖时还需在控制项目中使用 `yarn exec apphx`。
163
+ - 同一 apphx 任务只交给一个调度入口;不要同时手工编辑。Hermes 的定时任务去重不是 apphx 跨宿主的文件编辑锁。需要人工处理时先 `hermes cron pause CRON_ID`。
111
164
  - 系统 cron / launchd / CI 也遵循相同边界:只查询状态可直接运行 CLI;需要模型工作时必须唤醒已有 agent 宿主,保留任务 ID、工作目录、环境和防重叠设置。
112
165
 
113
166
  创建为暂停状态不会自动执行。具体调度、恢复和服务安装见 [Hermes 定时任务文档](https://hermes-agent.nousresearch.com/docs/user-guide/features/cron)。
@@ -117,7 +170,7 @@ hermes cron status
117
170
  <details>
118
171
  <summary>开发、打包与版本发布(Turborepo + tsdown + repoctl)</summary>
119
172
 
120
- 维护者使用 `.nvmrc` 指定的 **Node.js 24.22.1** 和 **pnpm 12.15.0**。根目录是私有工具 workspace,唯一发布包为 `packages/apple-app-factory` 中的 `apphx`。Turbo 编排构建、类型检查和测试,tsdown 生成发布产物,repoctl 管理版本和后续发布。
173
+ 维护者使用 `.nvmrc` 指定的 **Node.js 24.22.1** 和 **pnpm 12.15.0**。根目录是私有工具 workspace,唯一发布包为 `apphx`;内部源码目录保留 `packages/apple-app-factory`,以兼容 repoctl 对已发布版本的历史路径校验。Turbo 编排构建、类型检查和测试,tsdown 生成发布产物,repoctl 管理版本和发布。
121
174
 
122
175
  ```sh
123
176
  pnpm install
@@ -129,43 +182,36 @@ pnpm run package:pack
129
182
 
130
183
  构建缓存包含 `dist`、README 与两份 MIT 许可证,并将根目录 README/LICENSE 纳入缓存输入;测试不缓存结果。只使用本地 Turbo 缓存,没有启用远程缓存。安装包输出到仓库根目录。
131
184
 
132
- ### 首次发布 1.0.0
185
+ ### Trusted Publisher 与原始源码恢复
133
186
 
134
- 首发使用维护者本机的 npm 登录,不依赖尚未配置的 Trusted Publisher。先完成上方检查,审查并提交实现和发布意图;原生版本准备要求干净工作树,不能在未提交改动上跳过检查:
187
+ 1.0.0 的手动首发及原生元数据补齐已完成;后续使用 repoctl,不重复手动 `npm publish`。npm Trusted Publisher 保持以下身份:
135
188
 
136
- ```sh
137
- pnpm run release:plan
138
- pnpm version -r --json
139
- pnpm test
140
- pnpm run package:check
141
- pnpm run package:pack
142
- ```
189
+ | npm 字段 | 值 |
190
+ | --- | --- |
191
+ | Organization or user | `daguanren21` |
192
+ | Repository | `apple-app-factory` |
193
+ | Workflow filename | `release.yml`(只填文件名) |
194
+ | Environment name | `npm` |
195
+ | Allowed actions | 必须允许 `npm publish`;只有默认的 `npm stage publish` 不够 |
143
196
 
144
- 确认原生版本结果为 `apphx@1.0.0`,再提交生成的 changelog、`.changeset/ledger.yaml` 等版本元数据,并将两次提交推送到 `main`。记录这个首发源码提交,再发布已验证的包:
145
-
146
- ```sh
147
- git rev-parse HEAD
148
- npm login --registry=https://registry.npmjs.org
149
- npm publish ./apphx-1.0.0.tgz --access public --registry=https://registry.npmjs.org
150
- npm view apphx@1.0.0 version dist.integrity --json
151
- ```
197
+ GitHub 的 `npm` environment 应受保护,发布 job 保留 `id-token: write`;源码仓库必须公开以生成 provenance。CI 使用 OIDC,不保存长期 npm token。`npm dist-tag` 是独立权限,不能替代 `npm publish`。可在 **Release** 工作流运行 `oidc-audit` 核对身份。
152
198
 
153
- 保留首发提交 SHA 与 npm 返回的 `dist.integrity`。tarball 发布不能假定会自动附带当前仓库的 `gitHead`;后续恢复必须显式使用这个已推送的源码 SHA。
199
+ 新建 Trusted Publisher 后,须在 **2 天内**通过它完成首次成功发布,否则配置过期,必须删除重建;审计或已发布版本的元数据恢复不算首次发布。首次成功发布后,该初始 2 天限制不再适用。见 [npm Trusted Publisher 配置规则](https://docs.npmjs.com/trusted-publishers/)。
154
200
 
155
- 首发后再在 npm 的 `apphx` 包设置中配置 Trusted Publisher:仓库 `daguanren21/apple-app-factory`、工作流 `release.yml`、environment `npm`;**Allowed actions 勾选 `npm publish`**,不能只保留默认的 `npm stage publish`。同时配置并保护 GitHub 的 `npm` environment。repoctl 原生上传要求公开源码仓库,以生成 provenance;CI 使用 OIDC,不保存长期 npm token。
201
+ 发布中断时,从 `main` 手动分发 **Release**,选择 `publish`,`source-sha` 填引入该版本 changelog/ledger 的完整版本提交 SHA,先勾选 `dry-run`。它不能是任意后续文档提交。核对目标版本、源码与已发布包后,以同一输入取消 dry-run;repoctl 补齐缺失步骤,已存在的 npm 版本不重复上传。
156
202
 
157
- 用 `oidc-audit` 检查可信发布身份。**下一次 `prepare` 前必须完成首发元数据恢复**:在 Release 工作流选择 `publish`,将 `source-sha` 设为上述首发提交,先 dry-run,再补齐 GitHub tag、Release 和 repoctl 检查点。已有 `apphx@1.0.0` 只核对、不重复上传;否则 repoctl 会因上一版尚未完整交付而拒绝准备下一版。
203
+ 不要拿后续 `main` 冒充原始源码、重写历史 manifest/changelog/ledger,或覆盖 `GITHUB_SHA`。repoctl 会隔离所选版本提交;来源不明、内容冲突或完整性不符时停止调查,不移动已有 tag,也不绕过校验。保留 `repoctl-release-state` 和发布诊断产物。
158
204
 
159
- 新建 Trusted Publisher 后,须在 **2 天内**通过它完成一次新版本发布验证,否则配置过期;`oidc-audit` 或已发布版本的元数据恢复不算。临近下一次实际发布时再创建,过期则删除重建。见 [npm Trusted Publisher 配置规则](https://docs.npmjs.com/trusted-publishers/)。
205
+ ### 原生版本发布
160
206
 
161
- ### 后续版本
207
+ 按实际变更选择 `patch`、`minor` 或 `major`;每项变更只记录一次意图,再检查原生计划:
162
208
 
163
209
  ```sh
164
210
  pnpm change apphx --bump patch --summary "描述本次用户可见改动"
165
211
  pnpm run release:plan
166
212
  ```
167
213
 
168
- 提交改动后,在 GitHub Actions 的 **Release** 中运行 `plan`、`prepare`,审查并合并版本 PR,然后运行 `publish`(先勾选 `dry-run` 预览)。`prepare` 会创建并推送版本 PR,不是本地-only 命令。
214
+ 确认目标版本,审查并提交实现及意图,推送到 `main`;不能在脏工作树上跳过门禁。随后在 GitHub Actions 的 **Release** 中依次运行 `plan`、`prepare`,审查原生版本 PR 和 changelog/ledger,检查通过后合并,再运行 `publish`(先勾选 `dry-run`)。`prepare` 会创建并推送版本 PR;repoctl 负责质量门禁、版本、npm 上传、tag、GitHub Release 与检查点。
169
215
 
170
216
  使用默认 `GITHUB_TOKEN` 准备 PR 时,需要允许 Actions 创建 PR,并在生成的版本分支手动运行 **Check**;该 token 创建的 PR 不会自动触发普通 PR 工作流。已有 GitHub App 时,可配置 `REPOCTL_APP_CLIENT_ID` 变量和 `REPOCTL_APP_PRIVATE_KEY` secret。
171
217
 
@@ -173,7 +219,7 @@ Release 仅手动触发,不会因普通 push 自动上传。保留 `repoctl-re
173
219
 
174
220
  本项目不安装本地提交钩子,Release 是自有的手动工作流;`repo doctor` 对这两项的提示会保留,不应为清除提示而自动覆盖发布流程。
175
221
 
176
- 0.1 的 provider 配置与后台 `run` / `service` 入口不再使用;配置按 [factory.json](https://github.com/daguanren21/apple-app-factory/blob/main/packages/apple-app-factory/examples/factory.json) 更新,不要删除 `.factory`。全部命令见 `app-factory --help`;需求示例见 [luggage.json](https://github.com/daguanren21/apple-app-factory/blob/main/packages/apple-app-factory/examples/luggage.json)。
222
+ 配置与需求示例见 [factory.json](https://github.com/daguanren21/apple-app-factory/blob/main/packages/apple-app-factory/examples/factory.json) 和 [luggage.json](https://github.com/daguanren21/apple-app-factory/blob/main/packages/apple-app-factory/examples/luggage.json)。全部命令见 `apphx --help`;不要删除 `.factory`。
177
223
 
178
224
  </details>
179
225
 
package/dist/cli.js CHANGED
@@ -35,7 +35,7 @@ function inputJSON(path) {
35
35
  return JSON.parse(bytes.toString("utf8"));
36
36
  }
37
37
  function arity(args, minimum, maximum, usage) {
38
- if (args.length < minimum || args.length > maximum) throw new Error(`Usage: app-factory ${usage}`);
38
+ if (args.length < minimum || args.length > maximum) throw new Error(`Usage: apphx ${usage}`);
39
39
  }
40
40
  function outcome(job) {
41
41
  print(view(job));
@@ -58,7 +58,7 @@ async function probe(argv, config) {
58
58
  };
59
59
  }
60
60
  }
61
- const help = `app-factory <command> [--config factory.json] [--build-only]
61
+ const help = `apphx <command> [--config factory.json] [--build-only]
62
62
  hosts List supported agent hosts and activation commands
63
63
  install <host> Install the workflow in the current project
64
64
  update <host> Update an existing, unmodified host workflow
@@ -166,7 +166,7 @@ async function main() {
166
166
  feedback: ["config", "build-only"],
167
167
  remote: ["config", "observe"]
168
168
  };
169
- if (!(command in flags)) throw new Error(`Unknown command ${command}; run app-factory --help`);
169
+ if (!(command in flags)) throw new Error(`Unknown command ${command}; run apphx --help`);
170
170
  for (const token of tokens) if (token.kind === "option" && !flags[command].includes(token.name)) throw new Error(`${command} does not accept --${token.name}`);
171
171
  const configPath = values.config;
172
172
  const buildOnly = values["build-only"];
@@ -205,7 +205,7 @@ async function main() {
205
205
  print({
206
206
  config: resolve(configPath),
207
207
  localExecution: template.allowLocalExecution,
208
- next: "Configure Flutter/Xcode and IOS_DEVICE_ID, then run app-factory doctor --build-only"
208
+ next: "Configure Flutter/Xcode and IOS_DEVICE_ID, then run apphx doctor --build-only"
209
209
  });
210
210
  return;
211
211
  }
package/dist/cli.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"cli.js","names":[],"sources":["../src/cli.ts"],"sourcesContent":["#!/usr/bin/env node\nimport { readFileSync, writeFileSync, mkdirSync } from 'node:fs';\nimport { resolve, dirname, join } from 'node:path';\nimport { fileURLToPath } from 'node:url';\nimport { parseArgs } from 'node:util';\nimport { loadConfig, loadJSON } from './config.js';\nimport { Store } from './store.js';\nimport { run, executionEnvironment } from './process.js';\nimport { appleDoctor, validateAppleSpec } from './apple.js';\nimport { runStep } from './scheduler.js';\nimport { nextTurn, completeTurn, abandonTurn } from './runner.js';\nimport { hosts, installIntegration, updateIntegration, inspectIntegration, uninstallIntegration } from './integrations.js';\nimport { submitRequirement, scanInbox } from './inbox.js';\nimport { MAX_INPUT_BYTES, parseRequirement, readInputFile } from './requirements.js';\nimport { dispatchRemote } from './remote.js';\nimport { toError } from './schemas.js';\nimport type { Config, Job } from './schemas.js';\n\nconst packageRoot = resolve(dirname(fileURLToPath(import.meta.url)), '..');\nconst print = (value: unknown): void => console.log(JSON.stringify(value, null, 2));\nconst view = (job: Job) => ({ id: job.id, product: job.spec.id, version: job.spec.apple?.version ?? '1.0.0', stage: job.stage, status: job.status, hostTurns: job.hostTurns, attempts: job.attempts, workspace: job.workspace, artifact: job.artifact ?? null, error: job.error ?? null });\n\nfunction inputJSON(path: string): unknown {\n const bytes = path === '-' ? readFileSync(0) : readInputFile(path);\n if (bytes.length > MAX_INPUT_BYTES) throw new Error(`Input exceeds ${MAX_INPUT_BYTES} bytes`);\n return JSON.parse(bytes.toString('utf8')) as unknown;\n}\n\nfunction arity(args: string[], minimum: number, maximum: number, usage: string): void {\n if (args.length < minimum || args.length > maximum) throw new Error(`Usage: app-factory ${usage}`);\n}\n\nfunction outcome(job: Job): void {\n print(view(job));\n if (job.status === 'blocked' || job.status === 'failed') process.exitCode = 2;\n}\n\nasync function probe(argv: string[], config: Config) {\n try {\n const result = await run(argv, { env: executionEnvironment(config), timeoutMs: 30000 });\n return { available: result.code === 0, detail: (result.code === 0 ? result.stdout : result.stderr || result.stdout).trim() };\n } catch (error) { return { available: false, detail: toError(error).message }; }\n}\n\nconst help = `app-factory <command> [--config factory.json] [--build-only]\n hosts List supported agent hosts and activation commands\n install <host> Install the workflow in the current project\n update <host> Update an existing, unmodified host workflow\n uninstall <host> Remove an unchanged installation owned by this package\n --project <directory> Select the project (default: current directory)\n --global Use the host's user-level skill directory instead\n doctor --host <host> Check installed workflow files; does not run the agent\n init [--allow-local-execution] Create factory.json without overwriting it\n doctor Check local build tools and execution permissions\n enqueue <spec.json|-> Persist requirements and return a task ID\n next <id> Resume a host turn or show the next deterministic action\n complete <id> <turn> [json|-] Record research/review, or finish the host's build turn\n abandon <id> <turn> Abandon a host turn without resetting its budget\n step <id> Execute one verification, packaging or Apple stage\n status [id] Read durable task state as JSON\n events <id> Read task transitions and evidence\n retry <id> Resume stopped work; preserve budgets and permissions\n submit <spec.json|-> Write requirements to the configured inbox\n scan [--observe] Validate or ingest inbox files; never start an agent\n inbox Read durable intake results\n reconcile-intake <id> <json|-> Restore a legacy request identity from original input\n authorize <id> --submit [--release] Explicitly grant publication permission\n feedback <id> <file> Resume using actual App Review rejection feedback\n remote <command> Call an installed Mac CLI over SSH (build-only)\n --version Print the installed package version\n\nYour agent supplies the model, session, tools and native delegation.\nNo coding-agent subprocess or autonomous daemon is started by this CLI.\nOnly verified local artifacts reach built; uploads require separate authorization.`;\n\nasync function main(): Promise<void> {\n const { values, positionals, tokens } = parseArgs({\n allowPositionals: true,\n tokens: true,\n options: {\n config: { type: 'string', default: 'factory.json' },\n 'build-only': { type: 'boolean', default: false },\n project: { type: 'string' },\n global: { type: 'boolean' },\n host: { type: 'string' },\n 'allow-local-execution': { type: 'boolean' },\n observe: { type: 'boolean' },\n submit: { type: 'boolean' },\n release: { type: 'boolean' },\n version: { type: 'boolean', short: 'v' },\n help: { type: 'boolean', short: 'h' },\n },\n });\n const [command, ...args] = positionals;\n if (values.version) {\n const metadata = JSON.parse(readFileSync(join(packageRoot, 'package.json'), 'utf8')) as { version: string };\n console.log(metadata.version);\n return;\n }\n if (!command || command === 'help' || values.help) { console.log(help); return; }\n const flags: Record<string, string[]> = {\n hosts: [], install: ['project', 'global'], update: ['project', 'global'], uninstall: ['project', 'global'],\n init: ['config', 'allow-local-execution'], doctor: ['config', 'build-only', 'host', 'project', 'global'],\n enqueue: ['config', 'build-only'], next: ['config', 'build-only'], complete: ['config', 'build-only'], abandon: ['config', 'build-only'],\n step: ['config', 'build-only'], status: ['config', 'build-only'], events: ['config', 'build-only'], retry: ['config', 'build-only'],\n submit: ['config', 'build-only'], scan: ['config', 'build-only', 'observe'], inbox: ['config', 'build-only'],\n 'reconcile-intake': ['config', 'build-only'], authorize: ['config', 'build-only', 'submit', 'release'], feedback: ['config', 'build-only'],\n remote: ['config', 'observe'],\n };\n if (!(command in flags)) throw new Error(`Unknown command ${command}; run app-factory --help`);\n for (const token of tokens) {\n if (token.kind === 'option' && !flags[command].includes(token.name)) throw new Error(`${command} does not accept --${token.name}`);\n }\n const configPath = values.config;\n const buildOnly = values['build-only'];\n if (values.global && values.project) throw new Error('Choose --project or --global, not both');\n const scope = { project: values.project, global: values.global };\n if (command === 'hosts') { arity(args, 0, 0, 'hosts'); print(hosts); return; }\n if (command === 'install' || command === 'update' || command === 'uninstall') {\n arity(args, 1, 1, `${command} <host> [--project <directory> | --global]`);\n const operation = command === 'install' ? installIntegration : command === 'update' ? updateIntegration : uninstallIntegration;\n print(operation(args[0], scope));\n return;\n }\n if (command === 'doctor' && values.host) {\n arity(args, 0, 0, 'doctor --host <host> [--project <directory> | --global]');\n const result = inspectIntegration(values.host, scope);\n print(result);\n if (!result.installed) process.exitCode = 2;\n return;\n }\n if (command === 'doctor' && (values.project || values.global)) throw new Error('--project and --global require doctor --host <host>');\n if (command === 'init') {\n arity(args, 0, 0, 'init [--config <file>] [--allow-local-execution]');\n const template = JSON.parse(readFileSync(join(packageRoot, 'examples', 'factory.json'), 'utf8')) as Record<string, unknown>;\n if (values['allow-local-execution']) template.allowLocalExecution = true;\n mkdirSync(dirname(resolve(configPath)), { recursive: true });\n writeFileSync(configPath, `${JSON.stringify(template, null, 2)}\\n`, { flag: 'wx', mode: 0o600 });\n print({ config: resolve(configPath), localExecution: template.allowLocalExecution, next: 'Configure Flutter/Xcode and IOS_DEVICE_ID, then run app-factory doctor --build-only' });\n return;\n }\n if (command === 'remote') {\n process.exitCode = await dispatchRemote(loadJSON(configPath), [...args, ...(values.observe ? ['--observe'] : [])]);\n return;\n }\n const config = loadConfig(configPath);\n if (buildOnly && (config.allowSubmit || config.allowRelease)) throw new Error('--build-only refuses configurations with upload or release enabled');\n if (buildOnly && ['authorize', 'feedback'].includes(command)) throw new Error('Publication commands are forbidden in --build-only mode');\n if (command === 'submit') {\n arity(args, 1, 1, 'submit <spec.json|->');\n print(submitRequirement(config, inputJSON(args[0])));\n return;\n }\n if (command === 'doctor') {\n arity(args, 0, 0, 'doctor [--build-only]');\n const flutter = config.toolchain ? join(config.toolchain.flutterSdk, 'bin', 'flutter') : 'flutter';\n const [flutterCheck, xcode] = await Promise.all([probe([flutter, '--version'], config), probe(['xcodebuild', '-version'], config)]);\n const environment = executionEnvironment(config);\n const neededEnv = [...new Set([...config.verification.prerequisites, ...config.verification.checks, ...config.verification.runtime].flat().flatMap(arg => [...arg.matchAll(/\\$\\{([A-Z][A-Z0-9_]*)\\}/g)].map(match => match[1])))];\n const checks = [\n { name: 'local-execution-permission', available: config.allowLocalExecution, detail: config.executionWarning ?? 'Trusted local commands permitted; host tools remain under the host permission policy' },\n { name: 'flutter', ...flutterCheck }, { name: 'xcode', ...xcode },\n ...neededEnv.map(name => ({ name, available: Boolean(environment[name]), detail: environment[name] ? 'Available in the configured execution environment; value not printed' : `Set ${name} and include its name in executionEnv` })),\n ];\n if (config.allowSubmit) checks.push(...await appleDoctor());\n print({ checks, submissionEnabled: config.allowSubmit, model: 'Provided by your agent host; no separate model authentication is configured here' });\n if (checks.some(check => !check.available)) process.exitCode = 2;\n return;\n }\n const store = new Store(config.stateDir);\n try {\n if (command === 'enqueue') {\n arity(args, 1, 1, 'enqueue <spec.json|->');\n const { spec, baseJobId } = parseRequirement(inputJSON(args[0]));\n if (config.allowSubmit) validateAppleSpec(spec);\n print(view(store.enqueue(spec, config, baseJobId)));\n } else if (command === 'scan') {\n arity(args, 0, 0, 'scan [--observe]');\n const result = await scanInbox(store, config, { observe: values.observe });\n print(result);\n if (result.error || result.entries.some(entry => entry.status === 'error')) process.exitCode = 2;\n } else if (command === 'inbox') {\n arity(args, 0, 0, 'inbox');\n print(store.inboxEntries());\n } else if (command === 'status') {\n arity(args, 0, 1, 'status [id]');\n print(args[0] ? store.get(args[0]) : store.list().map(view));\n } else if (command === 'events') {\n arity(args, 1, 1, 'events <id>');\n print(store.events(args[0]));\n } else if (command === 'next') {\n arity(args, 1, 1, 'next <id>');\n const result = await nextTurn(store, args[0], { buildOnly });\n print(result);\n if (result.job.status === 'blocked' || result.job.status === 'failed') process.exitCode = 2;\n } else if (command === 'complete') {\n arity(args, 2, 3, 'complete <id> <turn-id> [result.json|-]');\n outcome(await completeTurn(store, args[0], args[1], args[2] ? inputJSON(args[2]) : undefined, { buildOnly }));\n } else if (command === 'abandon') {\n arity(args, 2, 2, 'abandon <id> <turn-id>');\n outcome(abandonTurn(store, args[0], args[1], { buildOnly }));\n } else if (command === 'step') {\n arity(args, 1, 1, 'step <id>');\n const controller = new AbortController();\n const stop = (): void => controller.abort();\n process.on('SIGINT', stop);\n process.on('SIGTERM', stop);\n try { outcome(await runStep(store, config, args[0], { signal: controller.signal, buildOnly })); }\n finally { process.removeListener('SIGINT', stop); process.removeListener('SIGTERM', stop); }\n } else if (command === 'reconcile-intake') {\n arity(args, 2, 2, 'reconcile-intake <id> <original-spec.json|->');\n const { spec, baseJobId } = parseRequirement(inputJSON(args[1]));\n print(view(store.reconcileIntake(args[0], spec, baseJobId)));\n } else if (command === 'retry') {\n arity(args, 1, 1, 'retry <id>');\n print(view(store.transition(args[0], job => {\n if (buildOnly && (job.config.allowSubmit || job.config.allowRelease)) throw new Error('--build-only refuses a task with snapshotted publication permission');\n if (!['blocked', 'ready', 'built'].includes(job.status)) throw new Error(`Cannot retry ${job.status}; budgets and active work cannot be reset by retry`);\n if (job.turn) throw new Error('Abandon the active host turn before changing task configuration');\n const proofChanged = JSON.stringify(config.verification) !== JSON.stringify(job.config.verification) || JSON.stringify(config.executionEnv) !== JSON.stringify(job.config.executionEnv) || JSON.stringify(config.toolchain) !== JSON.stringify(job.config.toolchain) || (job.verifiedDigest != null && !job.verifiedDependencyDigest);\n if (proofChanged && Object.keys(job.receipt).length) throw new Error('Verification proof is no longer valid after publication began; reconcile the existing upload before creating a new version');\n const patch: Partial<Job> = proofChanged && job.verifiedDigest ? { stage: 'verify', verifiedDigest: null, verifiedDependencyDigest: null, review: null, artifact: null } : {};\n const resumedConfig = { ...config, packageTarget: job.config.allowSubmit ? 'ipa' as const : config.packageTarget, allowSubmit: job.config.allowSubmit, allowRelease: job.config.allowRelease };\n return { ...patch, config: resumedConfig, status: 'queued', manualRecovery: null, nextAt: Date.now(), error: null };\n })));\n } else if (command === 'authorize') {\n arity(args, 1, 1, 'authorize <id> --submit [--release]');\n if (!values.submit) throw new Error('authorize requires --submit');\n print(view(store.transition(args[0], job => {\n if (job.turn || !['ready', 'blocked', 'built'].includes(job.status) || Object.keys(job.receipt).length || job.stage === 'monitor') throw new Error('Publication permission can change only on stopped work before any remote intent');\n const publishing = validateAppleSpec(job.spec);\n if (values.release && !/^[A-Za-z]{2}$/.test(publishing.storefrontCountry ?? '')) throw new Error('--release requires apple.storefrontCountry in the immutable product specification');\n const granted = { ...job.config, packageTarget: 'ipa' as const, allowSubmit: true, allowRelease: Boolean(values.release) };\n return { config: granted, status: ['ready', 'built'].includes(job.status) ? 'queued' : 'blocked', nextAt: Date.now() };\n })));\n } else if (command === 'feedback') {\n arity(args, 2, 2, 'feedback <id> <file>');\n const feedback = readInputFile(args[1], 'App Review feedback').toString('utf8').trim();\n if (!feedback) throw new Error('App Review feedback file is empty');\n print(view(store.transition(args[0], job => {\n if (job.turn || job.stage !== 'monitor' || job.appleReview?.status !== 'rejected' || job.status !== 'blocked') throw new Error('feedback is only allowed for an observed App Review rejection');\n const spec = structuredClone(job.spec);\n if (!spec.apple?.buildNumber || !/^\\d+$/.test(spec.apple.buildNumber)) throw new Error('A numeric apple.buildNumber is required to prepare a replacement build');\n spec.apple.buildNumber = String(BigInt(spec.apple.buildNumber) + 1n);\n const resumedConfig = { ...config, packageTarget: job.config.allowSubmit ? 'ipa' as const : config.packageTarget, allowSubmit: job.config.allowSubmit, allowRelease: job.config.allowRelease };\n return { spec, config: resumedConfig, feedback, status: 'queued', stage: 'build', createdAt: Date.now(), attempts: {}, hostTurns: 0, nextAt: Date.now(), receipt: {}, ipaPath: null, archiveDigest: null, verifiedDigest: null, verifiedDependencyDigest: null, review: null, error: null };\n })));\n }\n } finally { store.close(); }\n}\n\nmain().catch(error => { console.error(JSON.stringify({ error: toError(error).message })); process.exitCode = 1; });\n"],"mappings":"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"}
1
+ {"version":3,"file":"cli.js","names":[],"sources":["../src/cli.ts"],"sourcesContent":["#!/usr/bin/env node\nimport { readFileSync, writeFileSync, mkdirSync } from 'node:fs';\nimport { resolve, dirname, join } from 'node:path';\nimport { fileURLToPath } from 'node:url';\nimport { parseArgs } from 'node:util';\nimport { loadConfig, loadJSON } from './config.js';\nimport { Store } from './store.js';\nimport { run, executionEnvironment } from './process.js';\nimport { appleDoctor, validateAppleSpec } from './apple.js';\nimport { runStep } from './scheduler.js';\nimport { nextTurn, completeTurn, abandonTurn } from './runner.js';\nimport { hosts, installIntegration, updateIntegration, inspectIntegration, uninstallIntegration } from './integrations.js';\nimport { submitRequirement, scanInbox } from './inbox.js';\nimport { MAX_INPUT_BYTES, parseRequirement, readInputFile } from './requirements.js';\nimport { dispatchRemote } from './remote.js';\nimport { toError } from './schemas.js';\nimport type { Config, Job } from './schemas.js';\n\nconst packageRoot = resolve(dirname(fileURLToPath(import.meta.url)), '..');\nconst print = (value: unknown): void => console.log(JSON.stringify(value, null, 2));\nconst view = (job: Job) => ({ id: job.id, product: job.spec.id, version: job.spec.apple?.version ?? '1.0.0', stage: job.stage, status: job.status, hostTurns: job.hostTurns, attempts: job.attempts, workspace: job.workspace, artifact: job.artifact ?? null, error: job.error ?? null });\n\nfunction inputJSON(path: string): unknown {\n const bytes = path === '-' ? readFileSync(0) : readInputFile(path);\n if (bytes.length > MAX_INPUT_BYTES) throw new Error(`Input exceeds ${MAX_INPUT_BYTES} bytes`);\n return JSON.parse(bytes.toString('utf8')) as unknown;\n}\n\nfunction arity(args: string[], minimum: number, maximum: number, usage: string): void {\n if (args.length < minimum || args.length > maximum) throw new Error(`Usage: apphx ${usage}`);\n}\n\nfunction outcome(job: Job): void {\n print(view(job));\n if (job.status === 'blocked' || job.status === 'failed') process.exitCode = 2;\n}\n\nasync function probe(argv: string[], config: Config) {\n try {\n const result = await run(argv, { env: executionEnvironment(config), timeoutMs: 30000 });\n return { available: result.code === 0, detail: (result.code === 0 ? result.stdout : result.stderr || result.stdout).trim() };\n } catch (error) { return { available: false, detail: toError(error).message }; }\n}\n\nconst help = `apphx <command> [--config factory.json] [--build-only]\n hosts List supported agent hosts and activation commands\n install <host> Install the workflow in the current project\n update <host> Update an existing, unmodified host workflow\n uninstall <host> Remove an unchanged installation owned by this package\n --project <directory> Select the project (default: current directory)\n --global Use the host's user-level skill directory instead\n doctor --host <host> Check installed workflow files; does not run the agent\n init [--allow-local-execution] Create factory.json without overwriting it\n doctor Check local build tools and execution permissions\n enqueue <spec.json|-> Persist requirements and return a task ID\n next <id> Resume a host turn or show the next deterministic action\n complete <id> <turn> [json|-] Record research/review, or finish the host's build turn\n abandon <id> <turn> Abandon a host turn without resetting its budget\n step <id> Execute one verification, packaging or Apple stage\n status [id] Read durable task state as JSON\n events <id> Read task transitions and evidence\n retry <id> Resume stopped work; preserve budgets and permissions\n submit <spec.json|-> Write requirements to the configured inbox\n scan [--observe] Validate or ingest inbox files; never start an agent\n inbox Read durable intake results\n reconcile-intake <id> <json|-> Restore a legacy request identity from original input\n authorize <id> --submit [--release] Explicitly grant publication permission\n feedback <id> <file> Resume using actual App Review rejection feedback\n remote <command> Call an installed Mac CLI over SSH (build-only)\n --version Print the installed package version\n\nYour agent supplies the model, session, tools and native delegation.\nNo coding-agent subprocess or autonomous daemon is started by this CLI.\nOnly verified local artifacts reach built; uploads require separate authorization.`;\n\nasync function main(): Promise<void> {\n const { values, positionals, tokens } = parseArgs({\n allowPositionals: true,\n tokens: true,\n options: {\n config: { type: 'string', default: 'factory.json' },\n 'build-only': { type: 'boolean', default: false },\n project: { type: 'string' },\n global: { type: 'boolean' },\n host: { type: 'string' },\n 'allow-local-execution': { type: 'boolean' },\n observe: { type: 'boolean' },\n submit: { type: 'boolean' },\n release: { type: 'boolean' },\n version: { type: 'boolean', short: 'v' },\n help: { type: 'boolean', short: 'h' },\n },\n });\n const [command, ...args] = positionals;\n if (values.version) {\n const metadata = JSON.parse(readFileSync(join(packageRoot, 'package.json'), 'utf8')) as { version: string };\n console.log(metadata.version);\n return;\n }\n if (!command || command === 'help' || values.help) { console.log(help); return; }\n const flags: Record<string, string[]> = {\n hosts: [], install: ['project', 'global'], update: ['project', 'global'], uninstall: ['project', 'global'],\n init: ['config', 'allow-local-execution'], doctor: ['config', 'build-only', 'host', 'project', 'global'],\n enqueue: ['config', 'build-only'], next: ['config', 'build-only'], complete: ['config', 'build-only'], abandon: ['config', 'build-only'],\n step: ['config', 'build-only'], status: ['config', 'build-only'], events: ['config', 'build-only'], retry: ['config', 'build-only'],\n submit: ['config', 'build-only'], scan: ['config', 'build-only', 'observe'], inbox: ['config', 'build-only'],\n 'reconcile-intake': ['config', 'build-only'], authorize: ['config', 'build-only', 'submit', 'release'], feedback: ['config', 'build-only'],\n remote: ['config', 'observe'],\n };\n if (!(command in flags)) throw new Error(`Unknown command ${command}; run apphx --help`);\n for (const token of tokens) {\n if (token.kind === 'option' && !flags[command].includes(token.name)) throw new Error(`${command} does not accept --${token.name}`);\n }\n const configPath = values.config;\n const buildOnly = values['build-only'];\n if (values.global && values.project) throw new Error('Choose --project or --global, not both');\n const scope = { project: values.project, global: values.global };\n if (command === 'hosts') { arity(args, 0, 0, 'hosts'); print(hosts); return; }\n if (command === 'install' || command === 'update' || command === 'uninstall') {\n arity(args, 1, 1, `${command} <host> [--project <directory> | --global]`);\n const operation = command === 'install' ? installIntegration : command === 'update' ? updateIntegration : uninstallIntegration;\n print(operation(args[0], scope));\n return;\n }\n if (command === 'doctor' && values.host) {\n arity(args, 0, 0, 'doctor --host <host> [--project <directory> | --global]');\n const result = inspectIntegration(values.host, scope);\n print(result);\n if (!result.installed) process.exitCode = 2;\n return;\n }\n if (command === 'doctor' && (values.project || values.global)) throw new Error('--project and --global require doctor --host <host>');\n if (command === 'init') {\n arity(args, 0, 0, 'init [--config <file>] [--allow-local-execution]');\n const template = JSON.parse(readFileSync(join(packageRoot, 'examples', 'factory.json'), 'utf8')) as Record<string, unknown>;\n if (values['allow-local-execution']) template.allowLocalExecution = true;\n mkdirSync(dirname(resolve(configPath)), { recursive: true });\n writeFileSync(configPath, `${JSON.stringify(template, null, 2)}\\n`, { flag: 'wx', mode: 0o600 });\n print({ config: resolve(configPath), localExecution: template.allowLocalExecution, next: 'Configure Flutter/Xcode and IOS_DEVICE_ID, then run apphx doctor --build-only' });\n return;\n }\n if (command === 'remote') {\n process.exitCode = await dispatchRemote(loadJSON(configPath), [...args, ...(values.observe ? ['--observe'] : [])]);\n return;\n }\n const config = loadConfig(configPath);\n if (buildOnly && (config.allowSubmit || config.allowRelease)) throw new Error('--build-only refuses configurations with upload or release enabled');\n if (buildOnly && ['authorize', 'feedback'].includes(command)) throw new Error('Publication commands are forbidden in --build-only mode');\n if (command === 'submit') {\n arity(args, 1, 1, 'submit <spec.json|->');\n print(submitRequirement(config, inputJSON(args[0])));\n return;\n }\n if (command === 'doctor') {\n arity(args, 0, 0, 'doctor [--build-only]');\n const flutter = config.toolchain ? join(config.toolchain.flutterSdk, 'bin', 'flutter') : 'flutter';\n const [flutterCheck, xcode] = await Promise.all([probe([flutter, '--version'], config), probe(['xcodebuild', '-version'], config)]);\n const environment = executionEnvironment(config);\n const neededEnv = [...new Set([...config.verification.prerequisites, ...config.verification.checks, ...config.verification.runtime].flat().flatMap(arg => [...arg.matchAll(/\\$\\{([A-Z][A-Z0-9_]*)\\}/g)].map(match => match[1])))];\n const checks = [\n { name: 'local-execution-permission', available: config.allowLocalExecution, detail: config.executionWarning ?? 'Trusted local commands permitted; host tools remain under the host permission policy' },\n { name: 'flutter', ...flutterCheck }, { name: 'xcode', ...xcode },\n ...neededEnv.map(name => ({ name, available: Boolean(environment[name]), detail: environment[name] ? 'Available in the configured execution environment; value not printed' : `Set ${name} and include its name in executionEnv` })),\n ];\n if (config.allowSubmit) checks.push(...await appleDoctor());\n print({ checks, submissionEnabled: config.allowSubmit, model: 'Provided by your agent host; no separate model authentication is configured here' });\n if (checks.some(check => !check.available)) process.exitCode = 2;\n return;\n }\n const store = new Store(config.stateDir);\n try {\n if (command === 'enqueue') {\n arity(args, 1, 1, 'enqueue <spec.json|->');\n const { spec, baseJobId } = parseRequirement(inputJSON(args[0]));\n if (config.allowSubmit) validateAppleSpec(spec);\n print(view(store.enqueue(spec, config, baseJobId)));\n } else if (command === 'scan') {\n arity(args, 0, 0, 'scan [--observe]');\n const result = await scanInbox(store, config, { observe: values.observe });\n print(result);\n if (result.error || result.entries.some(entry => entry.status === 'error')) process.exitCode = 2;\n } else if (command === 'inbox') {\n arity(args, 0, 0, 'inbox');\n print(store.inboxEntries());\n } else if (command === 'status') {\n arity(args, 0, 1, 'status [id]');\n print(args[0] ? store.get(args[0]) : store.list().map(view));\n } else if (command === 'events') {\n arity(args, 1, 1, 'events <id>');\n print(store.events(args[0]));\n } else if (command === 'next') {\n arity(args, 1, 1, 'next <id>');\n const result = await nextTurn(store, args[0], { buildOnly });\n print(result);\n if (result.job.status === 'blocked' || result.job.status === 'failed') process.exitCode = 2;\n } else if (command === 'complete') {\n arity(args, 2, 3, 'complete <id> <turn-id> [result.json|-]');\n outcome(await completeTurn(store, args[0], args[1], args[2] ? inputJSON(args[2]) : undefined, { buildOnly }));\n } else if (command === 'abandon') {\n arity(args, 2, 2, 'abandon <id> <turn-id>');\n outcome(abandonTurn(store, args[0], args[1], { buildOnly }));\n } else if (command === 'step') {\n arity(args, 1, 1, 'step <id>');\n const controller = new AbortController();\n const stop = (): void => controller.abort();\n process.on('SIGINT', stop);\n process.on('SIGTERM', stop);\n try { outcome(await runStep(store, config, args[0], { signal: controller.signal, buildOnly })); }\n finally { process.removeListener('SIGINT', stop); process.removeListener('SIGTERM', stop); }\n } else if (command === 'reconcile-intake') {\n arity(args, 2, 2, 'reconcile-intake <id> <original-spec.json|->');\n const { spec, baseJobId } = parseRequirement(inputJSON(args[1]));\n print(view(store.reconcileIntake(args[0], spec, baseJobId)));\n } else if (command === 'retry') {\n arity(args, 1, 1, 'retry <id>');\n print(view(store.transition(args[0], job => {\n if (buildOnly && (job.config.allowSubmit || job.config.allowRelease)) throw new Error('--build-only refuses a task with snapshotted publication permission');\n if (!['blocked', 'ready', 'built'].includes(job.status)) throw new Error(`Cannot retry ${job.status}; budgets and active work cannot be reset by retry`);\n if (job.turn) throw new Error('Abandon the active host turn before changing task configuration');\n const proofChanged = JSON.stringify(config.verification) !== JSON.stringify(job.config.verification) || JSON.stringify(config.executionEnv) !== JSON.stringify(job.config.executionEnv) || JSON.stringify(config.toolchain) !== JSON.stringify(job.config.toolchain) || (job.verifiedDigest != null && !job.verifiedDependencyDigest);\n if (proofChanged && Object.keys(job.receipt).length) throw new Error('Verification proof is no longer valid after publication began; reconcile the existing upload before creating a new version');\n const patch: Partial<Job> = proofChanged && job.verifiedDigest ? { stage: 'verify', verifiedDigest: null, verifiedDependencyDigest: null, review: null, artifact: null } : {};\n const resumedConfig = { ...config, packageTarget: job.config.allowSubmit ? 'ipa' as const : config.packageTarget, allowSubmit: job.config.allowSubmit, allowRelease: job.config.allowRelease };\n return { ...patch, config: resumedConfig, status: 'queued', manualRecovery: null, nextAt: Date.now(), error: null };\n })));\n } else if (command === 'authorize') {\n arity(args, 1, 1, 'authorize <id> --submit [--release]');\n if (!values.submit) throw new Error('authorize requires --submit');\n print(view(store.transition(args[0], job => {\n if (job.turn || !['ready', 'blocked', 'built'].includes(job.status) || Object.keys(job.receipt).length || job.stage === 'monitor') throw new Error('Publication permission can change only on stopped work before any remote intent');\n const publishing = validateAppleSpec(job.spec);\n if (values.release && !/^[A-Za-z]{2}$/.test(publishing.storefrontCountry ?? '')) throw new Error('--release requires apple.storefrontCountry in the immutable product specification');\n const granted = { ...job.config, packageTarget: 'ipa' as const, allowSubmit: true, allowRelease: Boolean(values.release) };\n return { config: granted, status: ['ready', 'built'].includes(job.status) ? 'queued' : 'blocked', nextAt: Date.now() };\n })));\n } else if (command === 'feedback') {\n arity(args, 2, 2, 'feedback <id> <file>');\n const feedback = readInputFile(args[1], 'App Review feedback').toString('utf8').trim();\n if (!feedback) throw new Error('App Review feedback file is empty');\n print(view(store.transition(args[0], job => {\n if (job.turn || job.stage !== 'monitor' || job.appleReview?.status !== 'rejected' || job.status !== 'blocked') throw new Error('feedback is only allowed for an observed App Review rejection');\n const spec = structuredClone(job.spec);\n if (!spec.apple?.buildNumber || !/^\\d+$/.test(spec.apple.buildNumber)) throw new Error('A numeric apple.buildNumber is required to prepare a replacement build');\n spec.apple.buildNumber = String(BigInt(spec.apple.buildNumber) + 1n);\n const resumedConfig = { ...config, packageTarget: job.config.allowSubmit ? 'ipa' as const : config.packageTarget, allowSubmit: job.config.allowSubmit, allowRelease: job.config.allowRelease };\n return { spec, config: resumedConfig, feedback, status: 'queued', stage: 'build', createdAt: Date.now(), attempts: {}, hostTurns: 0, nextAt: Date.now(), receipt: {}, ipaPath: null, archiveDigest: null, verifiedDigest: null, verifiedDependencyDigest: null, review: null, error: null };\n })));\n }\n } finally { store.close(); }\n}\n\nmain().catch(error => { console.error(JSON.stringify({ error: toError(error).message })); process.exitCode = 1; });\n"],"mappings":"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"}
@@ -5,83 +5,83 @@ import { dirname, isAbsolute, join, parse, resolve, sep } from "node:path";
5
5
  import { closeSync, constants, fstatSync, lstatSync, mkdirSync, mkdtempSync, openSync, readFileSync, readdirSync, renameSync, rmSync, unlinkSync, writeFileSync } from "node:fs";
6
6
  import { fileURLToPath } from "node:url";
7
7
  // src/integrations.ts
8
- const skill = "apple-app-factory";
9
- const marker = ".apple-app-factory-install.json";
10
- const owner = "apple-app-factory";
11
- const bundledDirectory = fileURLToPath(new URL("../skills/apple-app-factory/", import.meta.url));
8
+ const skill = "apphx";
9
+ const marker = ".apphx-install.json";
10
+ const owner = "apphx";
11
+ const bundledDirectory = fileURLToPath(new URL("../skills/apphx/", import.meta.url));
12
12
  const packageFile = fileURLToPath(new URL("../package.json", import.meta.url));
13
13
  const hosts = Object.freeze([
14
14
  {
15
15
  id: "codex",
16
16
  name: "Codex",
17
- invocation: "$apple-app-factory",
17
+ invocation: "$apphx",
18
18
  projectDirectory: ".agents/skills",
19
19
  globalDirectory: "~/.agents/skills",
20
20
  source: "https://developers.openai.com/codex/skills",
21
- instructions: "Use $apple-app-factory in Codex CLI or the IDE extension; /skills lists skills. Restart if discovery has not refreshed."
21
+ instructions: "Use $apphx in Codex CLI or the IDE extension; /skills lists skills. Restart if discovery has not refreshed."
22
22
  },
23
23
  {
24
24
  id: "claude",
25
25
  name: "Claude Code",
26
- invocation: "/apple-app-factory",
26
+ invocation: "/apphx",
27
27
  projectDirectory: ".claude/skills",
28
28
  globalDirectory: "$CLAUDE_CONFIG_DIR/skills (default ~/.claude/skills)",
29
29
  source: "https://code.claude.com/docs/en/skills",
30
- instructions: "Use /apple-app-factory in a local Claude Code session. Start a new session if the skill is not listed. Cloud sessions do not inherit local user skills."
30
+ instructions: "Use /apphx in a local Claude Code session. Start a new session if the skill is not listed. Cloud sessions do not inherit local user skills."
31
31
  },
32
32
  {
33
33
  id: "cursor",
34
34
  name: "Cursor",
35
- invocation: "/apple-app-factory",
35
+ invocation: "/apphx",
36
36
  projectDirectory: ".cursor/skills",
37
37
  globalDirectory: "~/.cursor/skills",
38
38
  source: "https://cursor.com/docs/skills",
39
- instructions: "In Agent chat, type / and select apple-app-factory. Restart or reopen the project if necessary. Global installation is local, not a Cloud Agent sync."
39
+ instructions: "In Agent chat, type / and select apphx. Restart or reopen the project if necessary. Global installation is local, not a Cloud Agent sync."
40
40
  },
41
41
  {
42
42
  id: "pi",
43
43
  name: "Pi",
44
- invocation: "/skill:apple-app-factory",
44
+ invocation: "/skill:apphx",
45
45
  projectDirectory: ".pi/skills",
46
46
  globalDirectory: "$PI_CODING_AGENT_DIR/skills (default ~/.pi/agent/skills)",
47
47
  source: "https://github.com/badlogic/pi-mono/blob/main/packages/coding-agent/docs/skills.md",
48
- instructions: "Grant project trust before using project skills, then /reload and /skill:apple-app-factory. Stock Pi has no native subagents: use a separate human-started reviewer session, not a spawned coding-agent CLI."
48
+ instructions: "Grant project trust before using project skills, then /reload and /skill:apphx. Stock Pi has no native subagents: use a separate human-started reviewer session, not a spawned coding-agent CLI."
49
49
  },
50
50
  {
51
51
  id: "gemini",
52
52
  name: "Gemini CLI",
53
- invocation: "Use the apple-app-factory skill",
53
+ invocation: "Use the apphx skill",
54
54
  projectDirectory: ".gemini/skills",
55
55
  globalDirectory: "$GEMINI_CLI_HOME/.gemini/skills (default ~/.gemini/skills)",
56
56
  source: "https://geminicli.com/docs/cli/skills/",
57
- instructions: "Run /skills reload and /skills list, then ask: Use the apple-app-factory skill. Gemini calls activate_skill; approve its consent prompt. This is not a /apple-app-factory command."
57
+ instructions: "Run /skills reload and /skills list, then ask: Use the apphx skill. Gemini calls activate_skill; approve its consent prompt. This is not a /apphx command."
58
58
  },
59
59
  {
60
60
  id: "opencode",
61
61
  name: "OpenCode",
62
- invocation: "Use the apple-app-factory skill",
62
+ invocation: "Use the apphx skill",
63
63
  projectDirectory: ".opencode/skills",
64
64
  globalDirectory: "$XDG_CONFIG_HOME/opencode/skills (default ~/.config/opencode/skills)",
65
65
  source: "https://opencode.ai/docs/skills/",
66
- instructions: "Ask OpenCode to use apple-app-factory; the agent loads it with its native skill({ name: \"apple-app-factory\" }) tool. Skill permissions must permit loading. Restart if necessary; no custom slash command is installed."
66
+ instructions: "Ask OpenCode to use apphx; the agent loads it with its native skill({ name: \"apphx\" }) tool. Skill permissions must permit loading. Restart if necessary; no custom slash command is installed."
67
67
  },
68
68
  {
69
69
  id: "deepseek",
70
70
  name: "DeepSeek Harness",
71
- invocation: "Use the apple-app-factory skill",
71
+ invocation: "Use the apphx skill",
72
72
  projectDirectory: ".dsh/skills (nearest Git root, otherwise selected project)",
73
73
  globalDirectory: "$DSH_HOME/skills (default ~/.dsh/skills)",
74
74
  source: "https://deepseek-harness.github.io/deepseek-harness/en/reference/subsystems/skills",
75
- instructions: "Ask DeepSeek Harness to use apple-app-factory; its native skill({ name: \"apple-app-factory\" }) tool loads it. Requires the shipped filesystem skill provider and skill tool, with default roots enabled. Custom provider configurations are not modified."
75
+ instructions: "Ask DeepSeek Harness to use apphx; its native skill({ name: \"apphx\" }) tool loads it. Requires the shipped filesystem skill provider and skill tool, with default roots enabled. Custom provider configurations are not modified."
76
76
  },
77
77
  {
78
78
  id: "hermes",
79
79
  name: "Hermes Agent",
80
- invocation: "/apple-app-factory",
80
+ invocation: "/apphx",
81
81
  projectDirectory: ".hermes/skills",
82
82
  globalDirectory: "$HERMES_HOME/skills (default ~/.hermes/skills)",
83
83
  source: "https://hermes-agent.nousresearch.com/docs/user-guide/features/skills",
84
- instructions: "Use /apple-app-factory. Project installation requires a Git root and a separate owner-approved hermes skills trust decision; this installer never grants trust. For non-Git projects or profile-wide cron use --global in the same HERMES_HOME as Hermes. Confirm discovery with hermes skills list; cron must attach the skill and an absolute workdir."
84
+ instructions: "Use /apphx. Project installation requires a Git root and a separate owner-approved hermes skills trust decision; this installer never grants trust. For non-Git projects or profile-wide cron use --global in the same HERMES_HOME as Hermes. Confirm discovery with hermes skills list; cron must attach the skill and an absolute workdir."
85
85
  }
86
86
  ].map((host) => Object.freeze(host)));
87
87
  const digest = (bytes) => createHash("sha256").update(bytes).digest("hex");
@@ -260,7 +260,7 @@ function details(target) {
260
260
  }
261
261
  function bundle(host) {
262
262
  const tree = readTree(bundledDirectory, true);
263
- if (!tree.files["SKILL.md"] || tree.files[marker]) throw new Error("Invalid bundled apple-app-factory skill");
263
+ if (!tree.files["SKILL.md"] || tree.files[marker]) throw new Error("Invalid bundled apphx skill");
264
264
  const files = fileDigests(tree);
265
265
  if (!Object.keys(files).every(safeRelative) || JSON.stringify(tree.directories.sort()) !== JSON.stringify(directoriesFor(files))) throw new Error("Invalid bundled skill resource paths");
266
266
  const version = JSON.parse(readRegular(packageFile, true).toString("utf8")).version;
@@ -315,7 +315,7 @@ function withLock(target, action) {
315
315
  assertPath(directory);
316
316
  mkdirSync(directory, { recursive: true });
317
317
  assertPath(directory);
318
- const lock = join(directory, ".apple-app-factory-install.lock");
318
+ const lock = join(directory, ".apphx-install.lock");
319
319
  let descriptor;
320
320
  try {
321
321
  descriptor = openSync(lock, constants.O_CREAT | constants.O_EXCL | constants.O_WRONLY | constants.O_NOFOLLOW, 384);
@@ -333,11 +333,11 @@ function applyIntegration(host, options, operation) {
333
333
  const target = location(host, options);
334
334
  const next = bundle(host);
335
335
  assertPath(target.path);
336
- if (operation === "update" && !lstatSync(target.path, { throwIfNoEntry: false })) throw new Error(`No installation at ${target.path}; run app-factory install ${host} first with the same scope`);
336
+ if (operation === "update" && !lstatSync(target.path, { throwIfNoEntry: false })) throw new Error(`No installation at ${target.path}; run apphx install ${host} first with the same scope`);
337
337
  return withLock(target, () => {
338
338
  assertPath(target.path);
339
339
  const previous = lstatSync(target.path, { throwIfNoEntry: false }) ? readOwned(target.path, host) : void 0;
340
- if (operation === "update" && !previous) throw new Error(`No installation at ${target.path}; run app-factory install ${host} first with the same scope`);
340
+ if (operation === "update" && !previous) throw new Error(`No installation at ${target.path}; run apphx install ${host} first with the same scope`);
341
341
  const result = {
342
342
  ...details(target),
343
343
  installed: true,
@@ -349,10 +349,10 @@ function applyIntegration(host, options, operation) {
349
349
  ...result,
350
350
  changed: false
351
351
  };
352
- if (operation === "install" && previous) throw new Error(`An older or different workflow is installed at ${target.path}; use app-factory update ${host} with the same scope`);
352
+ if (operation === "install" && previous) throw new Error(`An older or different workflow is installed at ${target.path}; use apphx update ${host} with the same scope`);
353
353
  const parent = dirname(target.path);
354
354
  mkdirSync(parent, { recursive: true });
355
- const staging = mkdtempSync(join(dirname(parent), ".apple-app-factory-stage-"));
355
+ const staging = mkdtempSync(join(dirname(parent), ".apphx-stage-"));
356
356
  const replacement = join(staging, skill);
357
357
  const backup = join(staging, "previous");
358
358
  let moved = false;
@@ -1 +1 @@
1
- {"version":3,"file":"integrations.js","names":[],"sources":["../src/integrations.ts"],"sourcesContent":["import { createHash } from 'node:crypto';\nimport { closeSync, constants, fstatSync, lstatSync, mkdirSync, mkdtempSync, openSync, readFileSync, readdirSync, renameSync, rmSync, unlinkSync, writeFileSync } from 'node:fs';\nimport { homedir } from 'node:os';\nimport { dirname, isAbsolute, join, parse, resolve, sep } from 'node:path';\nimport { fileURLToPath } from 'node:url';\nimport { z } from 'zod';\n\n// Installation identity follows the skill, not the npm distribution name.\nconst skill = 'apple-app-factory';\nconst marker = '.apple-app-factory-install.json';\nconst owner = 'apple-app-factory';\nconst bundledDirectory = fileURLToPath(new URL('../skills/apple-app-factory/', import.meta.url));\nconst packageFile = fileURLToPath(new URL('../package.json', import.meta.url));\n\nexport const hosts = Object.freeze([\n { id: 'codex', name: 'Codex', invocation: '$apple-app-factory', projectDirectory: '.agents/skills', globalDirectory: '~/.agents/skills', source: 'https://developers.openai.com/codex/skills', instructions: 'Use $apple-app-factory in Codex CLI or the IDE extension; /skills lists skills. Restart if discovery has not refreshed.' },\n { id: 'claude', name: 'Claude Code', invocation: '/apple-app-factory', projectDirectory: '.claude/skills', globalDirectory: '$CLAUDE_CONFIG_DIR/skills (default ~/.claude/skills)', source: 'https://code.claude.com/docs/en/skills', instructions: 'Use /apple-app-factory in a local Claude Code session. Start a new session if the skill is not listed. Cloud sessions do not inherit local user skills.' },\n { id: 'cursor', name: 'Cursor', invocation: '/apple-app-factory', projectDirectory: '.cursor/skills', globalDirectory: '~/.cursor/skills', source: 'https://cursor.com/docs/skills', instructions: 'In Agent chat, type / and select apple-app-factory. Restart or reopen the project if necessary. Global installation is local, not a Cloud Agent sync.' },\n { id: 'pi', name: 'Pi', invocation: '/skill:apple-app-factory', projectDirectory: '.pi/skills', globalDirectory: '$PI_CODING_AGENT_DIR/skills (default ~/.pi/agent/skills)', source: 'https://github.com/badlogic/pi-mono/blob/main/packages/coding-agent/docs/skills.md', instructions: 'Grant project trust before using project skills, then /reload and /skill:apple-app-factory. Stock Pi has no native subagents: use a separate human-started reviewer session, not a spawned coding-agent CLI.' },\n { id: 'gemini', name: 'Gemini CLI', invocation: 'Use the apple-app-factory skill', projectDirectory: '.gemini/skills', globalDirectory: '$GEMINI_CLI_HOME/.gemini/skills (default ~/.gemini/skills)', source: 'https://geminicli.com/docs/cli/skills/', instructions: 'Run /skills reload and /skills list, then ask: Use the apple-app-factory skill. Gemini calls activate_skill; approve its consent prompt. This is not a /apple-app-factory command.' },\n { id: 'opencode', name: 'OpenCode', invocation: 'Use the apple-app-factory skill', projectDirectory: '.opencode/skills', globalDirectory: '$XDG_CONFIG_HOME/opencode/skills (default ~/.config/opencode/skills)', source: 'https://opencode.ai/docs/skills/', instructions: 'Ask OpenCode to use apple-app-factory; the agent loads it with its native skill({ name: \"apple-app-factory\" }) tool. Skill permissions must permit loading. Restart if necessary; no custom slash command is installed.' },\n { id: 'deepseek', name: 'DeepSeek Harness', invocation: 'Use the apple-app-factory skill', projectDirectory: '.dsh/skills (nearest Git root, otherwise selected project)', globalDirectory: '$DSH_HOME/skills (default ~/.dsh/skills)', source: 'https://deepseek-harness.github.io/deepseek-harness/en/reference/subsystems/skills', instructions: 'Ask DeepSeek Harness to use apple-app-factory; its native skill({ name: \"apple-app-factory\" }) tool loads it. Requires the shipped filesystem skill provider and skill tool, with default roots enabled. Custom provider configurations are not modified.' },\n { id: 'hermes', name: 'Hermes Agent', invocation: '/apple-app-factory', projectDirectory: '.hermes/skills', globalDirectory: '$HERMES_HOME/skills (default ~/.hermes/skills)', source: 'https://hermes-agent.nousresearch.com/docs/user-guide/features/skills', instructions: 'Use /apple-app-factory. Project installation requires a Git root and a separate owner-approved hermes skills trust decision; this installer never grants trust. For non-Git projects or profile-wide cron use --global in the same HERMES_HOME as Hermes. Confirm discovery with hermes skills list; cron must attach the skill and an absolute workdir.' },\n].map(host => Object.freeze(host)));\n\nexport type IntegrationOptions = { project?: string; global?: boolean };\ntype Host = (typeof hosts)[number];\ntype Location = { host: Host; scope: 'project' | 'global'; path: string };\nexport type IntegrationInspection = {\n host: string; name: string; scope: 'project' | 'global'; path: string;\n invocation: string; instructions: string; source: string;\n installed: boolean; status: 'missing' | 'installed' | 'conflict';\n version?: string; current?: boolean; reason?: string;\n};\n\ntype Tree = { files: Record<string, Buffer>; directories: string[] };\nconst digest = (bytes: string | Buffer) => createHash('sha256').update(bytes).digest('hex');\nconst safeRelative = (path: string) => /^[a-zA-Z0-9][a-zA-Z0-9._/-]*$/.test(path) && path.split('/').every(part => part !== '.' && part !== '..' && part.length > 0) && path !== marker;\nconst digestSchema = z.string().regex(/^[a-f0-9]{64}$/);\nconst manifestSchema = z.strictObject({\n format: z.literal(1), owner: z.literal(owner), skill: z.literal(skill), host: z.string(),\n version: z.string().regex(/^\\d+\\.\\d+\\.\\d+(?:-[\\w.-]+)?(?:\\+[\\w.-]+)?$/),\n files: z.record(z.string().refine(safeRelative), digestSchema), metadataDigest: digestSchema,\n});\ntype Manifest = z.infer<typeof manifestSchema>;\n\n// Never follow a symlink, including an existing ancestor of a not-yet-created target.\nfunction assertPath(path: string): void {\n const absolute = resolve(path);\n let current = parse(absolute).root;\n const parts = absolute.slice(current.length).split(sep).filter(Boolean);\n for (let i = 0; i < parts.length; i++) {\n current = join(current, parts[i]!);\n const info = lstatSync(current, { throwIfNoEntry: false });\n if (!info) return;\n if (info.isSymbolicLink()) throw new Error(`Refusing symlink path: ${current}. Select its real directory explicitly.`);\n if (i < parts.length - 1 && !info.isDirectory()) throw new Error(`Not a directory: ${current}`);\n }\n}\n\nfunction readRegular(path: string, packageSource = false): Buffer {\n assertPath(path);\n const descriptor = openSync(path, constants.O_RDONLY | constants.O_NOFOLLOW | constants.O_NONBLOCK);\n try {\n const info = fstatSync(descriptor);\n if (!info.isFile() || (!packageSource && info.nlink !== 1)) throw new Error(`Refusing non-regular or hard-linked installation file: ${path}`);\n return readFileSync(descriptor);\n } finally { closeSync(descriptor); }\n}\n\nfunction readTree(root: string, packageSource = false): Tree {\n assertPath(root);\n if (!lstatSync(root).isDirectory()) throw new Error(`Not a skill directory: ${root}`);\n const tree: Tree = { files: Object.create(null), directories: [] };\n function visit(directory: string, prefix: string): void {\n for (const name of readdirSync(directory).sort()) {\n const path = join(directory, name);\n const relative = prefix ? `${prefix}/${name}` : name;\n const info = lstatSync(path);\n if (info.isSymbolicLink()) throw new Error(`Refusing symlink installation entry: ${path}`);\n if (info.isDirectory()) { tree.directories.push(relative); visit(path, relative); }\n else tree.files[relative] = readRegular(path, packageSource);\n }\n }\n visit(root, '');\n return tree;\n}\n\nfunction fileDigests(tree: Tree): Record<string, string> {\n return Object.fromEntries(Object.entries(tree.files).filter(([path]) => path !== marker).map(([path, bytes]) => [path, digest(bytes)]));\n}\n\nfunction metadataDigest(value: Omit<Manifest, 'metadataDigest'>): string {\n return digest(JSON.stringify([value.format, value.owner, value.skill, value.host, value.version, Object.entries(value.files).sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0)]));\n}\n\nfunction directoriesFor(files: Record<string, unknown>): string[] {\n const directories = new Set<string>();\n for (const file of Object.keys(files)) {\n let parent = file.slice(0, file.lastIndexOf('/'));\n if (!file.includes('/')) continue;\n while (parent) { directories.add(parent); parent = parent.includes('/') ? parent.slice(0, parent.lastIndexOf('/')) : ''; }\n }\n return [...directories].sort();\n}\n\nfunction readOwned(path: string, host: string): Manifest {\n const tree = readTree(path);\n if (!tree.files[marker]) throw new Error(`Foreign skill directory has no ${marker}: ${path}`);\n let manifest: Manifest;\n try { manifest = manifestSchema.parse(JSON.parse(tree.files[marker].toString('utf8'))); }\n catch { throw new Error(`Invalid ownership metadata: ${path}`); }\n if (manifest.host !== host || manifest.metadataDigest !== metadataDigest(manifest)) throw new Error(`Foreign or edited ownership metadata: ${path}`);\n if (!manifest.files['SKILL.md'] || JSON.stringify(Object.keys(tree.files).filter(file => file !== marker).sort()) !== JSON.stringify(Object.keys(manifest.files).sort())) throw new Error(`Added or missing files in owned skill: ${path}`);\n for (const [file, expected] of Object.entries(manifest.files)) {\n if (digest(tree.files[file]!) !== expected) throw new Error(`User-edited skill file: ${join(path, file)}`);\n }\n if (JSON.stringify(tree.directories.sort()) !== JSON.stringify(directoriesFor(manifest.files))) throw new Error(`Unowned directories in skill: ${path}`);\n return manifest;\n}\n\nfunction selectedHost(id: string): Host {\n const host = hosts.find(value => value.id === id);\n if (!host) throw new Error(`Unsupported host ${JSON.stringify(id)}. Supported hosts: ${hosts.map(value => value.id).join(', ')}`);\n return host;\n}\n\nfunction environmentDirectory(name: string, fallback: string): string {\n const configured = process.env[name];\n if (!configured) return fallback;\n const expanded = configured === '~' ? homedir() : configured.startsWith('~/') ? join(homedir(), configured.slice(2)) : configured;\n if (!isAbsolute(expanded)) throw new Error(`${name} must be an absolute directory for installation`);\n return resolve(expanded);\n}\n\nfunction location(id: string, options: IntegrationOptions): Location {\n const host = selectedHost(id);\n if (options.global && options.project !== undefined) throw new Error('Choose either project or global installation, not both');\n if (options.global) {\n let directory: string;\n switch (id) {\n case 'claude': directory = join(environmentDirectory('CLAUDE_CONFIG_DIR', join(homedir(), '.claude')), 'skills'); break;\n case 'gemini': directory = join(environmentDirectory('GEMINI_CLI_HOME', homedir()), '.gemini', 'skills'); break;\n case 'pi': directory = join(environmentDirectory('PI_CODING_AGENT_DIR', join(homedir(), '.pi', 'agent')), 'skills'); break;\n case 'opencode': directory = join(environmentDirectory('XDG_CONFIG_HOME', join(homedir(), '.config')), 'opencode', 'skills'); break;\n case 'deepseek': directory = join(environmentDirectory('DSH_HOME', join(homedir(), '.dsh')), 'skills'); break;\n case 'hermes': directory = join(environmentDirectory('HERMES_HOME', join(homedir(), '.hermes')), 'skills'); break;\n default: directory = join(homedir(), host.projectDirectory);\n }\n return { host, scope: 'global', path: join(directory, skill) };\n }\n if (options.project !== undefined && !options.project.trim()) throw new Error('Project directory must not be empty');\n let project = resolve(options.project ?? process.cwd());\n assertPath(project);\n if (!lstatSync(project, { throwIfNoEntry: false })?.isDirectory()) throw new Error(`Project directory does not exist: ${project}`);\n if (id === 'deepseek' || id === 'hermes') {\n let found = false;\n for (let candidate = project; ; candidate = dirname(candidate)) {\n if (lstatSync(join(candidate, '.git'), { throwIfNoEntry: false })) { project = candidate; found = true; break; }\n if (dirname(candidate) === candidate) break;\n }\n if (id === 'hermes' && !found) throw new Error('Hermes project skills require a Git root; use --global for a profile-level installation instead');\n }\n return { host, scope: 'project', path: join(project, id === 'deepseek' ? '.dsh/skills' : host.projectDirectory, skill) };\n}\n\nfunction details(target: Location) {\n return { host: target.host.id, name: target.host.name, scope: target.scope, path: target.path, invocation: target.host.invocation, instructions: target.host.instructions, source: target.host.source };\n}\n\nfunction bundle(host: string): { tree: Tree; manifest: Manifest } {\n // Package stores may hard-link read-only resources; owned installation targets may not.\n const tree = readTree(bundledDirectory, true);\n if (!tree.files['SKILL.md'] || tree.files[marker]) throw new Error('Invalid bundled apple-app-factory skill');\n const files = fileDigests(tree);\n if (!Object.keys(files).every(safeRelative) || JSON.stringify(tree.directories.sort()) !== JSON.stringify(directoriesFor(files))) throw new Error('Invalid bundled skill resource paths');\n const version = JSON.parse(readRegular(packageFile, true).toString('utf8')).version;\n const value: Omit<Manifest, 'metadataDigest'> = { format: 1, owner, skill, host, version, files };\n const manifest = manifestSchema.parse({ ...value, metadataDigest: metadataDigest(value) });\n tree.files[marker] = Buffer.from(`${JSON.stringify(manifest, null, 2)}\\n`);\n return { tree, manifest };\n}\n\nexport function inspectIntegration(host: string, options: IntegrationOptions = {}): IntegrationInspection {\n const target = location(host, options);\n const result = details(target);\n try {\n assertPath(target.path);\n if (!lstatSync(target.path, { throwIfNoEntry: false })) return { ...result, installed: false, status: 'missing' };\n const manifest = readOwned(target.path, host);\n const current = bundle(host).manifest.metadataDigest === manifest.metadataDigest;\n return { ...result, installed: true, status: 'installed', version: manifest.version, current };\n } catch (error) {\n return { ...result, installed: false, status: 'conflict', reason: error instanceof Error ? error.message : String(error) };\n }\n}\n\nfunction withLock<T>(target: Location, action: () => T): T {\n const directory = dirname(dirname(target.path));\n assertPath(directory);\n mkdirSync(directory, { recursive: true });\n assertPath(directory);\n const lock = join(directory, '.apple-app-factory-install.lock');\n let descriptor: number;\n try { descriptor = openSync(lock, constants.O_CREAT | constants.O_EXCL | constants.O_WRONLY | constants.O_NOFOLLOW, 0o600); }\n catch { throw new Error(`Installation is locked at ${lock}; if an earlier installer stopped, inspect it before removing its lock`); }\n try { return action(); }\n finally { closeSync(descriptor); unlinkSync(lock); }\n}\n\nfunction applyIntegration(host: string, options: IntegrationOptions, operation: 'install' | 'update'): IntegrationInspection & { changed: boolean } {\n const target = location(host, options);\n const next = bundle(host);\n assertPath(target.path);\n if (operation === 'update' && !lstatSync(target.path, { throwIfNoEntry: false })) throw new Error(`No installation at ${target.path}; run app-factory install ${host} first with the same scope`);\n return withLock(target, () => {\n assertPath(target.path);\n const previous = lstatSync(target.path, { throwIfNoEntry: false }) ? readOwned(target.path, host) : undefined;\n if (operation === 'update' && !previous) throw new Error(`No installation at ${target.path}; run app-factory install ${host} first with the same scope`);\n const result = { ...details(target), installed: true, status: 'installed' as const, version: next.manifest.version, current: true };\n if (previous?.metadataDigest === next.manifest.metadataDigest) return { ...result, changed: false };\n if (operation === 'install' && previous) throw new Error(`An older or different workflow is installed at ${target.path}; use app-factory update ${host} with the same scope`);\n const parent = dirname(target.path);\n mkdirSync(parent, { recursive: true });\n const staging = mkdtempSync(join(dirname(parent), '.apple-app-factory-stage-'));\n const replacement = join(staging, skill);\n const backup = join(staging, 'previous');\n let moved = false;\n try {\n mkdirSync(replacement);\n for (const [file, bytes] of Object.entries(next.tree.files)) {\n const path = join(replacement, file);\n mkdirSync(dirname(path), { recursive: true });\n writeFileSync(path, bytes, { flag: 'wx', mode: 0o644 });\n }\n assertPath(target.path);\n if (previous) {\n if (readOwned(target.path, host).metadataDigest !== previous.metadataDigest) throw new Error('Installation changed during upgrade');\n renameSync(target.path, backup);\n moved = true;\n } else if (lstatSync(target.path, { throwIfNoEntry: false })) throw new Error('Skill appeared during installation; refusing overwrite');\n try { renameSync(replacement, target.path); }\n catch (error) { if (moved) { renameSync(backup, target.path); moved = false; } throw error; }\n if (moved) { readOwned(backup, host); rmSync(backup, { recursive: true }); moved = false; }\n return { ...result, changed: true };\n } finally {\n // Keep a displaced install intact if rollback or cleanup was interrupted.\n if (!moved) rmSync(staging, { recursive: true, force: true });\n }\n });\n}\n\nexport function installIntegration(host: string, options: IntegrationOptions = {}): IntegrationInspection & { changed: boolean } {\n return applyIntegration(host, options, 'install');\n}\n\nexport function updateIntegration(host: string, options: IntegrationOptions = {}): IntegrationInspection & { changed: boolean } {\n return applyIntegration(host, options, 'update');\n}\n\nexport function uninstallIntegration(host: string, options: IntegrationOptions = {}): IntegrationInspection & { changed: boolean } {\n const target = location(host, options);\n assertPath(target.path);\n const result = { ...details(target), installed: false, status: 'missing' as const };\n if (!lstatSync(target.path, { throwIfNoEntry: false })) return { ...result, changed: false };\n return withLock(target, () => {\n assertPath(target.path);\n if (!lstatSync(target.path, { throwIfNoEntry: false })) return { ...result, changed: false };\n readOwned(target.path, host);\n rmSync(target.path, { recursive: true });\n return { ...result, changed: true };\n });\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"integrations.js","names":[],"sources":["../src/integrations.ts"],"sourcesContent":["import { createHash } from 'node:crypto';\nimport { closeSync, constants, fstatSync, lstatSync, mkdirSync, mkdtempSync, openSync, readFileSync, readdirSync, renameSync, rmSync, unlinkSync, writeFileSync } from 'node:fs';\nimport { homedir } from 'node:os';\nimport { dirname, isAbsolute, join, parse, resolve, sep } from 'node:path';\nimport { fileURLToPath } from 'node:url';\nimport { z } from 'zod';\n\nconst skill = 'apphx';\nconst marker = '.apphx-install.json';\nconst owner = 'apphx';\nconst bundledDirectory = fileURLToPath(new URL('../skills/apphx/', import.meta.url));\nconst packageFile = fileURLToPath(new URL('../package.json', import.meta.url));\n\nexport const hosts = Object.freeze([\n { id: 'codex', name: 'Codex', invocation: '$apphx', projectDirectory: '.agents/skills', globalDirectory: '~/.agents/skills', source: 'https://developers.openai.com/codex/skills', instructions: 'Use $apphx in Codex CLI or the IDE extension; /skills lists skills. Restart if discovery has not refreshed.' },\n { id: 'claude', name: 'Claude Code', invocation: '/apphx', projectDirectory: '.claude/skills', globalDirectory: '$CLAUDE_CONFIG_DIR/skills (default ~/.claude/skills)', source: 'https://code.claude.com/docs/en/skills', instructions: 'Use /apphx in a local Claude Code session. Start a new session if the skill is not listed. Cloud sessions do not inherit local user skills.' },\n { id: 'cursor', name: 'Cursor', invocation: '/apphx', projectDirectory: '.cursor/skills', globalDirectory: '~/.cursor/skills', source: 'https://cursor.com/docs/skills', instructions: 'In Agent chat, type / and select apphx. Restart or reopen the project if necessary. Global installation is local, not a Cloud Agent sync.' },\n { id: 'pi', name: 'Pi', invocation: '/skill:apphx', projectDirectory: '.pi/skills', globalDirectory: '$PI_CODING_AGENT_DIR/skills (default ~/.pi/agent/skills)', source: 'https://github.com/badlogic/pi-mono/blob/main/packages/coding-agent/docs/skills.md', instructions: 'Grant project trust before using project skills, then /reload and /skill:apphx. Stock Pi has no native subagents: use a separate human-started reviewer session, not a spawned coding-agent CLI.' },\n { id: 'gemini', name: 'Gemini CLI', invocation: 'Use the apphx skill', projectDirectory: '.gemini/skills', globalDirectory: '$GEMINI_CLI_HOME/.gemini/skills (default ~/.gemini/skills)', source: 'https://geminicli.com/docs/cli/skills/', instructions: 'Run /skills reload and /skills list, then ask: Use the apphx skill. Gemini calls activate_skill; approve its consent prompt. This is not a /apphx command.' },\n { id: 'opencode', name: 'OpenCode', invocation: 'Use the apphx skill', projectDirectory: '.opencode/skills', globalDirectory: '$XDG_CONFIG_HOME/opencode/skills (default ~/.config/opencode/skills)', source: 'https://opencode.ai/docs/skills/', instructions: 'Ask OpenCode to use apphx; the agent loads it with its native skill({ name: \"apphx\" }) tool. Skill permissions must permit loading. Restart if necessary; no custom slash command is installed.' },\n { id: 'deepseek', name: 'DeepSeek Harness', invocation: 'Use the apphx skill', projectDirectory: '.dsh/skills (nearest Git root, otherwise selected project)', globalDirectory: '$DSH_HOME/skills (default ~/.dsh/skills)', source: 'https://deepseek-harness.github.io/deepseek-harness/en/reference/subsystems/skills', instructions: 'Ask DeepSeek Harness to use apphx; its native skill({ name: \"apphx\" }) tool loads it. Requires the shipped filesystem skill provider and skill tool, with default roots enabled. Custom provider configurations are not modified.' },\n { id: 'hermes', name: 'Hermes Agent', invocation: '/apphx', projectDirectory: '.hermes/skills', globalDirectory: '$HERMES_HOME/skills (default ~/.hermes/skills)', source: 'https://hermes-agent.nousresearch.com/docs/user-guide/features/skills', instructions: 'Use /apphx. Project installation requires a Git root and a separate owner-approved hermes skills trust decision; this installer never grants trust. For non-Git projects or profile-wide cron use --global in the same HERMES_HOME as Hermes. Confirm discovery with hermes skills list; cron must attach the skill and an absolute workdir.' },\n].map(host => Object.freeze(host)));\n\nexport type IntegrationOptions = { project?: string; global?: boolean };\ntype Host = (typeof hosts)[number];\ntype Location = { host: Host; scope: 'project' | 'global'; path: string };\nexport type IntegrationInspection = {\n host: string; name: string; scope: 'project' | 'global'; path: string;\n invocation: string; instructions: string; source: string;\n installed: boolean; status: 'missing' | 'installed' | 'conflict';\n version?: string; current?: boolean; reason?: string;\n};\n\ntype Tree = { files: Record<string, Buffer>; directories: string[] };\nconst digest = (bytes: string | Buffer) => createHash('sha256').update(bytes).digest('hex');\nconst safeRelative = (path: string) => /^[a-zA-Z0-9][a-zA-Z0-9._/-]*$/.test(path) && path.split('/').every(part => part !== '.' && part !== '..' && part.length > 0) && path !== marker;\nconst digestSchema = z.string().regex(/^[a-f0-9]{64}$/);\nconst manifestSchema = z.strictObject({\n format: z.literal(1), owner: z.literal(owner), skill: z.literal(skill), host: z.string(),\n version: z.string().regex(/^\\d+\\.\\d+\\.\\d+(?:-[\\w.-]+)?(?:\\+[\\w.-]+)?$/),\n files: z.record(z.string().refine(safeRelative), digestSchema), metadataDigest: digestSchema,\n});\ntype Manifest = z.infer<typeof manifestSchema>;\n\n// Never follow a symlink, including an existing ancestor of a not-yet-created target.\nfunction assertPath(path: string): void {\n const absolute = resolve(path);\n let current = parse(absolute).root;\n const parts = absolute.slice(current.length).split(sep).filter(Boolean);\n for (let i = 0; i < parts.length; i++) {\n current = join(current, parts[i]!);\n const info = lstatSync(current, { throwIfNoEntry: false });\n if (!info) return;\n if (info.isSymbolicLink()) throw new Error(`Refusing symlink path: ${current}. Select its real directory explicitly.`);\n if (i < parts.length - 1 && !info.isDirectory()) throw new Error(`Not a directory: ${current}`);\n }\n}\n\nfunction readRegular(path: string, packageSource = false): Buffer {\n assertPath(path);\n const descriptor = openSync(path, constants.O_RDONLY | constants.O_NOFOLLOW | constants.O_NONBLOCK);\n try {\n const info = fstatSync(descriptor);\n if (!info.isFile() || (!packageSource && info.nlink !== 1)) throw new Error(`Refusing non-regular or hard-linked installation file: ${path}`);\n return readFileSync(descriptor);\n } finally { closeSync(descriptor); }\n}\n\nfunction readTree(root: string, packageSource = false): Tree {\n assertPath(root);\n if (!lstatSync(root).isDirectory()) throw new Error(`Not a skill directory: ${root}`);\n const tree: Tree = { files: Object.create(null), directories: [] };\n function visit(directory: string, prefix: string): void {\n for (const name of readdirSync(directory).sort()) {\n const path = join(directory, name);\n const relative = prefix ? `${prefix}/${name}` : name;\n const info = lstatSync(path);\n if (info.isSymbolicLink()) throw new Error(`Refusing symlink installation entry: ${path}`);\n if (info.isDirectory()) { tree.directories.push(relative); visit(path, relative); }\n else tree.files[relative] = readRegular(path, packageSource);\n }\n }\n visit(root, '');\n return tree;\n}\n\nfunction fileDigests(tree: Tree): Record<string, string> {\n return Object.fromEntries(Object.entries(tree.files).filter(([path]) => path !== marker).map(([path, bytes]) => [path, digest(bytes)]));\n}\n\nfunction metadataDigest(value: Omit<Manifest, 'metadataDigest'>): string {\n return digest(JSON.stringify([value.format, value.owner, value.skill, value.host, value.version, Object.entries(value.files).sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0)]));\n}\n\nfunction directoriesFor(files: Record<string, unknown>): string[] {\n const directories = new Set<string>();\n for (const file of Object.keys(files)) {\n let parent = file.slice(0, file.lastIndexOf('/'));\n if (!file.includes('/')) continue;\n while (parent) { directories.add(parent); parent = parent.includes('/') ? parent.slice(0, parent.lastIndexOf('/')) : ''; }\n }\n return [...directories].sort();\n}\n\nfunction readOwned(path: string, host: string): Manifest {\n const tree = readTree(path);\n if (!tree.files[marker]) throw new Error(`Foreign skill directory has no ${marker}: ${path}`);\n let manifest: Manifest;\n try { manifest = manifestSchema.parse(JSON.parse(tree.files[marker].toString('utf8'))); }\n catch { throw new Error(`Invalid ownership metadata: ${path}`); }\n if (manifest.host !== host || manifest.metadataDigest !== metadataDigest(manifest)) throw new Error(`Foreign or edited ownership metadata: ${path}`);\n if (!manifest.files['SKILL.md'] || JSON.stringify(Object.keys(tree.files).filter(file => file !== marker).sort()) !== JSON.stringify(Object.keys(manifest.files).sort())) throw new Error(`Added or missing files in owned skill: ${path}`);\n for (const [file, expected] of Object.entries(manifest.files)) {\n if (digest(tree.files[file]!) !== expected) throw new Error(`User-edited skill file: ${join(path, file)}`);\n }\n if (JSON.stringify(tree.directories.sort()) !== JSON.stringify(directoriesFor(manifest.files))) throw new Error(`Unowned directories in skill: ${path}`);\n return manifest;\n}\n\nfunction selectedHost(id: string): Host {\n const host = hosts.find(value => value.id === id);\n if (!host) throw new Error(`Unsupported host ${JSON.stringify(id)}. Supported hosts: ${hosts.map(value => value.id).join(', ')}`);\n return host;\n}\n\nfunction environmentDirectory(name: string, fallback: string): string {\n const configured = process.env[name];\n if (!configured) return fallback;\n const expanded = configured === '~' ? homedir() : configured.startsWith('~/') ? join(homedir(), configured.slice(2)) : configured;\n if (!isAbsolute(expanded)) throw new Error(`${name} must be an absolute directory for installation`);\n return resolve(expanded);\n}\n\nfunction location(id: string, options: IntegrationOptions): Location {\n const host = selectedHost(id);\n if (options.global && options.project !== undefined) throw new Error('Choose either project or global installation, not both');\n if (options.global) {\n let directory: string;\n switch (id) {\n case 'claude': directory = join(environmentDirectory('CLAUDE_CONFIG_DIR', join(homedir(), '.claude')), 'skills'); break;\n case 'gemini': directory = join(environmentDirectory('GEMINI_CLI_HOME', homedir()), '.gemini', 'skills'); break;\n case 'pi': directory = join(environmentDirectory('PI_CODING_AGENT_DIR', join(homedir(), '.pi', 'agent')), 'skills'); break;\n case 'opencode': directory = join(environmentDirectory('XDG_CONFIG_HOME', join(homedir(), '.config')), 'opencode', 'skills'); break;\n case 'deepseek': directory = join(environmentDirectory('DSH_HOME', join(homedir(), '.dsh')), 'skills'); break;\n case 'hermes': directory = join(environmentDirectory('HERMES_HOME', join(homedir(), '.hermes')), 'skills'); break;\n default: directory = join(homedir(), host.projectDirectory);\n }\n return { host, scope: 'global', path: join(directory, skill) };\n }\n if (options.project !== undefined && !options.project.trim()) throw new Error('Project directory must not be empty');\n let project = resolve(options.project ?? process.cwd());\n assertPath(project);\n if (!lstatSync(project, { throwIfNoEntry: false })?.isDirectory()) throw new Error(`Project directory does not exist: ${project}`);\n if (id === 'deepseek' || id === 'hermes') {\n let found = false;\n for (let candidate = project; ; candidate = dirname(candidate)) {\n if (lstatSync(join(candidate, '.git'), { throwIfNoEntry: false })) { project = candidate; found = true; break; }\n if (dirname(candidate) === candidate) break;\n }\n if (id === 'hermes' && !found) throw new Error('Hermes project skills require a Git root; use --global for a profile-level installation instead');\n }\n return { host, scope: 'project', path: join(project, id === 'deepseek' ? '.dsh/skills' : host.projectDirectory, skill) };\n}\n\nfunction details(target: Location) {\n return { host: target.host.id, name: target.host.name, scope: target.scope, path: target.path, invocation: target.host.invocation, instructions: target.host.instructions, source: target.host.source };\n}\n\nfunction bundle(host: string): { tree: Tree; manifest: Manifest } {\n // Package stores may hard-link read-only resources; owned installation targets may not.\n const tree = readTree(bundledDirectory, true);\n if (!tree.files['SKILL.md'] || tree.files[marker]) throw new Error('Invalid bundled apphx skill');\n const files = fileDigests(tree);\n if (!Object.keys(files).every(safeRelative) || JSON.stringify(tree.directories.sort()) !== JSON.stringify(directoriesFor(files))) throw new Error('Invalid bundled skill resource paths');\n const version = JSON.parse(readRegular(packageFile, true).toString('utf8')).version;\n const value: Omit<Manifest, 'metadataDigest'> = { format: 1, owner, skill, host, version, files };\n const manifest = manifestSchema.parse({ ...value, metadataDigest: metadataDigest(value) });\n tree.files[marker] = Buffer.from(`${JSON.stringify(manifest, null, 2)}\\n`);\n return { tree, manifest };\n}\n\nexport function inspectIntegration(host: string, options: IntegrationOptions = {}): IntegrationInspection {\n const target = location(host, options);\n const result = details(target);\n try {\n assertPath(target.path);\n if (!lstatSync(target.path, { throwIfNoEntry: false })) return { ...result, installed: false, status: 'missing' };\n const manifest = readOwned(target.path, host);\n const current = bundle(host).manifest.metadataDigest === manifest.metadataDigest;\n return { ...result, installed: true, status: 'installed', version: manifest.version, current };\n } catch (error) {\n return { ...result, installed: false, status: 'conflict', reason: error instanceof Error ? error.message : String(error) };\n }\n}\n\nfunction withLock<T>(target: Location, action: () => T): T {\n const directory = dirname(dirname(target.path));\n assertPath(directory);\n mkdirSync(directory, { recursive: true });\n assertPath(directory);\n const lock = join(directory, '.apphx-install.lock');\n let descriptor: number;\n try { descriptor = openSync(lock, constants.O_CREAT | constants.O_EXCL | constants.O_WRONLY | constants.O_NOFOLLOW, 0o600); }\n catch { throw new Error(`Installation is locked at ${lock}; if an earlier installer stopped, inspect it before removing its lock`); }\n try { return action(); }\n finally { closeSync(descriptor); unlinkSync(lock); }\n}\n\nfunction applyIntegration(host: string, options: IntegrationOptions, operation: 'install' | 'update'): IntegrationInspection & { changed: boolean } {\n const target = location(host, options);\n const next = bundle(host);\n assertPath(target.path);\n if (operation === 'update' && !lstatSync(target.path, { throwIfNoEntry: false })) throw new Error(`No installation at ${target.path}; run apphx install ${host} first with the same scope`);\n return withLock(target, () => {\n assertPath(target.path);\n const previous = lstatSync(target.path, { throwIfNoEntry: false }) ? readOwned(target.path, host) : undefined;\n if (operation === 'update' && !previous) throw new Error(`No installation at ${target.path}; run apphx install ${host} first with the same scope`);\n const result = { ...details(target), installed: true, status: 'installed' as const, version: next.manifest.version, current: true };\n if (previous?.metadataDigest === next.manifest.metadataDigest) return { ...result, changed: false };\n if (operation === 'install' && previous) throw new Error(`An older or different workflow is installed at ${target.path}; use apphx update ${host} with the same scope`);\n const parent = dirname(target.path);\n mkdirSync(parent, { recursive: true });\n const staging = mkdtempSync(join(dirname(parent), '.apphx-stage-'));\n const replacement = join(staging, skill);\n const backup = join(staging, 'previous');\n let moved = false;\n try {\n mkdirSync(replacement);\n for (const [file, bytes] of Object.entries(next.tree.files)) {\n const path = join(replacement, file);\n mkdirSync(dirname(path), { recursive: true });\n writeFileSync(path, bytes, { flag: 'wx', mode: 0o644 });\n }\n assertPath(target.path);\n if (previous) {\n if (readOwned(target.path, host).metadataDigest !== previous.metadataDigest) throw new Error('Installation changed during upgrade');\n renameSync(target.path, backup);\n moved = true;\n } else if (lstatSync(target.path, { throwIfNoEntry: false })) throw new Error('Skill appeared during installation; refusing overwrite');\n try { renameSync(replacement, target.path); }\n catch (error) { if (moved) { renameSync(backup, target.path); moved = false; } throw error; }\n if (moved) { readOwned(backup, host); rmSync(backup, { recursive: true }); moved = false; }\n return { ...result, changed: true };\n } finally {\n // Keep a displaced install intact if rollback or cleanup was interrupted.\n if (!moved) rmSync(staging, { recursive: true, force: true });\n }\n });\n}\n\nexport function installIntegration(host: string, options: IntegrationOptions = {}): IntegrationInspection & { changed: boolean } {\n return applyIntegration(host, options, 'install');\n}\n\nexport function updateIntegration(host: string, options: IntegrationOptions = {}): IntegrationInspection & { changed: boolean } {\n return applyIntegration(host, options, 'update');\n}\n\nexport function uninstallIntegration(host: string, options: IntegrationOptions = {}): IntegrationInspection & { changed: boolean } {\n const target = location(host, options);\n assertPath(target.path);\n const result = { ...details(target), installed: false, status: 'missing' as const };\n if (!lstatSync(target.path, { throwIfNoEntry: false })) return { ...result, changed: false };\n return withLock(target, () => {\n assertPath(target.path);\n if (!lstatSync(target.path, { throwIfNoEntry: false })) return { ...result, changed: false };\n readOwned(target.path, host);\n rmSync(target.path, { recursive: true });\n return { ...result, changed: true };\n });\n}\n"],"mappings":"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"}
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "host": "mac-mini",
3
3
  "directory": "/srv/apple-project",
4
- "executable": "/opt/homebrew/bin/app-factory",
4
+ "executable": "/opt/homebrew/bin/apphx",
5
5
  "config": "/srv/apple-project/factory.json"
6
6
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "apphx",
3
- "version": "1.0.0",
3
+ "version": "1.1.0",
4
4
  "description": "Host-native harness for verified Flutter app delivery",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -9,7 +9,7 @@
9
9
  "node": ">=22.18"
10
10
  },
11
11
  "bin": {
12
- "app-factory": "./dist/cli.js"
12
+ "apphx": "./dist/cli.js"
13
13
  },
14
14
  "files": [
15
15
  "dist",
@@ -1,19 +1,19 @@
1
1
  ---
2
- name: apple-app-factory
3
- description: Build and locally package a Flutter iOS app from explicit product requirements using the embedded apple-app-factory harness. Use for app-factory init, enqueue, native research/build/independent review, external verification, packaging, or resuming a persisted job. Not a coding-agent launcher or an automatic App Store publisher.
2
+ name: apphx
3
+ description: Build and locally package a Flutter iOS app from explicit product requirements using the embedded apphx harness. Use for apphx init, enqueue, native research/build/independent review, external verification, packaging, or resuming a persisted job. Not a coding-agent launcher or an automatic App Store publisher.
4
4
  ---
5
5
 
6
- # Apple App Factory
6
+ # apphx
7
7
 
8
- Use your current host's model, context, authentication, filesystem, browser and native delegation tools. The installed `app-factory` CLI owns requirements, durable turns, verification, fingerprints and local packaging. Never start another coding-agent CLI, including Hermes, as a subprocess to perform a phase. There is no factory chat UI, model credential setup, daemon, or background agent loop.
8
+ Use your current host's model, context, authentication, filesystem, browser and native delegation tools. The installed `apphx` CLI owns requirements, durable turns, verification, fingerprints and local packaging. Never start another coding-agent CLI, including Hermes, as a subprocess to perform a phase. There is no apphx chat UI, model credential setup, daemon, or background agent loop.
9
9
 
10
- This skill is advisory to the host. Factory code gates factory-owned transitions, not arbitrary host edits or shell commands. It does not sandbox your host, enforce reviewer identity, guarantee Apple approval, or verify that a policy citation's contents are true. Preserve host permissions and the user's scope.
10
+ This skill is advisory to the host. apphx code gates apphx-owned transitions, not arbitrary host edits or shell commands. It does not sandbox your host, enforce reviewer identity, guarantee Apple approval, or verify that a policy citation's contents are true. Preserve host permissions and the user's scope.
11
11
 
12
12
  ## 1. Establish the control project
13
13
 
14
14
  1. Use the project where the user installed/invoked this skill as the control project. Keep `factory.json`, its SQLite state, requirement files and result JSON outside generated app workspaces. Keep the absolute config path for every command below, including commands issued while your working directory is an app workspace.
15
- 2. The npm package is `apphx`; its executable remains `app-factory`. Run `app-factory --version` and `app-factory --help`. If `apphx` is an already-installed local dependency of the control project rather than on PATH, use that project's package manager for **every** factory command: `pnpm exec app-factory`, `yarn exec app-factory`, or `npm exec --offline --package=apphx -- app-factory`. Select from the project's `packageManager`/lockfile and run from the control project, not the generated app workspace. Confirm `apphx` is already installed; do not fetch a similarly named package or silently change the user's environment. If neither route is available, stop and report the missing installation. Installing a skill alone does not install Node, Flutter or Xcode.
16
- 3. If there is no configuration, run `app-factory init --config /absolute/control/factory.json`. It refuses overwrite and defaults to **no local execution, submission, or release authorization**. Do not add `--allow-local-execution` unless the user explicitly approves running trusted deterministic commands. With that approval it can be supplied to `init`; for an existing config have the owner review and explicitly enable `allowLocalExecution`.
15
+ 2. The npm package, executable and skill are all `apphx`. Run `apphx --version` and `apphx --help`. If `apphx` is an already-installed local dependency of the control project rather than on PATH, use that project's package manager for **every** apphx command: `pnpm exec apphx`, `yarn exec apphx`, or `npm exec --offline --package=apphx -- apphx`. Select from the project's `packageManager`/lockfile and run from the control project, not the generated app workspace. Confirm `apphx` is already installed; do not fetch a similarly named package or silently change the user's environment. If neither route is available, stop and report the missing installation. Installing a skill alone does not install Node, Flutter or Xcode.
16
+ 3. If there is no configuration, run `apphx init --config /absolute/control/factory.json`. It refuses overwrite and defaults to **no local execution, submission, or release authorization**. Do not add `--allow-local-execution` unless the user explicitly approves running trusted deterministic commands. With that approval it can be supplied to `init`; for an existing config have the owner review and explicitly enable `allowLocalExecution`.
17
17
  4. Read the actual config. Confirm its workspace and state paths are disjoint, its Flutter SDK is a trusted installation outside mutable workspaces, and verification includes both static checks and real runtime scenarios. Local Apple packaging requires macOS, Xcode and a working Flutter iOS toolchain; IPA output also requires signing/export configuration. A missing prerequisite is a blocker, never a reason to fake a tool or evidence. Do not replace checks with no-op commands to make a job pass.
18
18
 
19
19
  In examples below, replace `/absolute/control/factory.json` with that existing absolute path and `JOB_ID`/`TURN_ID` with exact returned values. Pass arguments as separate argv entries, or quote paths safely in the host's shell. Do not execute prompt or JSON fields as shell code.
@@ -27,31 +27,31 @@ If the user supplied an existing task id or asked to resume it, including a sche
27
27
  The requirement must describe a real audience, purpose, differentiators, Flutter framework, concrete acceptance scenarios and constraints. Preserve the user's product; do not substitute a simpler app. Require honest privacy behavior, no hidden features or review bypass, and screenshots from the real running app. Clarify material unknown product choices before enqueue. Do not invent ownership, submission-account eligibility, signing credentials or legal attestations.
28
28
 
29
29
  ```sh
30
- app-factory enqueue /absolute/control/requirement.json --config /absolute/control/factory.json --build-only
31
- app-factory status JOB_ID --config /absolute/control/factory.json
32
- app-factory next JOB_ID --config /absolute/control/factory.json --build-only
30
+ apphx enqueue /absolute/control/requirement.json --config /absolute/control/factory.json --build-only
31
+ apphx status JOB_ID --config /absolute/control/factory.json
32
+ apphx next JOB_ID --config /absolute/control/factory.json --build-only
33
33
  ```
34
34
 
35
- Keep the returned immutable job id and workspace. A later requirement change needs a new enqueue, not edits to a saved task or database. For a local revision, the requirement may include `baseJobId` of a verified predecessor; the factory validates and prepares the base. Do not copy arbitrary stale build artifacts yourself.
35
+ Keep the returned immutable job id and workspace. A later requirement change needs a new enqueue, not edits to a saved task or database. For a local revision, the requirement may include `baseJobId` of a verified predecessor; apphx validates and prepares the base. Do not copy arbitrary stale build artifacts yourself.
36
36
 
37
37
  ## 3. Dispatch exactly the persisted action
38
38
 
39
39
  Read the JSON from `next`; do not guess stages from memory. Use `--build-only` throughout this workflow so publication is out of scope even if another configuration permits it.
40
40
 
41
41
  - **`action: "host"`**: preserve `turn.id`; read the full `turn.prompt`, `turn.stage`, `turn.cwd` and `turn.environment`. Perform that one native host phase as below. Work inside the returned cwd and merge the returned task/toolchain overrides into your host's existing command environment; do not replace it. Configured environment values are intentionally not printed in the response. This is not a host-wide security boundary; never copy publisher secrets into task commands. Only the CLI completion call advances the task. Repeated `next` resumes the same active turn without spending a second turn, unless its task deadline has expired.
42
- - **`action: "step"`**: run `app-factory step JOB_ID --config /absolute/control/factory.json --build-only` once, inspect its exit status and returned job, then ask `next` again if successful. Only deterministic verify/package stages should be needed here. `step` cannot perform research, build or review for you. Never label a host verdict as external verification.
42
+ - **`action: "step"`**: run `apphx step JOB_ID --config /absolute/control/factory.json --build-only` once, inspect its exit status and returned job, then ask `next` again if successful. Only deterministic verify/package stages should be needed here. `step` cannot perform research, build or review for you. Never label a host verdict as external verification.
43
43
  - **`action: "wait"`**: inspect the job's status, reason, error and due time. Report the exact blocker or scheduled time; do not busy-loop, automatically grant permission, or retry a failed command until it happens to pass. Resume with `next` after the prerequisite/due time is satisfied and the user intends to continue.
44
44
  - **`action: "done"`**: inspect the persisted outcome. `built` plus a recorded local artifact is successful local packaging. Blocked/failed/rejected outcomes are not success merely because no action remains. Report the artifact kind/path/digest and evidence, or the exact unmet requirement.
45
45
 
46
- For a scheduled wake, the host scheduler owns timing and non-overlap; do not create a factory daemon or a second schedule. Check `status` first: if it is `running` and the prior execution's completion is unconfirmed, stop and ask the owner rather than taking over its token. Respect the requested bound (for example one persisted action), then report state and exit. A `wait` or `done` result ends this wake; it does not authorize changing permissions, increasing budgets or polling indefinitely. A fresh scheduled session may review only if it did not implement the change and genuinely inspects the saved source/evidence. Missing independent review or human authorization pauses the work; never manufacture approval to keep a schedule moving.
46
+ For a scheduled wake, the host scheduler owns timing and non-overlap; do not create an apphx daemon or a second schedule. Check `status` first: if it is `running` and the prior execution's completion is unconfirmed, stop and ask the owner rather than taking over its token. Respect the requested bound (for example one persisted action), then report state and exit. A `wait` or `done` result ends this wake; it does not authorize changing permissions, increasing budgets or polling indefinitely. A fresh scheduled session may review only if it did not implement the change and genuinely inspects the saved source/evidence. Missing independent review or human authorization pauses the work; never manufacture approval to keep a schedule moving.
47
47
 
48
48
  To stop an active host phase without completing it:
49
49
 
50
50
  ```sh
51
- app-factory abandon JOB_ID TURN_ID --config /absolute/control/factory.json --build-only
51
+ apphx abandon JOB_ID TURN_ID --config /absolute/control/factory.json --build-only
52
52
  ```
53
53
 
54
- This explicitly abandons the current token; it does not refund the consumed turn. Resume later with `next`. Never reuse an abandoned, stale or already completed token. If a completion's outcome is uncertain, inspect `status`/`next` rather than replaying it blindly. Do not edit SQLite or increase budgets to bypass a rejection. Use `app-factory retry --help` and the stopped task's reported recovery instructions only after fixing the actual cause with owner approval.
54
+ This explicitly abandons the current token; it does not refund the consumed turn. Resume later with `next`. Never reuse an abandoned, stale or already completed token. If a completion's outcome is uncertain, inspect `status`/`next` rather than replaying it blindly. Do not edit SQLite or increase budgets to bypass a rejection. Use `apphx retry --help` and the stopped task's reported recovery instructions only after fixing the actual cause with owner approval.
55
55
 
56
56
  ## 4. Native research
57
57
 
@@ -62,8 +62,8 @@ Use [research-result.json](resources/research-result.json) for the exact JSON sh
62
62
  Save result JSON **outside the app workspace**, for example `/absolute/control/results/JOB_ID-research.json`, then:
63
63
 
64
64
  ```sh
65
- app-factory complete JOB_ID TURN_ID /absolute/control/results/JOB_ID-research.json --config /absolute/control/factory.json --build-only
66
- app-factory next JOB_ID --config /absolute/control/factory.json --build-only
65
+ apphx complete JOB_ID TURN_ID /absolute/control/results/JOB_ID-research.json --config /absolute/control/factory.json --build-only
66
+ apphx next JOB_ID --config /absolute/control/factory.json --build-only
67
67
  ```
68
68
 
69
69
  ## 5. Native implementation
@@ -73,15 +73,15 @@ Perform the returned build prompt with your own native edit, terminal and design
73
73
  Native delegation may handle independent file boundaries. The implementation owner integrates before any external verification. Complete only after the source exists and the requested implementation is ready:
74
74
 
75
75
  ```sh
76
- app-factory complete JOB_ID TURN_ID --config /absolute/control/factory.json --build-only
77
- app-factory next JOB_ID --config /absolute/control/factory.json --build-only
76
+ apphx complete JOB_ID TURN_ID --config /absolute/control/factory.json --build-only
77
+ apphx next JOB_ID --config /absolute/control/factory.json --build-only
78
78
  ```
79
79
 
80
80
  No result JSON is required for build. Build completion only advances to deterministic verification; it is not approval or a package.
81
81
 
82
82
  ## 6. External verification, then independent review
83
83
 
84
- The next deterministic `step` executes the configured prerequisite/static/runtime checks and records real results and source/dependency fingerprints. Read logs and evidence from the returned job or `app-factory events JOB_ID --config /absolute/control/factory.json`. A compilation-only success is not runtime acceptance. Never synthesize receipts or report unavailable iOS execution as passed. Fix the concrete failure through the factory's stopped-task/retry path; do not directly move its stage.
84
+ The next deterministic `step` executes the configured prerequisite/static/runtime checks and records real results and source/dependency fingerprints. Read logs and evidence from the returned job or `apphx events JOB_ID --config /absolute/control/factory.json`. A compilation-only success is not runtime acceptance. Never synthesize receipts or report unavailable iOS execution as passed. Fix the concrete failure through apphx's stopped-task/retry path; do not directly move its stage.
85
85
 
86
86
  When `next` returns a **review** turn, use a **fresh native reviewer context** that did not implement the change. Give it the exact review prompt, requirement, workspace and external evidence. Have it independently inspect every acceptance scenario, code and real evidence, with **no source/dependency/screenshot edits**. Evidence-producing commands that mutate the verified source invalidate approval. An implementation agent's second self-review is not independent.
87
87
 
@@ -90,14 +90,14 @@ Use the host's native subagent/reviewer facility if present. If unavailable (inc
90
90
  Use [review-result.json](resources/review-result.json) for the strict shape: `approved` boolean, `findings` array, and `acceptanceEvidence` array. Every frozen acceptance string must appear **exactly** as `requirement`, with nonempty concrete `evidence` identifying files/symbols and observed commands/scenarios. Approve only if all acceptance criteria are supported and there are no blocking findings. The example is deliberately a rejection, not reusable evidence. Save the verdict outside the app workspace:
91
91
 
92
92
  ```sh
93
- app-factory complete JOB_ID TURN_ID /absolute/control/results/JOB_ID-review.json --config /absolute/control/factory.json --build-only
94
- app-factory next JOB_ID --config /absolute/control/factory.json --build-only
93
+ apphx complete JOB_ID TURN_ID /absolute/control/results/JOB_ID-review.json --config /absolute/control/factory.json --build-only
94
+ apphx next JOB_ID --config /absolute/control/factory.json --build-only
95
95
  ```
96
96
 
97
- The factory rejects incomplete evidence, rejected verdicts and changed fingerprints. It cannot prove which model/person authored a verdict; accurately report the review method and limitations.
97
+ apphx rejects incomplete evidence, rejected verdicts and changed fingerprints. It cannot prove which model/person authored a verdict; accurately report the review method and limitations.
98
98
 
99
99
  ## 7. Package locally and hand off
100
100
 
101
101
  Run the returned package `step` once with `--build-only`, then inspect `status` and `next`. Packaging produces a local simulator build or, when explicitly configured with signing prerequisites, an IPA. Report only recorded artifact paths/digests and exercised checks, including any remaining environmental limitation. Local packaging is not App Store submission, approval, TestFlight availability or release.
102
102
 
103
- Do not call `authorize`, upload with other tools, enable `allowSubmit`/`allowRelease`, or run publication/monitoring on the strength of this skill. Publication requires a separate explicit owner request, the existing factory permission/receipt gates, truthful account ownership, signing and Apple prerequisites. This workflow ends at the local artifact or an accurately reported blocker.
103
+ Do not call `authorize`, upload with other tools, enable `allowSubmit`/`allowRelease`, or run publication/monitoring on the strength of this skill. Publication requires a separate explicit owner request, the existing apphx permission/receipt gates, truthful account ownership, signing and Apple prerequisites. This workflow ends at the local artifact or an accurately reported blocker.
File without changes