global-app-skill 0.1.2 → 0.1.4

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
@@ -18,7 +18,7 @@ npx -y --prefer-online global-app-skill@latest skill install
18
18
  npx -y --prefer-online global-app-skill@latest context resolve --mode research
19
19
  ```
20
20
 
21
- `--mode` 为 `download`、`research` 或 `ued`。CLI 每次查询 GitHub 的 `releases/latest.json`,验证清单、压缩包及逐文件 SHA-256,返回绝对 `resourceRoot` 和有序 `mustRead`。agent 读取这些文件后按原工作流编排任务。
21
+ `--mode` 为 `download`、`research` 或 `ued`。CLI 每次查询 GitHub 最新正式 Release 的 `latest.json` 附件,验证清单、压缩包及逐文件 SHA-256,返回绝对 `resourceRoot` 和有序 `mustRead`。agent 读取这些文件后按原工作流编排任务。
22
22
 
23
23
  `context resolve` 和 `run` 执行前会将已安装的薄入口同步为当前 CLI 捆绑的版本。入口使用 `npx --prefer-online ...@latest`,因此发布新版 CLI 后,下一次执行会更新本地 SKILL.md。传入 `--skill-dir /absolute/skill-directory` 可精确定位安装目录;安装器已自动写入该参数。未传入时仅检查 `~/.agents/skills/overseas-app-research` 和 `${CODEX_HOME:-~/.codex}/skills/overseas-app-research` 中现有的 CLI 入口,不创建新 skill,不覆盖手工维护的其他实现或符号链接,也不降级更新的入口。
24
24
 
@@ -44,9 +44,11 @@ npx -y --prefer-online global-app-skill@latest run preflight --mode research --r
44
44
  | `resources/overseas-app-research/scripts/` | 原有 Python 脚本及回归测试 |
45
45
  | `resources/overseas-app-research/examples/` | 业务报告模板 |
46
46
  | `scripts/build-resources.mjs` | 生成确定性的资源包与清单 |
47
- | `releases/` | GitHub 托管的最新清单与按哈希命名的资源包 |
47
+ | `.github/workflows/publish.yml` | tag 触发的构建、GitHub Release 和 npm 发布 |
48
+ | `scripts/release.mjs` | tag 校验、打包、校验和及发布资产核对 |
49
+ | `dist/release/` | 本次构建的 Release 资产,不提交到 Git |
48
50
 
49
- 分发源为 `https://raw.githubusercontent.com/shiguang-lab/global-app-skill/main/releases/latest.json`。npm 包仅包含 CLI 与薄 skill,工作流资源独立从 GitHub 更新,无需为文档或 Python 脚本修改发布 npm 新版本。
51
+ 分发源为 `https://github.com/shiguang-lab/global-app-skill/releases/latest/download/latest.json`。资源源码在仓库的 `resources/`,正式生成包由 Actions 上传到 GitHub Release。清单中的 `releaseTag` 决定资源包的固定下载地址,因此 latest 切换不会混用不同 Release 的清单和包。npm 包仅包含 CLI 与薄 skill;工作流资源可独立发布,不要求发布 npm 新版本。
50
52
 
51
53
  缓存默认位于 `~/.cache/global-app-skill/releases/<resourceHash>`,可用 `GLOBAL_APP_SKILL_CACHE` 或 `--cache-dir` 指定。下载先在临时目录完整校验,再发布快照。网络或完整性失败会报错,不静默使用旧资源;损坏的固定快照拒绝执行,重新 resolve 可修复。旧快照保留供现有任务使用。运行产物写入工作区,不写回资源缓存。
52
54
 
@@ -55,14 +57,44 @@ npx -y --prefer-online global-app-skill@latest run preflight --mode research --r
55
57
  在当前仓库编辑资源后:
56
58
 
57
59
  ```sh
58
- npm run resources:build
59
60
  npm test
60
61
  npm run test:python
61
- npm run resources:check
62
+ npm run release:prepare
62
63
  ```
63
64
 
64
- Python 回归测试中的长图部分需要 Pillow。CLI 无第三方 Node 依赖。`--feed` 可指定 HTTPS 清单,测试允许 loopback HTTP,用于在发布前验证同一协议。
65
+ Python 回归测试中的长图部分需要 Pillow。CLI 无第三方 Node 依赖。`--feed` 可指定 HTTPS 清单,测试允许 loopback HTTP。`release:prepare` 在本地预演与 Actions 相同的构建,默认 tag 为 `v<package.json.version>`,也可指定 `RELEASE_TAG`。构建结果进入忽略的 `dist/release/`,不手工上传。
65
66
 
