@inneranimalmedia/agentsam-sdk 2.6.7 → 2.6.8

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 (318) hide show
  1. package/README.md +37 -0
  2. package/docs/REPO_FILE_MAP.txt +132 -0
  3. package/migrations/d1/0018_goap_control_plane_remaster.sql +40 -0
  4. package/migrations/d1/0019_agentsam_agent_run_plan_todo.sql +14 -0
  5. package/migrations/d1/README_PORTABLE_CONTROL_PLANE.md +20 -9
  6. package/migrations/runtime/0002_goap_blackboard_v1.sql +15 -0
  7. package/migrations/runtime/0004_agent_run_plan_linkage.sql +8 -0
  8. package/native/agentsam-machine/cli/Cargo.toml +8 -1
  9. package/native/agentsam-machine/cli/README.md +21 -0
  10. package/native/agentsam-machine/core/Cargo.toml +7 -0
  11. package/native/agentsam-machine/core/README.md +14 -0
  12. package/native/agentsam-machine/core/build.rs +33 -0
  13. package/native/agentsam-machine/core/contracts/source-types.v1.json +353 -0
  14. package/native/agentsam-machine/core/src/lib.rs +6 -0
  15. package/native/agentsam-machine/core/src/source_types.rs +2 -2
  16. package/package.json +16 -6
  17. package/packages/agentsam-errors/package.json +1 -1
  18. package/packages/agentsam-errors/src/index.js +1 -0
  19. package/packages/agentsam-errors/src/tool.js +56 -0
  20. package/packages/agentsam-errors/test/tool.test.mjs +58 -0
  21. package/packages/agentsam-goap/README.md +52 -0
  22. package/packages/agentsam-goap/package.json +37 -0
  23. package/packages/agentsam-goap/src/adapters/d1-sqlite.js +570 -0
  24. package/packages/agentsam-goap/src/adapters/memory.js +199 -0
  25. package/packages/agentsam-goap/src/contracts.js +226 -0
  26. package/packages/agentsam-goap/src/control-plane.js +112 -0
  27. package/packages/agentsam-goap/src/errors.js +157 -0
  28. package/packages/agentsam-goap/src/index.js +5 -0
  29. package/packages/agentsam-goap/src/world.js +202 -0
  30. package/packages/agentsam-goap/test/goap-control-plane.test.mjs +245 -0
  31. package/packages/agentsam-goap/test/world.test.mjs +79 -0
  32. package/packages/agentsam-nav/dist/Nav.d.ts +43 -0
  33. package/packages/agentsam-nav/dist/NavCollapsible.d.ts +10 -0
  34. package/packages/agentsam-nav/dist/NavContent.d.ts +2 -0
  35. package/packages/agentsam-nav/dist/NavFooter.d.ts +2 -0
  36. package/packages/agentsam-nav/dist/NavGroup.d.ts +3 -0
  37. package/packages/agentsam-nav/dist/NavHeader.d.ts +2 -0
  38. package/packages/agentsam-nav/dist/NavMenu.d.ts +22 -0
  39. package/packages/agentsam-nav/dist/NavProvider.d.ts +35 -0
  40. package/packages/agentsam-nav/dist/NavTrigger.d.ts +2 -0
  41. package/packages/agentsam-nav/dist/Surfaces.d.ts +41 -0
  42. package/packages/agentsam-nav/dist/browser.d.ts +11 -0
  43. package/packages/agentsam-nav/dist/browser.global.js +135 -0
  44. package/packages/agentsam-nav/dist/browser.js +7290 -0
  45. package/packages/agentsam-nav/dist/contracts.d.ts +76 -0
  46. package/packages/agentsam-nav/dist/index.d.ts +11 -0
  47. package/packages/agentsam-nav/dist/index.js +7291 -0
  48. package/packages/agentsam-nav/dist/legacy.css +83 -0
  49. package/packages/agentsam-nav/dist/shell.css +113 -0
  50. package/packages/agentsam-nav/dist/theme.css +113 -0
  51. package/packages/agentsam-nav/dist/utils.d.ts +1 -0
  52. package/packages/agentsam-repository/src/goap.js +13 -14
  53. package/packages/agentsam-repository/src/goap.js.bak-20260922011251 +670 -0
  54. package/packages/agentsam-repository/src/tickets.js +15 -22
  55. package/packages/agentsam-repository/test/goap-tickets-workspace-state.test.mjs +19 -8
  56. package/packages/connectors/cloudflare/package.json +3 -1
  57. package/packages/identity/tests/lane1-packed-consumer.test.mjs +1 -1
  58. package/python/agentsam_sdk/repository/intelligence/discovery.py +27 -2
  59. package/python/agentsam_sdk/repository/intelligence/git.py +4 -0
  60. package/python/tests/test_repository_intelligence.py +30 -0
  61. package/src/cli/command-catalog.js +2 -1
  62. package/src/cli.js +10 -1
  63. package/src/commands/goap-world.js +134 -0
  64. package/src/commands/goap.js +11 -4
  65. package/src/commands/machine-binary.js +33 -27
  66. package/src/commands/machine-runtime.js +266 -0
  67. package/src/commands/machine.js +122 -51
  68. package/src/commands/package.js +530 -0
  69. package/src/commands/plan-ledger.js +370 -0
  70. package/src/commands/plan.js +183 -0
  71. package/src/commands/skill.js +78 -17
  72. package/src/knowledge/source.js +1 -1
  73. package/src/lib/slash-commands.js +1 -1
  74. package/src/local/runtime-store.js +24 -2
  75. package/src/local/sqlite.js +17 -0
  76. package/src/sam/operations/package-audit.js +41 -0
  77. package/src/sam/seed.js +2 -0
  78. package/skills/grok-auth/SKILL.md +0 -110
  79. package/skills/grok-auth/agentsam.skill.json +0 -25
  80. package/skills/grok-auth/references/grok-identity.md +0 -64
  81. package/skills/grok-auth/references/per-user-data.md +0 -49
  82. package/skills/grok-auth/references/prewired-and-env.md +0 -54
  83. package/skills/grok-auth/references/session-ui.md +0 -133
  84. package/skills/grok-auth/references/sign-in-methods.md +0 -55
  85. package/skills/grok-auth/references/wiring.md +0 -71
  86. package/skills/grok-building-games/SKILL.md +0 -213
  87. package/skills/grok-building-games/agentsam.skill.json +0 -25
  88. package/skills/grok-building-games/references/3d-libs.md +0 -64
  89. package/skills/grok-building-games/references/ai-pathfinding.md +0 -122
  90. package/skills/grok-building-games/references/audio.md +0 -129
  91. package/skills/grok-building-games/references/babylon.md +0 -202
  92. package/skills/grok-building-games/references/collision-physics.md +0 -122
  93. package/skills/grok-building-games/references/ecs-architecture.md +0 -111
  94. package/skills/grok-building-games/references/game-feel-juice.md +0 -110
  95. package/skills/grok-building-games/references/genres/board-card-chess.md +0 -89
  96. package/skills/grok-building-games/references/genres/endless-runner.md +0 -83
  97. package/skills/grok-building-games/references/genres/fps.md +0 -105
  98. package/skills/grok-building-games/references/genres/platformer-2d.md +0 -97
  99. package/skills/grok-building-games/references/genres/puzzle-match3-tetris.md +0 -131
  100. package/skills/grok-building-games/references/genres/racing-kart.md +0 -106
  101. package/skills/grok-building-games/references/genres/topdown-twin-stick.md +0 -97
  102. package/skills/grok-building-games/references/genres/tower-defense.md +0 -84
  103. package/skills/grok-building-games/references/genres/voxel-minecraft.md +0 -89
  104. package/skills/grok-building-games/references/input.md +0 -139
  105. package/skills/grok-building-games/references/phaser.md +0 -277
  106. package/skills/grok-building-games/references/procedural-generation.md +0 -141
  107. package/skills/grok-building-games/references/save-persistence.md +0 -108
  108. package/skills/grok-building-games/references/threejs-foundational.md +0 -148
  109. package/skills/grok-controls/SKILL.md +0 -274
  110. package/skills/grok-controls/agentsam.skill.json +0 -25
  111. package/skills/grok-design-ui/SKILL.md +0 -122
  112. package/skills/grok-design-ui/agentsam.skill.json +0 -25
  113. package/skills/grok-design-ui/references/animations.md +0 -379
  114. package/skills/grok-design-ui/references/performance.md +0 -88
  115. package/skills/grok-design-ui/references/refined-ui.md +0 -384
  116. package/skills/grok-design-ui/references/surfaces.md +0 -247
  117. package/skills/grok-design-ui/references/typography.md +0 -123
  118. package/skills/grok-game-animation-frames/SKILL.md +0 -84
  119. package/skills/grok-game-animation-frames/agentsam.skill.json +0 -25
  120. package/skills/grok-game-asset-core/SKILL.md +0 -69
  121. package/skills/grok-game-asset-core/agentsam.skill.json +0 -25
  122. package/skills/grok-game-character-consistency/SKILL.md +0 -69
  123. package/skills/grok-game-character-consistency/agentsam.skill.json +0 -25
  124. package/skills/grok-game-tilesets/SKILL.md +0 -65
  125. package/skills/grok-game-tilesets/agentsam.skill.json +0 -25
  126. package/skills/grok-game-ui-icons/SKILL.md +0 -56
  127. package/skills/grok-game-ui-icons/agentsam.skill.json +0 -25
  128. package/skills/grok-generate2dmap/LICENSE +0 -21
  129. package/skills/grok-generate2dmap/SKILL.md +0 -120
  130. package/skills/grok-generate2dmap/SOURCE.md +0 -6
  131. package/skills/grok-generate2dmap/agentsam.skill.json +0 -25
  132. package/skills/grok-generate2dmap/references/deliverables.md +0 -101
  133. package/skills/grok-generate2dmap/references/layered-map-contract.md +0 -209
  134. package/skills/grok-generate2dmap/references/map-strategies.md +0 -341
  135. package/skills/grok-generate2dmap/references/object-production-gate.md +0 -24
  136. package/skills/grok-generate2dmap/references/pipeline.md +0 -149
  137. package/skills/grok-generate2dmap/references/prop-pack-contract.md +0 -174
  138. package/skills/grok-generate2dmap/references/side-scroll-stages.md +0 -29
  139. package/skills/grok-generate2dmap/scripts/compose_layered_preview.py +0 -157
  140. package/skills/grok-generate2dmap/scripts/extract_prop_pack.py +0 -369
  141. package/skills/grok-generate2dsprite/LICENSE +0 -21
  142. package/skills/grok-generate2dsprite/SKILL.md +0 -313
  143. package/skills/grok-generate2dsprite/SOURCE.md +0 -6
  144. package/skills/grok-generate2dsprite/agentsam.skill.json +0 -25
  145. package/skills/grok-generate2dsprite/references/modes.md +0 -124
  146. package/skills/grok-generate2dsprite/references/prompt-rules.md +0 -315
  147. package/skills/grok-generate2dsprite/scripts/generate2dsprite.py +0 -1031
  148. package/skills/grok-generate2dsprite/scripts/make_layout_guide.py +0 -109
  149. package/skills/grok-imagine/SKILL.md +0 -201
  150. package/skills/grok-imagine/agentsam.skill.json +0 -25
  151. package/skills/grok-multiplayer-p2p/SKILL.md +0 -111
  152. package/skills/grok-multiplayer-p2p/agentsam.skill.json +0 -25
  153. package/skills/grok-multiplayer-p2p/references/react-binding.md +0 -144
  154. package/skills/grok-multiplayer-p2p/references/signaling-relay.md +0 -269
  155. package/skills/grok-neon/SKILL.md +0 -157
  156. package/skills/grok-neon/agentsam.skill.json +0 -25
  157. package/skills/grok-og/SKILL.md +0 -102
  158. package/skills/grok-og/agentsam.skill.json +0 -25
  159. package/skills/grok-og/references/brand-pass.md +0 -53
  160. package/skills/grok-og/references/custom-card.md +0 -107
  161. package/skills/grok-og/references/favicon-and-icons.md +0 -48
  162. package/skills/grok-og/references/og-type-contract.md +0 -18
  163. package/skills/grok-og/references/placeholder-card.md +0 -21
  164. package/skills/grok-og/references/x-banner.md +0 -41
  165. package/skills/grok-threejs/SKILL.md +0 -67
  166. package/skills/grok-threejs/agentsam.skill.json +0 -25
  167. package/skills/grok-threejs/references/llms-full.txt +0 -2780
  168. package/skills/grok-video2dsprite/LICENSE +0 -21
  169. package/skills/grok-video2dsprite/SKILL.md +0 -236
  170. package/skills/grok-video2dsprite/SOURCE.md +0 -6
  171. package/skills/grok-video2dsprite/agentsam.skill.json +0 -25
  172. package/skills/grok-video2dsprite/references/pipeline.md +0 -90
  173. package/skills/grok-video2dsprite/references/prompt-rules.md +0 -63
  174. package/skills/grok-video2dsprite/scripts/video2dsprite.py +0 -490
  175. package/skills/grok-xai-api/SKILL.md +0 -152
  176. package/skills/grok-xai-api/agentsam.skill.json +0 -25
  177. package/test/account-session.test.mjs +0 -101
  178. package/test/agent-capabilities.test.mjs +0 -67
  179. package/test/app-authority-context.test.mjs +0 -32
  180. package/test/app-building-skills.test.mjs +0 -61
  181. package/test/apps-scaffold-contract.test.mjs +0 -115
  182. package/test/blender-cad.test.mjs +0 -146
  183. package/test/cad-docker.test.mjs +0 -96
  184. package/test/capabilities.test.mjs +0 -133
  185. package/test/cli/api-key-env-whoami.test.mjs +0 -129
  186. package/test/cli/catalog-authority.test.mjs +0 -64
  187. package/test/cli/codebaseindex-plan-ux.test.mjs +0 -37
  188. package/test/cli/eval-live.test.mjs +0 -219
  189. package/test/cli/go.test.mjs +0 -128
  190. package/test/cli/local-vault-and-provider-setup.test.mjs +0 -268
  191. package/test/cli/machine-command.test.mjs +0 -34
  192. package/test/cli/mcp-client.test.mjs +0 -256
  193. package/test/cli/preferences-runtime.test.mjs +0 -11
  194. package/test/cli/runtime-persist.test.mjs +0 -56
  195. package/test/cli/runtime-ui.test.mjs +0 -141
  196. package/test/cli/rust-command.test.mjs +0 -21
  197. package/test/cli/skill-npm-and-env.test.mjs +0 -52
  198. package/test/cli/status-actions.test.mjs +0 -88
  199. package/test/cli/wireframes-go-registry.test.mjs +0 -139
  200. package/test/cli-preferences.test.mjs +0 -46
  201. package/test/cloudflare-connector.test.mjs +0 -93
  202. package/test/cloudflare-runtime.test.mjs +0 -82
  203. package/test/context.test.mjs +0 -159
  204. package/test/credentials-audit.test.mjs +0 -113
  205. package/test/deploy-health-scan.test.mjs +0 -95
  206. package/test/deploy-receipt.test.mjs +0 -91
  207. package/test/dockerize.test.mjs +0 -93
  208. package/test/error-diagnostics.test.mjs +0 -124
  209. package/test/eval-context.test.mjs +0 -37
  210. package/test/execution-approvals.test.mjs +0 -27
  211. package/test/fixtures/cad/courtyard-house.json +0 -208
  212. package/test/fixtures/knowledge-rpc-worker.mjs +0 -16
  213. package/test/fixtures/project-authority/customer-app/.agentsam/app.json +0 -5
  214. package/test/fixtures/project-authority/customer-app/apps/my-product/index.ts +0 -1
  215. package/test/fixtures/project-authority/customer-app/wrangler.toml +0 -17
  216. package/test/gcloud-auth.test.mjs +0 -111
  217. package/test/go/build-source-identity.test.mjs +0 -31
  218. package/test/go/cloudflare-probe.test.mjs +0 -317
  219. package/test/go/distribution.test.mjs +0 -28
  220. package/test/google-cloud-connection.test.mjs +0 -83
  221. package/test/google-cloud-inventory.test.mjs +0 -64
  222. package/test/indexing-provider.test.mjs +0 -29
  223. package/test/init-options.test.mjs +0 -35
  224. package/test/integration/ai-access-onboarding.test.mjs +0 -47
  225. package/test/integration/app-command.test.mjs +0 -85
  226. package/test/integration/autorag-project-authority.integration.test.mjs +0 -51
  227. package/test/integration/autorag-runtime-discovery.test.mjs +0 -107
  228. package/test/integration/cad-discovery.test.mjs +0 -158
  229. package/test/integration/cad-docker-executor.test.mjs +0 -249
  230. package/test/integration/cad-execution.test.mjs +0 -184
  231. package/test/integration/cad-project-app.test.mjs +0 -31
  232. package/test/integration/cad-project-d1.test.mjs +0 -25
  233. package/test/integration/cad-project-native.test.mjs +0 -24
  234. package/test/integration/cad-project-workflow.test.mjs +0 -9
  235. package/test/integration/cad-project.test.mjs +0 -85
  236. package/test/integration/cli-help.test.mjs +0 -41
  237. package/test/integration/cms-app-scaffold.test.mjs +0 -77
  238. package/test/integration/cms-site-tenancy-contract.test.mjs +0 -268
  239. package/test/integration/codebaseindex-ingest.test.mjs +0 -255
  240. package/test/integration/core-client.integration.test.mjs +0 -55
  241. package/test/integration/database-hosted-honesty.test.mjs +0 -49
  242. package/test/integration/desktop-keychain-boundary.test.mjs +0 -45
  243. package/test/integration/error-runtime-rpc.test.mjs +0 -71
  244. package/test/integration/features-resolve.test.mjs +0 -147
  245. package/test/integration/hosted-work-host-authority.test.mjs +0 -47
  246. package/test/integration/icon-registry.test.mjs +0 -67
  247. package/test/integration/ingest-discover-models.test.mjs +0 -30
  248. package/test/integration/inspect-path-cli.test.mjs +0 -46
  249. package/test/integration/install-script.test.mjs +0 -110
  250. package/test/integration/inventory-core.test.mjs +0 -53
  251. package/test/integration/inventory-eligibility.test.mjs +0 -59
  252. package/test/integration/inventory-evidence.test.mjs +0 -125
  253. package/test/integration/knowledge-rpc.test.mjs +0 -112
  254. package/test/integration/local-fs.test.mjs +0 -113
  255. package/test/integration/local-provider-bridge.test.mjs +0 -65
  256. package/test/integration/local-studio-settings-production.test.mjs +0 -40
  257. package/test/integration/lockfile-sync.test.mjs +0 -26
  258. package/test/integration/merkle-cli.test.mjs +0 -61
  259. package/test/integration/merkle-persistence-identity.test.mjs +0 -48
  260. package/test/integration/plugins-cloudflare.integration.test.mjs +0 -28
  261. package/test/integration/provider-env-cli.test.mjs +0 -60
  262. package/test/integration/provider-factory.test.mjs +0 -197
  263. package/test/integration/repository-company-graph.test.mjs +0 -90
  264. package/test/integration/runtime-migrations.test.mjs +0 -87
  265. package/test/integration/runtime-status.integration.test.mjs +0 -227
  266. package/test/integration/sam-activity-recovery.test.mjs +0 -147
  267. package/test/integration/sam-decision.test.mjs +0 -584
  268. package/test/integration/sam-kernel.test.mjs +0 -99
  269. package/test/integration/sam-planning-astar.test.mjs +0 -279
  270. package/test/integration/scaffold-cloudflare.test.mjs +0 -46
  271. package/test/integration/skill-runtime.test.mjs +0 -240
  272. package/test/integration/storage-architecture.test.mjs +0 -154
  273. package/test/integration/store-discovery.test.mjs +0 -44
  274. package/test/integration/studio-fs-pty-e2e.test.mjs +0 -294
  275. package/test/integration/terminal-exec.integration.test.mjs +0 -86
  276. package/test/integration/theme-package-registry.test.mjs +0 -31
  277. package/test/integration/tool-runtime.test.mjs +0 -124
  278. package/test/integration/tool-schema.test.mjs +0 -59
  279. package/test/jr-dev-skill.test.mjs +0 -26
  280. package/test/knowledge-context-pack.test.mjs +0 -19
  281. package/test/knowledge-postgres.test.mjs +0 -40
  282. package/test/knowledge-service.test.mjs +0 -110
  283. package/test/knowledge.test.mjs +0 -206
  284. package/test/live/terminal-transport.live.test.mjs +0 -24
  285. package/test/local-sessions.test.mjs +0 -57
  286. package/test/local-studio-deploy.test.mjs +0 -83
  287. package/test/loopback-oauth.test.mjs +0 -53
  288. package/test/merkle-render.test.mjs +0 -31
  289. package/test/mini.test.mjs +0 -140
  290. package/test/model-catalog.test.mjs +0 -70
  291. package/test/models.test.mjs +0 -309
  292. package/test/npm10-lock.test.mjs +0 -29
  293. package/test/ollama.test.mjs +0 -137
  294. package/test/open-url.test.mjs +0 -64
  295. package/test/openai-responses.test.mjs +0 -155
  296. package/test/portable-context.test.mjs +0 -94
  297. package/test/project-config.test.mjs +0 -81
  298. package/test/project-rules.test.mjs +0 -44
  299. package/test/provider-credentials.test.mjs +0 -106
  300. package/test/rehydrate.test.mjs +0 -25
  301. package/test/release-hygiene.test.mjs +0 -42
  302. package/test/repository-snapshot-view.test.mjs +0 -112
  303. package/test/responses-runner.test.mjs +0 -465
  304. package/test/runtime-activity.test.mjs +0 -98
  305. package/test/sdk-worker-contract.test.mjs +0 -72
  306. package/test/security.test.mjs +0 -266
  307. package/test/setup-planner.test.mjs +0 -74
  308. package/test/setup-runtime.test.mjs +0 -51
  309. package/test/shell.test.mjs +0 -281
  310. package/test/skills.test.mjs +0 -22
  311. package/test/smoke.mjs +0 -122
  312. package/test/telemetry.test.mjs +0 -79
  313. package/test/terminal/local-pty.mock.test.mjs +0 -153
  314. package/test/terminal-machine-identity.test.mjs +0 -88
  315. package/test/theme-portability.test.mjs +0 -14
  316. package/test/tools-search.test.mjs +0 -73
  317. package/test/unit/website-assets-bindings.test.mjs +0 -60
  318. package/test/whoami-resume.test.mjs +0 -157
