@playcraft/cli 0.0.56 → 0.0.58

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.
Files changed (228) hide show
  1. package/README.md +41 -26
  2. package/REMIX.md +17 -5
  3. package/dist/cli-root-help.js +1 -0
  4. package/dist/commands/build-all.js +10 -9
  5. package/dist/commands/build.js +13 -12
  6. package/dist/commands/create.js +26 -33
  7. package/dist/commands/platform-skills.generated.js +19 -0
  8. package/dist/commands/remix.js +48 -2
  9. package/dist/commands/skills.js +2 -0
  10. package/dist/commands/tools-generation.js +1 -1
  11. package/dist/commands/workspace-runtime.js +54 -0
  12. package/dist/index.js +3 -1
  13. package/dist/project-skills/commands.js +72 -0
  14. package/dist/project-skills/lifecycle.js +28 -0
  15. package/dist/project-skills/local-run.js +112 -0
  16. package/dist/project-skills/local-store.js +149 -0
  17. package/dist/project-skills/messages.js +72 -0
  18. package/dist/project-skills/reconcile.js +62 -0
  19. package/dist/project-skills/remote-cache.js +51 -0
  20. package/dist/project-skills/validation.js +19 -0
  21. package/dist/remix/clone.js +4 -1
  22. package/dist/remix/init-template.js +2 -0
  23. package/dist/remix/local-git.js +56 -0
  24. package/dist/remix/pull.js +66 -0
  25. package/dist/remix/push.js +90 -0
  26. package/dist/utils/agent-api-client.js +54 -17
  27. package/dist/utils/tool-operation-journal.js +54 -0
  28. package/dist/workspace-runtime/codex/app-server.js +395 -0
  29. package/dist/workspace-runtime/codex/config-toml.js +25 -0
  30. package/dist/workspace-runtime/codex/jsonrpc-stdio.js +106 -0
  31. package/dist/workspace-runtime/codex/loopback.js +59 -0
  32. package/dist/workspace-runtime/codex/native-adapter.js +3 -0
  33. package/dist/workspace-runtime/main.js +10 -0
  34. package/dist/workspace-runtime/persistence/journal.js +350 -0
  35. package/dist/workspace-runtime/processes/managed-writes.js +69 -0
  36. package/dist/workspace-runtime/serve.js +111 -0
  37. package/dist/workspace-runtime/server/auth.js +21 -0
  38. package/dist/workspace-runtime/server/dispatch.js +861 -0
  39. package/dist/workspace-runtime/server/execution-group.js +88 -0
  40. package/dist/workspace-runtime/server/http.js +561 -0
  41. package/dist/workspace-runtime/server/lock.js +53 -0
  42. package/dist/workspace-runtime/server/types.js +1 -0
  43. package/dist/workspace-runtime/workspaces/context-error.js +2 -0
  44. package/dist/workspace-runtime/workspaces/file-snapshot.js +213 -0
  45. package/dist/workspace-runtime/workspaces/files.js +143 -0
  46. package/dist/workspace-runtime/workspaces/git.js +242 -0
  47. package/dist/workspace-runtime/workspaces/json5-edit.js +170 -0
  48. package/dist/workspace-runtime/workspaces/parameters.js +272 -0
  49. package/dist/workspace-runtime/workspaces/prepare.js +364 -0
  50. package/dist/workspace-runtime/workspaces/registry.js +107 -0
  51. package/dist/workspace-runtime/workspaces/revisions.js +31 -0
  52. package/package.json +6 -3
  53. package/project-template-v2/.claude/agents/artist.md +82 -0
  54. package/project-template-v2/.claude/agents/developer.md +153 -0
  55. package/project-template-v2/.claude/agents/game-designer.md +264 -0
  56. package/project-template-v2/.claude/agents/refs/artist-art-style-catalog.md +533 -0
  57. package/project-template-v2/.claude/agents/refs/artist-color-audio-recipes.md +153 -0
  58. package/project-template-v2/.claude/agents/refs/artist-dimension-axis.md +27 -0
  59. package/project-template-v2/.claude/agents/refs/artist-master-composite-recipes.md +208 -0
  60. package/project-template-v2/.claude/agents/refs/atom-skill-library.md +81 -0
  61. package/project-template-v2/.claude/agents/refs/developer-impl-cookbook.md +432 -0
  62. package/project-template-v2/.claude/agents/refs/framework-5-component-filter.md +252 -0
  63. package/project-template-v2/.claude/agents/refs/framework-game-feel-juice.md +266 -0
  64. package/project-template-v2/.claude/agents/refs/framework-mda.md +147 -0
  65. package/project-template-v2/.claude/agents/refs/game-designer-gameplay-sufficiency.md +123 -0
  66. package/project-template-v2/.claude/agents/refs/ta-3d-flip-recipe.md +88 -0
  67. package/project-template-v2/.claude/agents/refs/ta-atlas-deliverable-standard.md +67 -0
  68. package/project-template-v2/.claude/agents/refs/ta-batch-pipeline-recipes.md +120 -0
  69. package/project-template-v2/.claude/agents/refs/ta-image-generation-detail.md +300 -0
  70. package/project-template-v2/.claude/agents/refs/ta-image-ops-reference.md +495 -0
  71. package/project-template-v2/.claude/agents/refs/ta-pipeline-cookbook.md +1141 -0
  72. package/project-template-v2/.claude/agents/refs/ta-tools-reference.md +111 -0
  73. package/project-template-v2/.claude/agents/refs/ta-vfx-preset-catalog.md +365 -0
  74. package/project-template-v2/.claude/agents/refs/threejs-cannon-pitfalls.md +412 -0
  75. package/project-template-v2/.claude/agents/reviewer.md +75 -0
  76. package/project-template-v2/.claude/agents/technical-artist.md +86 -0
  77. package/project-template-v2/.claude/hooks/snapshot-milestone.mjs +243 -0
  78. package/project-template-v2/.claude/hooks/user-prompt.mjs +133 -0
  79. package/project-template-v2/.claude/settings.json +33 -0
  80. package/project-template-v2/.claude/skills/brainstorming/SKILL.md +161 -0
  81. package/project-template-v2/.claude/skills/brainstorming/scripts/frame-template.html +270 -0
  82. package/project-template-v2/.claude/skills/brainstorming/scripts/helper.js +177 -0
  83. package/project-template-v2/.claude/skills/brainstorming/scripts/server.cjs +354 -0
  84. package/project-template-v2/.claude/skills/brainstorming/scripts/start-server.sh +148 -0
  85. package/project-template-v2/.claude/skills/brainstorming/scripts/stop-server.sh +56 -0
  86. package/project-template-v2/.claude/skills/brainstorming/scripts/wait-for-selection.sh +62 -0
  87. package/project-template-v2/.claude/skills/brainstorming/spec-document-reviewer-prompt.md +49 -0
  88. package/project-template-v2/.claude/skills/brainstorming/visual-companion.md +309 -0
  89. package/project-template-v2/.claude/skills/playcraft-ad-psychology/SKILL.md +182 -0
  90. package/project-template-v2/.claude/skills/playcraft-art-style-guide/SKILL.md +123 -0
  91. package/project-template-v2/.claude/skills/playcraft-asset-state-sheet/SKILL.md +205 -0
  92. package/project-template-v2/.claude/skills/playcraft-audio-generation/SKILL.md +280 -0
  93. package/project-template-v2/.claude/skills/playcraft-batch-pipeline/SKILL.md +184 -0
  94. package/project-template-v2/.claude/skills/playcraft-build-optimizer/SKILL.md +306 -0
  95. package/project-template-v2/.claude/skills/playcraft-image-generation/SKILL.md +298 -0
  96. package/project-template-v2/.claude/skills/playcraft-image-generation/reference/build-sprite-sheet.template.mjs +123 -0
  97. package/project-template-v2/.claude/skills/playcraft-image-generation/reference/compare-style.template.mjs +254 -0
  98. package/project-template-v2/.claude/skills/playcraft-image-generation/reference/gen-batch-sprite.template.mjs +324 -0
  99. package/project-template-v2/.claude/skills/playcraft-image-generation/reference/gen-batch.template.mjs +97 -0
  100. package/project-template-v2/.claude/skills/playcraft-image-generation/reference/gen-edit-variants.template.mjs +118 -0
  101. package/project-template-v2/.claude/skills/playcraft-image-generation/reference/process-batch.template.mjs +137 -0
  102. package/project-template-v2/.claude/skills/playcraft-image-generation/reference/prompt-cookbook.md +397 -0
  103. package/project-template-v2/.claude/skills/playcraft-image-generation/reference/validate-sprite-sheet.template.mjs +296 -0
  104. package/project-template-v2/.claude/skills/playcraft-image-ops/SKILL.md +122 -0
  105. package/project-template-v2/.claude/skills/playcraft-image-processing/SKILL.md +219 -0
  106. package/project-template-v2/.claude/skills/playcraft-masking/SKILL.md +373 -0
  107. package/project-template-v2/.claude/skills/playcraft-playable-optimization/SKILL.md +161 -0
  108. package/project-template-v2/.claude/skills/playcraft-research/SKILL.md +215 -0
  109. package/project-template-v2/.claude/skills/playcraft-skill-recommender/SKILL.md +382 -0
  110. package/project-template-v2/.claude/skills/playcraft-sprite-generation/SKILL.md +423 -0
  111. package/project-template-v2/.claude/skills/playcraft-sprite-remix/SKILL.md +158 -0
  112. package/project-template-v2/.claude/skills/playcraft-sprite-sheet/SKILL.md +100 -0
  113. package/project-template-v2/.claude/skills/playcraft-storyboard/SKILL.md +167 -0
  114. package/project-template-v2/.claude/skills/playcraft-style-qa/SKILL.md +270 -0
  115. package/project-template-v2/.claude/skills/playcraft-text-rendering/SKILL.md +236 -0
  116. package/project-template-v2/.claude/skills/playcraft-vfx-animation/SKILL.md +130 -0
  117. package/project-template-v2/.claude/skills/playwright-cli/SKILL.md +390 -0
  118. package/project-template-v2/.claude/skills/playwright-cli/references/element-attributes.md +23 -0
  119. package/project-template-v2/.claude/skills/playwright-cli/references/playwright-tests.md +39 -0
  120. package/project-template-v2/.claude/skills/playwright-cli/references/request-mocking.md +87 -0
  121. package/project-template-v2/.claude/skills/playwright-cli/references/running-code.md +240 -0
  122. package/project-template-v2/.claude/skills/playwright-cli/references/session-management.md +226 -0
  123. package/project-template-v2/.claude/skills/playwright-cli/references/spec-driven-testing.md +308 -0
  124. package/project-template-v2/.claude/skills/playwright-cli/references/storage-state.md +275 -0
  125. package/project-template-v2/.claude/skills/playwright-cli/references/test-generation.md +134 -0
  126. package/project-template-v2/.claude/skills/playwright-cli/references/tracing.md +142 -0
  127. package/project-template-v2/.claude/skills/playwright-cli/references/video-recording.md +153 -0
  128. package/project-template-v2/.claude/skills/session-analyzer/SKILL.md +386 -0
  129. package/project-template-v2/.claude/skills/session-analyzer/scripts/execution-breakdown.mjs +182 -0
  130. package/project-template-v2/.claude/skills/session-analyzer/scripts/find-turns.mjs +72 -0
  131. package/project-template-v2/.claude/skills/session-analyzer/scripts/heavy-output.mjs +121 -0
  132. package/project-template-v2/.claude/skills/session-analyzer/scripts/resolve-session.mjs +102 -0
  133. package/project-template-v2/.claude/skills/session-analyzer/scripts/subagent-stats.mjs +127 -0
  134. package/project-template-v2/.claude/skills/session-analyzer/scripts/subagent-tool-timeline.mjs +106 -0
  135. package/project-template-v2/.claude/skills/session-analyzer/scripts/time-gaps.mjs +128 -0
  136. package/project-template-v2/.claude/skills/session-analyzer/scripts/turn-timeline.mjs +67 -0
  137. package/project-template-v2/.claude/snapshot.mjs +263 -0
  138. package/project-template-v2/.playcraft/skills.lock.json +152 -0
  139. package/project-template-v2/CLAUDE.md +146 -0
  140. package/project-template-v2/assets/audio/bgm/.gitkeep +0 -0
  141. package/project-template-v2/assets/audio/sfx/.gitkeep +0 -0
  142. package/project-template-v2/assets/bundles/.gitkeep +0 -0
  143. package/project-template-v2/assets/images/bg/.gitkeep +0 -0
  144. package/project-template-v2/assets/images/reference/.gitkeep +0 -0
  145. package/project-template-v2/assets/images/storyboard/.gitkeep +0 -0
  146. package/project-template-v2/assets/images/tiles/.gitkeep +0 -0
  147. package/project-template-v2/assets/images/ui/.gitkeep +0 -0
  148. package/project-template-v2/assets/images/vfx/.gitkeep +0 -0
  149. package/project-template-v2/assets/models/.gitkeep +0 -0
  150. package/project-template-v2/docs/harness/iteration-1/context-flow.md +254 -0
  151. package/project-template-v2/docs/harness/iteration-1/generate-flow.md +91 -0
  152. package/project-template-v2/docs/harness/iteration-1/ideate-flow.md +214 -0
  153. package/project-template-v2/docs/harness/iteration-1/optimize-flow.md +75 -0
  154. package/project-template-v2/docs/harness/iteration-1/wrapup-flow.md +63 -0
  155. package/project-template-v2/docs/harness/iteration-2/context-flow.md +223 -0
  156. package/project-template-v2/docs/harness/iteration-2/generate-flow.md +129 -0
  157. package/project-template-v2/docs/harness/iteration-2/ideate-flow.md +267 -0
  158. package/project-template-v2/docs/harness/iteration-2/optimize-flow.md +164 -0
  159. package/project-template-v2/docs/harness/iteration-2/wrapup-flow.md +115 -0
  160. package/project-template-v2/docs/harness/orchestrator-flow.md +364 -0
  161. package/project-template-v2/docs/project-state.json +60 -0
  162. package/project-template-v2/docs/project-state.md +72 -0
  163. package/project-template-v2/docs/standards/README.md +225 -0
  164. package/project-template-v2/docs/standards/agent-behavior-standards.md +174 -0
  165. package/project-template-v2/docs/standards/artifacts/design-brief.md +19 -0
  166. package/project-template-v2/docs/standards/artifacts/design.md +22 -0
  167. package/project-template-v2/docs/standards/artifacts/game-code.md +41 -0
  168. package/project-template-v2/docs/standards/artifacts/todo-list.md +41 -0
  169. package/project-template-v2/docs/standards/iter1-agent-behavior-standards.md +343 -0
  170. package/project-template-v2/game/index.ts +18 -0
  171. package/project-template-v2/globals.d.ts +51 -0
  172. package/project-template-v2/index.css +34 -0
  173. package/project-template-v2/index.html +18 -0
  174. package/project-template-v2/main.ts +9 -0
  175. package/project-template-v2/package.json +46 -0
  176. package/project-template-v2/skills/_shared/scripts/dispatch-clear.mjs +31 -0
  177. package/project-template-v2/skills/_shared/scripts/dispatch-set.mjs +86 -0
  178. package/project-template-v2/skills/_shared/scripts/dod-check.mjs +153 -0
  179. package/project-template-v2/skills/_shared/scripts/handoff-append.mjs +70 -0
  180. package/project-template-v2/skills/_shared/scripts/lib/validator-artifacts.mjs +91 -0
  181. package/project-template-v2/skills/_shared/scripts/pipeline/dod-config.mjs +131 -0
  182. package/project-template-v2/skills/_shared/scripts/pipeline/index.mjs +80 -0
  183. package/project-template-v2/skills/_shared/scripts/pipeline/iteration-1-core.mjs +90 -0
  184. package/project-template-v2/skills/_shared/scripts/pipeline/iteration-2-wrap.mjs +93 -0
  185. package/project-template-v2/skills/_shared/scripts/pipeline/iteration-3-visual.mjs +23 -0
  186. package/project-template-v2/skills/_shared/scripts/render-project-state.mjs +230 -0
  187. package/project-template-v2/skills/_shared/scripts/state-advance-stage.mjs +58 -0
  188. package/project-template-v2/skills/_shared/scripts/state-get.mjs +339 -0
  189. package/project-template-v2/skills/_shared/scripts/state-handoff.mjs +39 -0
  190. package/project-template-v2/skills/_shared/scripts/state-set.mjs +94 -0
  191. package/project-template-v2/skills/_shared/scripts/state-store.mjs +783 -0
  192. package/project-template-v2/skills/_shared/scripts/todo-add.mjs +88 -0
  193. package/project-template-v2/skills/_shared/scripts/todo-get.mjs +130 -0
  194. package/project-template-v2/skills/_shared/scripts/todo-remove.mjs +33 -0
  195. package/project-template-v2/skills/_shared/scripts/todo-set.mjs +47 -0
  196. package/project-template-v2/skills/_shared/scripts/verify-asset-code-sync.mjs +268 -0
  197. package/project-template-v2/skills/_shared/scripts/verify-env.mjs +285 -0
  198. package/project-template-v2/skills/_shared/scripts/verify-placeholders.mjs +161 -0
  199. package/project-template-v2/skills/playable-autoplay/SKILL.md +176 -0
  200. package/project-template-v2/skills/playable-autoplay/agents/openai.yaml +4 -0
  201. package/project-template-v2/skills/playable-debug/SKILL.md +116 -0
  202. package/project-template-v2/skills/playable-debug/agents/openai.yaml +4 -0
  203. package/project-template-v2/skills/playable-debug/references/debug-config.md +40 -0
  204. package/project-template-v2/skills/playable-record/SKILL.md +140 -0
  205. package/project-template-v2/skills/playable-record/scripts/lib/dev-server.mjs +104 -0
  206. package/project-template-v2/skills/playable-record/scripts/lib/record-audio-bridge.js +141 -0
  207. package/project-template-v2/skills/playable-record/scripts/record-playable.mjs +425 -0
  208. package/project-template-v2/skills/playable-record/scripts/verify-contract.mjs +261 -0
  209. package/project-template-v2/skills/playable-record/scripts/verify-firstwin.mjs +398 -0
  210. package/project-template-v2/skills/playable-record/scripts/verify-fusion.mjs +322 -0
  211. package/project-template-v2/skills/playable-record/scripts/verify-lifecycle.mjs +105 -0
  212. package/project-template-v2/skills/playable-record/scripts/verify-vlm-video.mjs +230 -0
  213. package/project-template-v2/skills/playable-validate/SKILL.md +61 -0
  214. package/project-template-v2/skills/playable-validate/validation-rules.md +14 -0
  215. package/project-template-v2/skills/playable-verify-ui/SKILL.md +63 -0
  216. package/project-template-v2/skills/playable-verify-ui/scripts/lib/__init__.py +1 -0
  217. package/project-template-v2/skills/playable-verify-ui/scripts/lib/config_expr.js +218 -0
  218. package/project-template-v2/skills/playable-verify-ui/scripts/lib/node_utils.js +51 -0
  219. package/project-template-v2/skills/playable-verify-ui/scripts/lib/profile_loader.py +59 -0
  220. package/project-template-v2/skills/playable-verify-ui/scripts/lib/report.py +106 -0
  221. package/project-template-v2/skills/playable-verify-ui/scripts/lib/runtime_contract.js +116 -0
  222. package/project-template-v2/skills/playable-verify-ui/scripts/verify_fetch_antipatterns.py +59 -0
  223. package/project-template-v2/skills/playable-verify-ui/scripts/verify_hardcoded_layout.js +64 -0
  224. package/project-template-v2/skills/playable-verify-ui/scripts/verify_runtime_contract.js +58 -0
  225. package/project-template-v2/skills/playable-verify-ui/scripts/verify_text_overlap.py +333 -0
  226. package/project-template-v2/ta-workspace/scripts/.gitkeep +0 -0
  227. package/project-template-v2/tsconfig.json +20 -0
  228. package/project-template-v2/vite.config.ts +27 -0