66
- 将资源源文件、`releases/latest.json` 和新资源包一起提交并 push 到 GitHub 的 `main`。保留旧资源包,避免读到旧清单的客户端遇到 404。只修改资源时到此即可,后续任务自动获取最新快照;每个快照完整包含 references、scripts 和 examples,删除的源文件不会残留到新快照。
67
+ ### 发布 CLI、薄入口与资源
67
68
 
68
- 修改 CLI 或薄入口时更新 `package.json` 的版本,完成检查并推送代码后执行 `npm publish`。`prepublishOnly` 会执行 Node/Python 测试和资源一致性检查。初次发布需要 npm 登录和账号所要求的发布认证;GitHub 仓库需公开可读,才能使用默认免认证资源源。当前项目未授予开源许可证,npm 元数据使用 `UNLICENSED`。
69
+ 修改源码并更新 `package.json` 的版本,提交、push 后创建对应版本的 tag。例如版本为 `0.1.3` 时:
70
+
71
+ ```sh
72
+ git tag v0.1.3
73
+ git push origin v0.1.3
74
+ ```
75
+
76
+ `publish.yml` 校验 tag 与 package.json 一致,执行 Node/Python 测试,从 tag 源码生成以下资产:
77
+
78
+ - `latest.json`:资源清单、Release tag、最低 CLI 版本、逐文件哈希。
79
+ - `<resourceHash>.json.gz`:references、scripts、examples 的完整快照。
80
+ - `global-app-skill.tgz`:同一 tag 构建的 CLI 安装包。
81
+ - `SHA256SUMS`:上述三个资产的校验和。
82
+
83
+ Actions 先创建草稿 Release,上传所有已验证资产后发布为 latest,随后把同一个 CLI tgz 发布到 npm。npm 发布失败不会删除已经完整发布的 GitHub Release。重跑时先核对已发布资产;正式 Release 不覆盖不同字节的包。
84
+
85
+ ### 仅发布资源
86
+
87
+ 修改资源源码并提交,创建独立的资源 tag,例如:
88
+
89
+ ```sh
90
+ git tag resources-v0.1.4
91
+ git push origin resources-v0.1.4
92
+ ```
93
+
94
+ 该 tag 只发布 GitHub Release,跳过 npm。客户端下次 resolve 自动获取新资源。资源工作流仍包含完整快照,删除的源文件不会残留到新快照。已有任务继续使用其固定快照。不要删除历史 Release 的资源资产。
95
+
96
+ ### 一次性配置 npm 发布认证
97
+
98
+ 在 GitHub 仓库 Actions secrets 中配置拥有 `global-app-skill` 发布权限的 `NPM_TOKEN`,工作流将其绑定到 npm 使用的 `NODE_AUTH_TOKEN`,不读取或复制本机 token。也可在 npm 包 Settings 中配置 GitHub Actions Trusted Publisher:组织 `shiguang-lab`、仓库 `global-app-skill`、工作流文件 `publish.yml`、允许 `npm publish`,不设置 environment。工作流保留 `id-token: write`,供 OIDC 与 provenance 使用。GitHub Release 使用 Actions 自带的 `GITHUB_TOKEN` 与 `contents: write`。
99
+
100
+ 以后只推 tag,由 Actions 发布,不在本机执行 `npm publish`。当前项目未授予开源许可证,npm 元数据使用 `UNLICENSED`。
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "global-app-skill",
3
- "version": "0.1.2",
3
+ "version": "0.1.4",
4
4
  "description": "Thin skill bootstrap and GitHub resource runner for overseas Android app research",
5
5
  "type": "module",
6
6
  "license": "UNLICENSED",
@@ -17,6 +17,7 @@
17
17
  "test:python": "python3 -m unittest discover -s resources/overseas-app-research/scripts -p 'test_*.py'",
18
18
  "resources:build": "node scripts/build-resources.mjs",
19
19
  "resources:check": "node scripts/build-resources.mjs --check",
20
- "prepublishOnly": "npm test && npm run test:python && npm run resources:check"
20
+ "release:prepare": "node scripts/release.mjs",
21
+ "prepublishOnly": "npm test && npm run test:python && npm run resources:build && npm run resources:check"
21
22
  }
22
23
  }
package/src/protocol.mjs CHANGED
@@ -2,7 +2,9 @@ import { createHash } from 'node:crypto';
2
2
  import { readFileSync } from 'node:fs';
3
3
 
4
4
  export const VERSION = JSON.parse(readFileSync(new URL('../package.json', import.meta.url))).version;