@@ -1,369 +0,0 @@
1
- #!/usr/bin/env python3
2
- """Extract transparent map props from a solid-magenta prop-pack sheet."""
3
-
4
- from __future__ import annotations
5
-
6
- import argparse
7
- import json
8
- import math
9
- import re
10
- from collections import deque
11
- from pathlib import Path
12
- from typing import Iterable
13
-
14
- from PIL import Image
15
-
16
- MAGENTA = (255, 0, 255)
17
-
18
-
19
- def color_distance(rgb: tuple[int, int, int], target: tuple[int, int, int] = MAGENTA) -> float:
20
- r, g, b = rgb
21
- tr, tg, tb = target
22
- return math.sqrt((r - tr) ** 2 + (g - tg) ** 2 + (b - tb) ** 2)
23
-
24
-
25
- def remove_bg_magenta(img: Image.Image, threshold: int, edge_threshold: int) -> Image.Image:
26
- img = img.convert("RGBA")
27
- pixels = img.load()
28
- width, height = img.size
29
-
30
- for x in range(width):
31
- for y in range(height):
32
- r, g, b, a = pixels[x, y]
33
- if a > 0 and color_distance((r, g, b)) < threshold:
34
- pixels[x, y] = (0, 0, 0, 0)
35
-
36
- visited: set[tuple[int, int]] = set()
37
- queue: deque[tuple[int, int]] = deque()
38
- for x in range(width):
39
- queue.append((x, 0))
40
- queue.append((x, height - 1))
41
- for y in range(height):
42
- queue.append((0, y))
43
- queue.append((width - 1, y))
44
-
45
- while queue:
46
- x, y = queue.popleft()
47
- if (x, y) in visited or x < 0 or x >= width or y < 0 or y >= height:
48
- continue
49
- visited.add((x, y))
50
- r, g, b, a = pixels[x, y]
51
- should_expand = a == 0
52
- if a > 0 and color_distance((r, g, b)) < edge_threshold:
53
- pixels[x, y] = (0, 0, 0, 0)
54
- should_expand = True
55
- if should_expand:
56
- for dx in (-1, 0, 1):
57
- for dy in (-1, 0, 1):
58
- if dx == 0 and dy == 0:
59
- continue
60
- nxt = (x + dx, y + dy)
61
- if nxt not in visited:
62
- queue.append(nxt)
63
-
64
- return img
65
-
66
-
67
- def trim_border(img: Image.Image, px: int) -> Image.Image:
68
- if px <= 0:
69
- return img
70
- width, height = img.size
71
- if width <= px * 2 or height <= px * 2:
72
- return img
73
- return img.crop((px, px, width - px, height - px))
74
-
75
-
76
- def clean_edges(img: Image.Image, depth: int) -> Image.Image:
77
- if depth <= 0:
78
- return img
79
- pixels = img.load()
80
- width, height = img.size
81
- for d in range(depth):
82
- for x in range(width):
83
- for y in (d, height - 1 - d):
84
- if 0 <= y < height:
85
- r, g, b, a = pixels[x, y]
86
- if a > 0 and (
87
- (r < 40 and g < 40 and b < 40) or color_distance((r, g, b)) < 150
88
- ):
89
- pixels[x, y] = (0, 0, 0, 0)
90
- for y in range(height):
91
- for x in (d, width - 1 - d):
92
- if 0 <= x < width:
93
- r, g, b, a = pixels[x, y]
94
- if a > 0 and (
95
- (r < 40 and g < 40 and b < 40) or color_distance((r, g, b)) < 150
96
- ):
97
- pixels[x, y] = (0, 0, 0, 0)
98
- return img
99
-
100
-
101
- def connected_components(img: Image.Image, min_area: int) -> list[dict[str, object]]:
102
- alpha = img.getchannel("A")
103
- pixels = alpha.load()
104
- width, height = img.size
105
- visited = [[False] * width for _ in range(height)]
106
- components: list[dict[str, object]] = []
107
-
108
- for y in range(height):
109
- for x in range(width):
110
- if pixels[x, y] == 0 or visited[y][x]:
111
- continue
112
- queue: deque[tuple[int, int]] = deque([(x, y)])
113
- visited[y][x] = True
114
- coords: list[tuple[int, int]] = []
115
- min_x = max_x = x
116
- min_y = max_y = y
117
- touches_edge = x == 0 or y == 0 or x == width - 1 or y == height - 1
118
-
119
- while queue:
120
- cx, cy = queue.popleft()
121
- coords.append((cx, cy))
122
- min_x = min(min_x, cx)
123
- min_y = min(min_y, cy)
124
- max_x = max(max_x, cx)
125
- max_y = max(max_y, cy)
126
- if cx == 0 or cy == 0 or cx == width - 1 or cy == height - 1:
127
- touches_edge = True
128
- for dx, dy in ((1, 0), (-1, 0), (0, 1), (0, -1)):
129
- nx, ny = cx + dx, cy + dy
130
- if (
131
- 0 <= nx < width
132
- and 0 <= ny < height
133
- and pixels[nx, ny] > 0
134
- and not visited[ny][nx]
135
- ):
136
- visited[ny][nx] = True
137
- queue.append((nx, ny))
138
-
139
- if len(coords) >= min_area:
140
- components.append(
141
- {
142
- "area": len(coords),
143
- "bbox": (min_x, min_y, max_x + 1, max_y + 1),
144
- "touches_edge": touches_edge,
145
- "coords": coords,
146
- }
147
- )
148
-
149
- components.sort(key=lambda item: int(item["area"]), reverse=True)
150
- return components
151
-
152
-
153
- def pad_bbox(
154
- bbox: tuple[int, int, int, int], padding: int, width: int, height: int
155
- ) -> tuple[int, int, int, int]:
156
- x0, y0, x1, y1 = bbox
157
- return (
158
- max(0, x0 - padding),
159
- max(0, y0 - padding),
160
- min(width, x1 + padding),
161
- min(height, y1 + padding),
162
- )
163
-
164
-
165
- def bbox_touches_edge(
166
- bbox: tuple[int, int, int, int] | None,
167
- width: int,
168
- height: int,
169
- margin: int,
170
- ) -> bool:
171
- if bbox is None:
172
- return False
173
- x0, y0, x1, y1 = bbox
174
- return x0 <= margin or y0 <= margin or x1 >= width - margin or y1 >= height - margin
175
-
176
-
177
- def sanitize_slug(value: str) -> str:
178
- slug = re.sub(r"[^a-zA-Z0-9]+", "-", value.strip().lower()).strip("-")
179
- return slug or "prop"
180
-
181
-
182
- def parse_labels(args: argparse.Namespace, expected_count: int) -> list[str]:
183
- labels: list[str] = []
184
- if args.labels:
185
- labels = [item.strip() for item in args.labels.split(",")]
186
- if args.labels_file:
187
- labels = [
188
- line.strip()
189
- for line in args.labels_file.read_text(encoding="utf-8").splitlines()
190
- if line.strip() and not line.lstrip().startswith("#")
191
- ]
192
- if not labels:
193
- labels = [f"prop-{index + 1}" for index in range(expected_count)]
194
- if len(labels) > expected_count:
195
- raise ValueError(f"Got {len(labels)} labels for {expected_count} cells.")
196
- labels.extend(f"prop-{index + 1}" for index in range(len(labels), expected_count))
197
- return [
198
- sanitize_slug(label) if label.lower() not in {"empty", "skip", "-"} else ""
199
- for label in labels
200
- ]
201
-
202
-
203
- def alpha_bbox(img: Image.Image) -> tuple[int, int, int, int] | None:
204
- return img.getchannel("A").getbbox()
205
-
206
-
207
- def mask_to_component(img: Image.Image, component: dict[str, object]) -> Image.Image:
208
- selected = Image.new("RGBA", img.size, (0, 0, 0, 0))
209
- src = img.load()
210
- dst = selected.load()
211
- for x, y in component["coords"]: # type: ignore[index]
212
- dst[x, y] = src[x, y]
213
- return selected
214
-
215
-
216
- def extract_cell(
217
- cell: Image.Image,
218
- args: argparse.Namespace,
219
- ) -> tuple[Image.Image | None, dict[str, object]]:
220
- frame = trim_border(cell, args.trim_border)
221
- frame = clean_edges(frame, args.edge_clean_depth)
222
- components = connected_components(frame, args.min_component_area)
223
- selected_component = None
224
- bbox = alpha_bbox(frame)
225
-
226
- if args.component_mode == "largest" and components:
227
- selected_component = components[0]
228
- frame = mask_to_component(frame, selected_component)
229
- bbox = tuple(selected_component["bbox"]) # type: ignore[arg-type]
230
- elif components:
231
- bbox = alpha_bbox(frame)
232
-
233
- padded_bbox = (
234
- pad_bbox(bbox, args.component_padding, frame.width, frame.height) if bbox else None
235
- )
236
- edge_touch = bbox_touches_edge(bbox, frame.width, frame.height, args.edge_touch_margin)
237
- prop = frame.crop(padded_bbox) if padded_bbox else None
238
-
239
- return prop, {
240
- "component_mode": args.component_mode,
241
- "component_count": len(components),
242
- "selected_component_area": int(selected_component["area"]) if selected_component else None,
243
- "selected_component_bbox": list(selected_component["bbox"]) if selected_component else None,
244
- "crop_bbox": list(bbox) if bbox else None,
245
- "padded_crop_bbox": list(padded_bbox) if padded_bbox else None,
246
- "edge_touch": edge_touch,
247
- "output_size": list(prop.size) if prop else [0, 0],
248
- }
249
-
250
-
251
- def iter_cells(
252
- img: Image.Image, rows: int, cols: int
253
- ) -> Iterable[tuple[int, int, tuple[int, int, int, int], Image.Image]]:
254
- width, height = img.size
255
- cell_width = width // cols
256
- cell_height = height // rows
257
- for row in range(rows):
258
- for col in range(cols):
259
- box = (
260
- col * cell_width,
261
- row * cell_height,
262
- (col + 1) * cell_width,
263
- (row + 1) * cell_height,
264
- )
265
- yield row, col, box, img.crop(box)
266
-
267
-
268
- def build_parser() -> argparse.ArgumentParser:
269
- parser = argparse.ArgumentParser(description=__doc__)
270
- parser.add_argument("--input", required=True, type=Path)
271
- parser.add_argument("--rows", required=True, type=int)
272
- parser.add_argument("--cols", required=True, type=int)
273
- parser.add_argument("--output-dir", required=True, type=Path)
274
- parser.add_argument("--manifest", type=Path)
275
- parser.add_argument("--labels", help="Comma-separated labels in row-major order.")
276
- parser.add_argument("--labels-file", type=Path)
277
- parser.add_argument("--threshold", type=int, default=100)
278
- parser.add_argument("--edge-threshold", type=int, default=150)
279
- parser.add_argument("--trim-border", type=int, default=4)
280
- parser.add_argument("--edge-clean-depth", type=int, default=2)
281
- parser.add_argument("--component-mode", choices=["all", "largest"], default="largest")
282
- parser.add_argument("--component-padding", type=int, default=8)
283
- parser.add_argument("--min-component-area", type=int, default=100)
284
- parser.add_argument("--edge-touch-margin", type=int, default=0)
285
- parser.add_argument("--reject-edge-touch", action="store_true")
286
- parser.add_argument("--keep-empty", action="store_true")
287
- return parser
288
-
289
-
290
- def main() -> None:
291
- args = build_parser().parse_args()
292
- expected_count = args.rows * args.cols
293
- labels = parse_labels(args, expected_count)
294
- args.output_dir.mkdir(parents=True, exist_ok=True)
295
-
296
- raw = Image.open(args.input).convert("RGBA")
297
- cleaned = remove_bg_magenta(raw, args.threshold, args.edge_threshold)
298
- manifest_path = args.manifest or (args.output_dir / "prop-pack.json")
299
- accepted: list[dict[str, object]] = []
300
- rejected: list[dict[str, object]] = []
301
-
302
- for index, (row, col, source_box, cell) in enumerate(iter_cells(cleaned, args.rows, args.cols)):
303
- label = labels[index]
304
- cell_info: dict[str, object] = {
305
- "index": index,
306
- "label": label,
307
- "grid": [row, col],
308
- "source_box": list(source_box),
309
- }
310
- if not label:
311
- cell_info["status"] = "skipped-label"
312
- rejected.append(cell_info)
313
- continue
314
-
315
- prop, info = extract_cell(cell, args)
316
- cell_info.update(info)
317
-
318
- if prop is None:
319
- cell_info["status"] = "empty"
320
- if args.keep_empty:
321
- prop = Image.new("RGBA", (1, 1), (0, 0, 0, 0))
322
- else:
323
- rejected.append(cell_info)
324
- continue
325
-
326
- # With --reject-edge-touch, never write edge-touching props as accepted.
327
- if args.reject_edge_touch and bool(cell_info.get("edge_touch")):
328
- cell_info["status"] = "edge_touch"
329
- rejected.append(cell_info)
330
- continue
331
-
332
- prop_dir = args.output_dir / label
333
- prop_dir.mkdir(parents=True, exist_ok=True)
334
- prop_path = prop_dir / "prop.png"
335
- prop.save(prop_path)
336
- cell_info["status"] = "accepted"
337
- cell_info["image"] = str(prop_path)
338
- accepted.append(cell_info)
339
-
340
- edge_touch_props = [
341
- item["label"]
342
- for item in accepted + rejected
343
- if bool(item.get("edge_touch")) and item.get("status") in {"accepted", "edge_touch"}
344
- ]
345
- manifest = {
346
- "input": str(args.input),
347
- "rows": args.rows,
348
- "cols": args.cols,
349
- "threshold": args.threshold,
350
- "edge_threshold": args.edge_threshold,
351
- "component_mode": args.component_mode,
352
- "component_padding": args.component_padding,
353
- "min_component_area": args.min_component_area,
354
- "edge_touch_margin": args.edge_touch_margin,
355
- "accepted": accepted,
356
- "rejected": rejected,
357
- "edge_touch_props": edge_touch_props,
358
- }
359
- manifest_path.parent.mkdir(parents=True, exist_ok=True)
360
- manifest_path.write_text(json.dumps(manifest, indent=2), encoding="utf-8")
361
-
362
- if args.reject_edge_touch and edge_touch_props:
363
- raise ValueError(f"Props touch a cell edge (not accepted): {edge_touch_props}")
364
-
365
- print(str(manifest_path.resolve()))
366
-
367
-
368
- if __name__ == "__main__":
369
- main()
@@ -1,21 +0,0 @@
1
- MIT License
2
-
3
- Copyright (c) 2026 0x0funky
4
-
5
- Permission is hereby granted, free of charge, to any person obtaining a copy
6
- of this software and associated documentation files (the "Software"), to deal
7
- in the Software without restriction, including without limitation the rights
8
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
- copies of the Software, and to permit persons to whom the Software is
10
- furnished to do so, subject to the following conditions:
11
-
12
- The above copyright notice and this permission notice shall be included in all
13
- copies or substantial portions of the Software.
14
-
15
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
- SOFTWARE.