@@ -0,0 +1,56 @@
1
+ import { spawnSync } from 'node:child_process';
2
+ import { existsSync, readFileSync } from 'node:fs';
3
+ import path from 'node:path';
4
+ /** @internal exported for tests */
5
+ export function readRemixLocalMeta(repoDir) {
6
+ const metaPath = path.join(repoDir, '.playcraft.json');
7
+ if (!existsSync(metaPath)) {
8
+ throw new Error(`未找到 .playcraft.json(当前目录:${repoDir})。请先在该目录执行 playcraft remix clone,或 cd 到 clone 得到的目录。`);
9
+ }
10
+ try {
11
+ const raw = JSON.parse(readFileSync(metaPath, 'utf8'));
12
+ const projectId = String(raw.projectId || '').trim();
13
+ const branch = String(raw.branch || '').trim();
14
+ const cloneUrl = String(raw.cloneUrl || '').trim();
15
+ if (!projectId || !branch || !cloneUrl) {
16
+ throw new Error('invalid_meta');
17
+ }
18
+ return {
19
+ schemaVersion: '1.0',
20
+ projectId,
21
+ branch,
22
+ cloneUrl,
23
+ clonedAt: typeof raw.clonedAt === 'string' ? raw.clonedAt : new Date().toISOString(),
24
+ };
25
+ }
26
+ catch (error) {
27
+ if (error instanceof Error && error.message.includes('.playcraft.json'))
28
+ throw error;
29
+ throw new Error(`.playcraft.json 无效或损坏:${metaPath}`);
30
+ }
31
+ }
32
+ /** @internal exported for tests */
33
+ export function assertPlaycraftProxyRemote(cloneUrl) {
34
+ if (!cloneUrl.includes('/api/git/projects/')) {
35
+ throw new Error(`当前 .playcraft.json 的 cloneUrl 不是 PlayCraft Git 代理地址。\n` +
36
+ `请用 playcraft remix clone 重新克隆,不要改 remote 到工蜂 / 其它 Git 主机。\n` +
37
+ `cloneUrl: ${cloneUrl}`);
38
+ }
39
+ }
40
+ /**
41
+ * 运行带 PlayCraft PAT 的 git 子命令:注入 Bearer,并清空 credential.helper,
42
+ * 避免 Windows Git Credential Manager 弹窗。
43
+ */
44
+ export function runAuthenticatedGit(gitCommandAndArgs, options) {
45
+ return spawnSync('git', [
46
+ '-c',
47
+ 'credential.helper=',
48
+ '-c',
49
+ `http.extraHeader=Authorization: Bearer ${options.token}`,
50
+ ...gitCommandAndArgs,
51
+ ], {
52
+ cwd: options.cwd,
53
+ encoding: 'utf8',
54
+ stdio: options.pipe ? 'pipe' : 'inherit',
55
+ });
56
+ }
@@ -0,0 +1,66 @@
1
+ import { prepareLocalSkillLifecycle } from '../project-skills/lifecycle.js';
2
+ import { spawnSync } from 'node:child_process';
3
+ import { existsSync } from 'node:fs';
4
+ import path from 'node:path';
5
+ import { PlayCraftHttpClient } from '../utils/playcraft-http-client.js';
6
+ import { assertPlaycraftProxyRemote, readRemixLocalMeta, runAuthenticatedGit, } from './local-git.js';
7
+ /**
8
+ * 从 PlayCraft Git 代理拉取当前 Remix。
9
+ * 与 remix push 相同:注入 Bearer PAT,并禁用 credential.helper(避免 Windows GCM)。
10
+ */
11
+ export async function remixPull(options = {}) {
12
+ const repoDir = path.resolve(options.dir?.trim() || process.cwd());
13
+ if (!existsSync(path.join(repoDir, '.git'))) {
14
+ throw new Error(`目录不是 Git 仓库:${repoDir}`);
15
+ }
16
+ const meta = readRemixLocalMeta(repoDir);
17
+ assertPlaycraftProxyRemote(meta.cloneUrl);
18
+ const client = new PlayCraftHttpClient({
19
+ url: options.url,
20
+ token: options.token,
21
+ });
22
+ const gitArgs = options.gitArgs?.length ? options.gitArgs : [];
23
+ const effectiveArgs = gitArgs.length > 0
24
+ ? gitArgs
25
+ : (() => {
26
+ const upstream = spawnSync('git', ['rev-parse', '--abbrev-ref', '--symbolic-full-name', '@{u}'], {
27
+ cwd: repoDir,
28
+ encoding: 'utf8',
29
+ });
30
+ if (upstream.status === 0 && upstream.stdout.trim()) {
31
+ return [];
32
+ }
33
+ return ['origin', meta.branch];
34
+ })();
35
+ if (!options.json) {
36
+ process.stderr.write(`[pull] ${meta.cloneUrl}\n`);
37
+ process.stderr.write(`[pull] branch=${meta.branch} dir=${repoDir}\n`);
38
+ }
39
+ const result = runAuthenticatedGit(['pull', ...effectiveArgs], {
40
+ cwd: repoDir,
41
+ token: client.token,
42
+ pipe: Boolean(options.json),
43
+ });
44
+ if (result.status !== 0) {
45
+ const detail = [result.stderr, result.stdout].filter(Boolean).join('\n').trim();
46
+ throw new Error(detail || `git pull 失败(exit ${result.status})`);
47
+ }
48
+ await prepareLocalSkillLifecycle(repoDir, 'checkout');
49
+ if (options.json) {
50
+ console.log(JSON.stringify({
51
+ schemaVersion: '1.0',
52
+ ok: true,
53
+ command: 'remix.pull',
54
+ requestId: client.requestId,
55
+ data: {
56
+ projectId: meta.projectId,
57
+ branch: meta.branch,
58
+ cloneUrl: meta.cloneUrl,
59
+ dir: repoDir,
60
+ gitArgs: effectiveArgs,
61
+ },
62
+ }));
63
+ return;
64
+ }
65
+ console.log(`✓ 已从 Remix 分支 ${meta.branch} 拉取`);
66
+ }
@@ -0,0 +1,90 @@
1
+ import { prepareLocalSkillLifecycle } from '../project-skills/lifecycle.js';
2
+ import { reconcileLocalProjectSkills } from '../project-skills/reconcile.js';
3
+ import { projectSkillsMessage as skillMessage } from '../project-skills/messages.js';
4
+ import { spawnSync } from 'node:child_process';
5
+ import { existsSync } from 'node:fs';
6
+ import path from 'node:path';
7
+ import { PlayCraftHttpClient } from '../utils/playcraft-http-client.js';
8
+ import { assertPlaycraftProxyRemote, readRemixLocalMeta, runAuthenticatedGit, } from './local-git.js';
9
+ export { assertPlaycraftProxyRemote, readRemixLocalMeta } from './local-git.js';
10
+ /**
11
+ * Push 当前 Remix 仓库到 PlayCraft Git 代理。
12
+ *
13
+ * 故意不用 `git push` + credential.helper:Windows 上 Git Credential Manager
14
+ * 经常抢走鉴权并弹窗。这里与 clone 一样,用一次性 `http.extraHeader` 注入 PAT,
15
+ * 并清空本命令的 credential.helper,避免 GCM 介入。
16
+ */
17
+ export async function remixPush(options = {}) {
18
+ const repoDir = path.resolve(options.dir?.trim() || process.cwd());
19
+ if (!existsSync(path.join(repoDir, '.git'))) {
20
+ throw new Error(`目录不是 Git 仓库:${repoDir}`);
21
+ }
22
+ const meta = readRemixLocalMeta(repoDir);
23
+ assertPlaycraftProxyRemote(meta.cloneUrl);
24
+ const client = new PlayCraftHttpClient({
25
+ url: options.url,
26
+ token: options.token,
27
+ });
28
+ const gitArgs = options.gitArgs?.length ? options.gitArgs : [];
29
+ // 默认推当前分支;不强制改用户已设的 upstream。若无 upstream 且未传参数,
30
+ // 用 meta.branch 设 -u,避免「没有 upstream」报错。
31
+ const effectiveArgs = gitArgs.length > 0
32
+ ? gitArgs
33
+ : (() => {
34
+ const upstream = spawnSync('git', ['rev-parse', '--abbrev-ref', '--symbolic-full-name', '@{u}'], {
35
+ cwd: repoDir,
36
+ encoding: 'utf8',
37
+ });
38
+ if (upstream.status === 0 && upstream.stdout.trim()) {
39
+ return [];
40
+ }
41
+ return ['-u', 'origin', meta.branch];
42
+ })();
43
+ if (!options.json) {
44
+ process.stderr.write(`[push] ${meta.cloneUrl}\n`);
45
+ process.stderr.write(`[push] branch=${meta.branch} dir=${repoDir}\n`);
46
+ }
47
+ const skills = await prepareLocalSkillLifecycle(repoDir, 'push');
48
+ const result = runAuthenticatedGit(['push', ...effectiveArgs], {
49
+ cwd: repoDir,
50
+ token: client.token,
51
+ pipe: Boolean(options.json),
52
+ });
53
+ if (result.status !== 0) {
54
+ const detail = [result.stderr, result.stdout].filter(Boolean).join('\n').trim();
55
+ throw new Error(detail || `git push 失败(exit ${result.status})`);
56
+ }
57
+ let skillIndex;
58
+ let skillIndexFailed = false;
59
+ if (skills.managed) {
60
+ try {
61
+ skillIndex = await reconcileLocalProjectSkills(repoDir, { url: options.url, token: options.token });
62
+ }
63
+ catch (error) {
64
+ skillIndexFailed = true;
65
+ skillIndex = { indexed: false, message: skillMessage('pushIndexFailed'), detail: error instanceof Error ? error.message : String(error) };
66
+ process.exitCode = 1;
67
+ }
68
+ if (!options.json)
69
+ process.stderr.write(`${JSON.stringify(skillIndex)}\n`);
70
+ }
71
+ if (options.json) {
72
+ console.log(JSON.stringify({
73
+ schemaVersion: '1.0',
74
+ ok: !skillIndexFailed,
75
+ command: 'remix.push',
76
+ requestId: client.requestId,
77
+ data: {
78
+ projectId: meta.projectId,
79
+ branch: meta.branch,
80
+ cloneUrl: meta.cloneUrl,
81
+ dir: repoDir,
82
+ gitArgs: effectiveArgs,
83
+ gitPushed: true,
84
+ skills: skillIndex,
85
+ },
86
+ }));
87
+ return;
88
+ }
89
+ console.log(`✓ 已推送到 Remix 分支 ${meta.branch}`);
90
+ }
@@ -3,6 +3,8 @@ import { join } from 'path';
3
3
  import { homedir } from 'os';