5
- export const FEED = 'https://raw.githubusercontent.com/shiguang-lab/global-app-skill/main/releases/latest.json';
5
+ export const RELEASE_BASE = 'https://github.com/shiguang-lab/global-app-skill/releases';
6
+ export const FEED = `${RELEASE_BASE}/latest/download/latest.json`;
7
+ export const RELEASE_TAG = /^(?:v|resources-v)(?:0|[1-9]\d*)\.(?:0|[1-9]\d*)\.(?:0|[1-9]\d*)$/;
6
8
  export const MAX_BYTES = 16 * 1024 * 1024;
7
9
  export const MODES = {
8
10
  download: 'references/download-workflow.md',
@@ -20,7 +22,7 @@ export function validateMode(mode) {
20
22
 
21
23
  export function validateManifest(manifest) {
22
24
  const hash = /^[a-f0-9]{64}$/;
23
- if (manifest?.schemaVersion !== 1 || !hash.test(manifest.resourceHash) ||
25
+ if (manifest?.schemaVersion !== 1 || !RELEASE_TAG.test(manifest.releaseTag) || !hash.test(manifest.resourceHash) ||
24
26
  !/^\d+\.\d+\.\d+$/.test(manifest.minCliVersion) ||
25
27
  !Array.isArray(manifest.files) || !manifest.files.length || manifest.files.length > 1024 ||
26
28
  manifest.bundle !== `${manifest.resourceHash}.json.gz` || !hash.test(manifest.sha256) ||
@@ -50,3 +52,13 @@ export function validateManifest(manifest) {
50
52
  }
51
53
  return manifest;
52
54
  }
55
+
56
+ export function bundleUrl(manifest, feed) {
57
+ const url = new URL(feed);
58
+ if (url.origin === 'https://github.com' && url.pathname.startsWith('/shiguang-lab/global-app-skill/releases/')) {
59
+ // Pin the bundle to the manifest's release, even if "latest" advances
60
+ // between fetching the manifest and fetching its archive.
61
+ return new URL(`${RELEASE_BASE}/download/${manifest.releaseTag}/${manifest.bundle}`);
62
+ }
63
+ return new URL(manifest.bundle, url);
64
+ }
package/src/resources.mjs CHANGED
@@ -3,7 +3,7 @@ import { homedir } from 'node:os';
3
3
  import path from 'node:path';
4
4
  import { setTimeout as delay } from 'node:timers/promises';
5
5
  import { gunzipSync } from 'node:zlib';
6
- import { FEED, MAX_BYTES, MODES, sha256, validateManifest, validateMode } from './protocol.mjs';
6
+ import { bundleUrl, FEED, MAX_BYTES, MODES, sha256, validateManifest, validateMode } from './protocol.mjs';
7
7
 
8
8
  async function download(url, limit) {
9
9
  const response = await fetch(url, { cache: 'no-store', signal: AbortSignal.timeout(30000) });
@@ -61,7 +61,7 @@ export async function resolveContext({ mode, feed = FEED, cacheDir, progress = (
61
61
  let updated = false;
62
62
  if (!(await validSnapshot(resourceRoot, manifest.resourceHash))) {
63
63
  progress(`Downloading and verifying ${manifest.files.length} resource files`);
64
- const bytes = await download(new URL(manifest.bundle, url), MAX_BYTES);
64
+ const bytes = await download(bundleUrl(manifest, url), MAX_BYTES);
65
65
  if (bytes.length !== manifest.size || sha256(bytes) !== manifest.sha256) throw new Error('Resource bundle integrity check failed');
66
66
  const bundle = JSON.parse(gunzipSync(bytes, { maxOutputLength: MAX_BYTES * 2 }).toString('utf8'));
67
67
  if (!Array.isArray(bundle.files) || bundle.files.length !== manifest.files.length) throw new Error('Invalid resource bundle');
@@ -109,7 +109,7 @@ export async function resolveContext({ mode, feed = FEED, cacheDir, progress = (
109
109
  progress(updated ? 'Resource snapshot ready' : 'Verified cached snapshot is current');
110
110
  const mustRead = ['references/cli.md', 'references/guide.md', 'references/orchestration.md', MODES[mode]]
111
111
  .map(name => path.join(resourceRoot, name));
112
- return { ok: true, source: url.hostname === 'raw.githubusercontent.com' ? 'github' : 'custom', feed: url.href,
113
- mode, resourceHash: manifest.resourceHash, resourceRoot,
112
+ return { ok: true, source: url.origin === 'https://github.com' ? 'github' : 'custom', feed: url.href,
113
+ mode, releaseTag: manifest.releaseTag, resourceHash: manifest.resourceHash, resourceRoot,
114
114
  referencePath: path.join(resourceRoot, 'references/guide.md'), mustRead, updated };
115
115
  }