4
4
  import { loadGlobalConfig } from '../config.js';
5
5
  import { DEFAULT_PLAYCRAFT_CLOUD_BASE } from '../commands/login.js';
6
+ import { AGENT_TOOL_OPERATION_PROTOCOL, AGENT_TOOL_OPERATION_HEADERS, AGENT_TOOL_OPERATION_CAPABILITIES_PATH, } from '@playcraft/common/agent-tools';
7
+ import { beginToolOperation } from './tool-operation-journal.js';
6
8
  /**
7
9
  * 判断 token 是否为用户 PAT(Personal Access Token)。
8
10
  * PAT 以 "playcraft_pat_" 开头;沙箱 JWT 为三段式 base64。
@@ -48,9 +50,7 @@ export class AgentApiClient {
48
50
  this.apiUrl = apiUrl;
49
51
  this.token = token;
50
52
  this.isPat = isPatToken(token);
51
- this.playcraftEnv = (process.env.PLAYCRAFT_ENV ||
52
- localConfig.env ||
53
- '').trim();
53
+ this.playcraftEnv = (process.env.PLAYCRAFT_ENV || localConfig.env || '').trim();
54
54
  }
55
55
  /** 根据 token 类型构造鉴权 headers */
56
56
  authHeaders() {
@@ -67,21 +67,58 @@ export class AgentApiClient {
67
67
  const p = path.startsWith('/') ? path : `/${path}`;
68
68
  return new URL(`/api/agent/tools${p}`, this.apiUrl);
69
69
  }
70
- async post(path, body) {
71
- const url = this.toolsRequestUrl(path).href;
72
- const res = await fetch(url, {
73
- method: 'POST',
74
- headers: {
75
- 'Content-Type': 'application/json',
76
- ...this.authHeaders(),
77
- },
78
- body: JSON.stringify(body),
79
- });
80
- if (!res.ok) {
81
- const text = await res.text().catch(() => res.statusText);
82
- throw new Error(`API ${res.status}: ${text}`);
70
+ async post(path, body, options) {
71
+ const url = this.toolsRequestUrl(path);
72
+ const serializedBody = JSON.stringify(body);
73
+ if (serializedBody === undefined)
74
+ throw new Error('Tool request body is required');
75
+ const operation = beginToolOperation(url.pathname, serializedBody, options?.requestId ?? process.env.PLAYCRAFT_TOOL_REQUEST_ID);
76
+ try {
77
+ const capabilityResponse = await fetch(this.toolsRequestUrl(AGENT_TOOL_OPERATION_CAPABILITIES_PATH), {
78
+ headers: this.authHeaders(),
79
+ redirect: 'error',
80
+ signal: AbortSignal.timeout(30_000),
81
+ });
82
+ if (!capabilityResponse.ok) {
83
+ await capabilityResponse.body?.cancel();
84
+ throw new Error(`Business tool protocol unavailable (${capabilityResponse.status}); no operation was sent`);
85
+ }
86
+ const capabilities = (await capabilityResponse.json());
87
+ if (capabilities.protocol !== AGENT_TOOL_OPERATION_PROTOCOL ||
88
+ capabilities.settledOnly !== true)
89
+ throw new Error('Business tool protocol is incompatible; no operation was sent');
90
+ const res = await fetch(url, {
91
+ method: 'POST',
92
+ redirect: 'error',
93
+ signal: AbortSignal.timeout(30 * 60_000),
94
+ headers: {
95
+ 'Content-Type': 'application/json',
96
+ ...this.authHeaders(),
97
+ [AGENT_TOOL_OPERATION_HEADERS.protocol]: AGENT_TOOL_OPERATION_PROTOCOL,
98
+ [AGENT_TOOL_OPERATION_HEADERS.requestId]: operation.requestId,
99
+ ...(operation.settledOnly ? { [AGENT_TOOL_OPERATION_HEADERS.settledOnly]: 'true' } : {}),
100
+ },
101
+ body: serializedBody,
102
+ });
103
+ if (res.headers.get(AGENT_TOOL_OPERATION_HEADERS.protocol) !== AGENT_TOOL_OPERATION_PROTOCOL ||
104
+ res.headers.get(AGENT_TOOL_OPERATION_HEADERS.requestId) !== operation.requestId) {
105
+ await res.body?.cancel();
106
+ throw new Error('Business did not acknowledge a durable receipt; outcome remains unconfirmed');
107
+ }
108
+ if (!res.ok) {
109
+ const text = await res.text().catch(() => res.statusText);
110
+ throw new Error(`API ${res.status}: ${text}`);
111
+ }
112
+ const result = (await res.json());
113
+ operation.complete();
114
+ return result;
115
+ }
116
+ catch (error) {
117
+ throw new Error(`${error instanceof Error ? error.message : String(error)}\n` +
118
+ `Tool request ${operation.requestId}; record: ${operation.file}. ` +
119
+ `To query its settled result, rerun the same command with PLAYCRAFT_TOOL_REQUEST_ID=${operation.requestId}. ` +
120
+ 'This recovery does not start another operation.');
83
121
  }
84
- return res.json();
85
122
  }
86
123
  async get(path, params) {
87
124
  const url = this.toolsRequestUrl(path);
@@ -0,0 +1,54 @@
1
+ import { createHash, randomUUID } from 'node:crypto';
2
+ import { mkdirSync, readFileSync, writeFileSync, renameSync } from 'node:fs';
3
+ import { homedir } from 'node:os';
4
+ import { isAbsolute, join } from 'node:path';
5
+ import { isAgentToolRequestId } from '@playcraft/common/agent-tools';
6
+ /** Persist an identity before sending. Store neither credentials nor request/response bodies. */
7
+ export function beginToolOperation(path, serializedBody, recoverId) {
8
+ const identifier = recoverId ?? randomUUID();
9
+ if (!isAgentToolRequestId(identifier))
10
+ throw new Error('Invalid PLAYCRAFT_TOOL_REQUEST_ID');
11
+ const requestId = identifier.toLowerCase();
12
+ const directory = process.env.PLAYCRAFT_TOOL_REQUEST_DIR ?? join(homedir(), '.playcraft', 'tool-requests');
13
+ if (!isAbsolute(directory))
14
+ throw new Error('PLAYCRAFT_TOOL_REQUEST_DIR must be an absolute path');
15
+ mkdirSync(directory, { recursive: true, mode: 0o700 });
16
+ const file = join(directory, `${requestId}.json`);
17
+ const inputHash = createHash('sha256').update(serializedBody).digest('hex');
18
+ const now = new Date().toISOString();
19
+ const entry = {
20
+ version: 1,
21
+ requestId,
22
+ path,
23
+ inputHash,
24
+ state: 'unconfirmed',
25
+ createdAt: now,
26
+ updatedAt: now,
27
+ };
28
+ let settledOnly = recoverId !== undefined;
29
+ try {
30
+ writeFileSync(file, JSON.stringify(entry), { flag: 'wx', mode: 0o600 });
31
+ }
32
+ catch (error) {
33
+ if (error.code !== 'EEXIST')
34
+ throw error;
35
+ const previous = JSON.parse(readFileSync(file, 'utf8'));
36
+ if (previous.version !== 1 ||
37
+ previous.requestId !== requestId ||
38
+ previous.path !== path ||
39
+ previous.inputHash !== inputHash)
40
+ throw new Error(`Tool request ${requestId} conflicts with its durable local record`);
41
+ settledOnly = true;
42
+ }
43
+ return {
44
+ requestId,
45
+ settledOnly,
46
+ file,
47
+ complete() {
48
+ const current = JSON.parse(readFileSync(file, 'utf8'));
49
+ const temporary = `${file}.${randomUUID()}.tmp`;
50
+ writeFileSync(temporary, JSON.stringify({ ...current, state: 'completed', updatedAt: new Date().toISOString() }), { flag: 'wx', mode: 0o600 });
51
+ renameSync(temporary, file);
52
+ },
53
+ };
54
+ }