gumloop 0.5.0__tar.gz → 0.5.2__tar.gz

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 (189) hide show
  1. {gumloop-0.5.0 → gumloop-0.5.2}/PKG-INFO +33 -1
  2. {gumloop-0.5.0 → gumloop-0.5.2}/README.md +32 -0
  3. {gumloop-0.5.0 → gumloop-0.5.2}/src/gumloop/_http.py +14 -0
  4. {gumloop-0.5.0 → gumloop-0.5.2}/src/gumloop/_version.py +1 -1
  5. gumloop-0.5.2/src/gumloop/brain_sync.py +59 -0
  6. gumloop-0.5.2/src/gumloop/cli/commands/brain.py +530 -0
  7. {gumloop-0.5.0 → gumloop-0.5.2}/src/gumloop/cli/commands/sessions.py +11 -3
  8. {gumloop-0.5.0 → gumloop-0.5.2}/src/gumloop/cli/plugin_assets/gumloop/skills/gumloop-cli/references/commands.md +5 -1
  9. gumloop-0.5.2/src/gumloop/resources/brain.py +216 -0
  10. {gumloop-0.5.0 → gumloop-0.5.2}/src/gumloop/resources/skills.py +7 -15
  11. {gumloop-0.5.0 → gumloop-0.5.2}/src/gumloop/types.py +80 -0
  12. gumloop-0.5.2/tests/cli/test_brain_sync.py +181 -0
  13. {gumloop-0.5.0 → gumloop-0.5.2}/tests/cli/test_sessions.py +32 -0
  14. gumloop-0.5.2/tests/sdk/test_brain.py +177 -0
  15. gumloop-0.5.0/src/gumloop/cli/commands/brain.py +0 -66
  16. gumloop-0.5.0/src/gumloop/resources/brain.py +0 -47
  17. gumloop-0.5.0/tests/sdk/test_brain.py +0 -75
  18. {gumloop-0.5.0 → gumloop-0.5.2}/.env.example +0 -0
  19. {gumloop-0.5.0 → gumloop-0.5.2}/.github/workflows/ci.yml +0 -0
  20. {gumloop-0.5.0 → gumloop-0.5.2}/.github/workflows/release.yml +0 -0
  21. {gumloop-0.5.0 → gumloop-0.5.2}/.gitignore +0 -0
  22. {gumloop-0.5.0 → gumloop-0.5.2}/.pre-commit-config.yaml +0 -0
  23. {gumloop-0.5.0 → gumloop-0.5.2}/.python-version +0 -0
  24. {gumloop-0.5.0 → gumloop-0.5.2}/LICENSE +0 -0
  25. {gumloop-0.5.0 → gumloop-0.5.2}/pyproject.toml +0 -0
  26. {gumloop-0.5.0 → gumloop-0.5.2}/scripts/check +0 -0
  27. {gumloop-0.5.0 → gumloop-0.5.2}/scripts/fmt +0 -0
  28. {gumloop-0.5.0 → gumloop-0.5.2}/scripts/import-logins.sh +0 -0
  29. {gumloop-0.5.0 → gumloop-0.5.2}/scripts/install.sh +0 -0
  30. {gumloop-0.5.0 → gumloop-0.5.2}/scripts/sdk_examples.py +0 -0
  31. {gumloop-0.5.0 → gumloop-0.5.2}/src/gumloop/__init__.py +0 -0
  32. {gumloop-0.5.0 → gumloop-0.5.2}/src/gumloop/_auth.py +0 -0
  33. {gumloop-0.5.0 → gumloop-0.5.2}/src/gumloop/_client.py +0 -0
  34. {gumloop-0.5.0 → gumloop-0.5.2}/src/gumloop/_gumcp_transport.py +0 -0
  35. {gumloop-0.5.0 → gumloop-0.5.2}/src/gumloop/browser_logins/__init__.py +0 -0
  36. {gumloop-0.5.0 → gumloop-0.5.2}/src/gumloop/browser_logins/chromium_cookies.py +0 -0
  37. {gumloop-0.5.0 → gumloop-0.5.2}/src/gumloop/browser_logins/discovery.py +0 -0
  38. {gumloop-0.5.0 → gumloop-0.5.2}/src/gumloop/browser_logins/extract.py +0 -0
  39. {gumloop-0.5.0 → gumloop-0.5.2}/src/gumloop/browser_logins/filter.py +0 -0
  40. {gumloop-0.5.0 → gumloop-0.5.2}/src/gumloop/browser_logins/firefox_cookies.py +0 -0
  41. {gumloop-0.5.0 → gumloop-0.5.2}/src/gumloop/cli/__init__.py +0 -0
  42. {gumloop-0.5.0 → gumloop-0.5.2}/src/gumloop/cli/commands/__init__.py +0 -0
  43. {gumloop-0.5.0 → gumloop-0.5.2}/src/gumloop/cli/commands/_args_input.py +0 -0
  44. {gumloop-0.5.0 → gumloop-0.5.2}/src/gumloop/cli/commands/_downloads.py +0 -0
  45. {gumloop-0.5.0 → gumloop-0.5.2}/src/gumloop/cli/commands/_sync_output.py +0 -0
  46. {gumloop-0.5.0 → gumloop-0.5.2}/src/gumloop/cli/commands/agents.py +0 -0
  47. {gumloop-0.5.0 → gumloop-0.5.2}/src/gumloop/cli/commands/artifacts.py +0 -0
  48. {gumloop-0.5.0 → gumloop-0.5.2}/src/gumloop/cli/commands/auth.py +0 -0
  49. {gumloop-0.5.0 → gumloop-0.5.2}/src/gumloop/cli/commands/browser.py +0 -0
  50. {gumloop-0.5.0 → gumloop-0.5.2}/src/gumloop/cli/commands/chat.py +0 -0
  51. {gumloop-0.5.0 → gumloop-0.5.2}/src/gumloop/cli/commands/evaluations.py +0 -0
  52. {gumloop-0.5.0 → gumloop-0.5.2}/src/gumloop/cli/commands/mcp.py +0 -0
  53. {gumloop-0.5.0 → gumloop-0.5.2}/src/gumloop/cli/commands/models.py +0 -0
  54. {gumloop-0.5.0 → gumloop-0.5.2}/src/gumloop/cli/commands/plugin.py +0 -0
  55. {gumloop-0.5.0 → gumloop-0.5.2}/src/gumloop/cli/commands/skills.py +0 -0
  56. {gumloop-0.5.0 → gumloop-0.5.2}/src/gumloop/cli/commands/sync.py +0 -0
  57. {gumloop-0.5.0 → gumloop-0.5.2}/src/gumloop/cli/commands/update.py +0 -0
  58. {gumloop-0.5.0 → gumloop-0.5.2}/src/gumloop/cli/console.py +0 -0
  59. {gumloop-0.5.0 → gumloop-0.5.2}/src/gumloop/cli/context.py +0 -0
  60. {gumloop-0.5.0 → gumloop-0.5.2}/src/gumloop/cli/credentials.py +0 -0
  61. {gumloop-0.5.0 → gumloop-0.5.2}/src/gumloop/cli/errors.py +0 -0
  62. {gumloop-0.5.0 → gumloop-0.5.2}/src/gumloop/cli/main.py +0 -0
  63. {gumloop-0.5.0 → gumloop-0.5.2}/src/gumloop/cli/oauth.py +0 -0
  64. {gumloop-0.5.0 → gumloop-0.5.2}/src/gumloop/cli/plugin_assets/gumloop/plugin.json +0 -0
  65. {gumloop-0.5.0 → gumloop-0.5.2}/src/gumloop/cli/plugin_assets/gumloop/skills/gumloop-cli/SKILL.md +0 -0
  66. {gumloop-0.5.0 → gumloop-0.5.2}/src/gumloop/errors.py +0 -0
  67. {gumloop-0.5.0 → gumloop-0.5.2}/src/gumloop/oauth.py +0 -0
  68. {gumloop-0.5.0 → gumloop-0.5.2}/src/gumloop/py.typed +0 -0
  69. {gumloop-0.5.0 → gumloop-0.5.2}/src/gumloop/resources/__init__.py +0 -0
  70. {gumloop-0.5.0 → gumloop-0.5.2}/src/gumloop/resources/agents.py +0 -0
  71. {gumloop-0.5.0 → gumloop-0.5.2}/src/gumloop/resources/artifacts.py +0 -0
  72. {gumloop-0.5.0 → gumloop-0.5.2}/src/gumloop/resources/browser_profiles.py +0 -0
  73. {gumloop-0.5.0 → gumloop-0.5.2}/src/gumloop/resources/chat.py +0 -0
  74. {gumloop-0.5.0 → gumloop-0.5.2}/src/gumloop/resources/evaluations.py +0 -0
  75. {gumloop-0.5.0 → gumloop-0.5.2}/src/gumloop/resources/mcp.py +0 -0
  76. {gumloop-0.5.0 → gumloop-0.5.2}/src/gumloop/resources/organizations.py +0 -0
  77. {gumloop-0.5.0 → gumloop-0.5.2}/src/gumloop/resources/sessions.py +0 -0
  78. {gumloop-0.5.0 → gumloop-0.5.2}/src/gumloop/resources/sync.py +0 -0
  79. {gumloop-0.5.0 → gumloop-0.5.2}/src/gumloop/resources/teams.py +0 -0
  80. {gumloop-0.5.0 → gumloop-0.5.2}/src/gumloop/spec/__init__.py +0 -0
  81. {gumloop-0.5.0 → gumloop-0.5.2}/src/gumloop/spec/_compat.py +0 -0
  82. {gumloop-0.5.0 → gumloop-0.5.2}/src/gumloop/spec/_extensions.py +0 -0
  83. {gumloop-0.5.0 → gumloop-0.5.2}/src/gumloop/sync/__init__.py +0 -0
  84. {gumloop-0.5.0 → gumloop-0.5.2}/src/gumloop/sync/archive.py +0 -0
  85. {gumloop-0.5.0 → gumloop-0.5.2}/src/gumloop/sync/bundle.py +0 -0
  86. {gumloop-0.5.0 → gumloop-0.5.2}/src/gumloop/sync/bundle_validation.py +0 -0
  87. {gumloop-0.5.0 → gumloop-0.5.2}/src/gumloop/sync/errors.py +0 -0
  88. {gumloop-0.5.0 → gumloop-0.5.2}/src/gumloop/sync/hashing.py +0 -0
  89. {gumloop-0.5.0 → gumloop-0.5.2}/src/gumloop/sync/installed.py +0 -0
  90. {gumloop-0.5.0 → gumloop-0.5.2}/src/gumloop/sync/local_state.py +0 -0
  91. {gumloop-0.5.0 → gumloop-0.5.2}/src/gumloop/sync/lock.py +0 -0
  92. {gumloop-0.5.0 → gumloop-0.5.2}/src/gumloop/sync/markers.py +0 -0
  93. {gumloop-0.5.0 → gumloop-0.5.2}/src/gumloop/sync/reconcile.py +0 -0
  94. {gumloop-0.5.0 → gumloop-0.5.2}/src/gumloop/sync/reporting.py +0 -0
  95. {gumloop-0.5.0 → gumloop-0.5.2}/src/gumloop/sync/results.py +0 -0
  96. {gumloop-0.5.0 → gumloop-0.5.2}/src/gumloop/sync/run.py +0 -0
  97. {gumloop-0.5.0 → gumloop-0.5.2}/src/gumloop/sync/scheduler.py +0 -0
  98. {gumloop-0.5.0 → gumloop-0.5.2}/src/gumloop/sync/target_files.py +0 -0
  99. {gumloop-0.5.0 → gumloop-0.5.2}/src/gumloop/sync/targets.py +0 -0
  100. {gumloop-0.5.0 → gumloop-0.5.2}/src/gumloop/sync/wire.py +0 -0
  101. {gumloop-0.5.0 → gumloop-0.5.2}/tests/__init__.py +0 -0
  102. {gumloop-0.5.0 → gumloop-0.5.2}/tests/cli/__init__.py +0 -0
  103. {gumloop-0.5.0 → gumloop-0.5.2}/tests/cli/conftest.py +0 -0
  104. {gumloop-0.5.0 → gumloop-0.5.2}/tests/cli/sync_scenario_helpers.py +0 -0
  105. {gumloop-0.5.0 → gumloop-0.5.2}/tests/cli/sync_test_fakes.py +0 -0
  106. {gumloop-0.5.0 → gumloop-0.5.2}/tests/cli/test_agents.py +0 -0
  107. {gumloop-0.5.0 → gumloop-0.5.2}/tests/cli/test_args_input.py +0 -0
  108. {gumloop-0.5.0 → gumloop-0.5.2}/tests/cli/test_artifacts.py +0 -0
  109. {gumloop-0.5.0 → gumloop-0.5.2}/tests/cli/test_brain.py +0 -0
  110. {gumloop-0.5.0 → gumloop-0.5.2}/tests/cli/test_browser_logins.py +0 -0
  111. {gumloop-0.5.0 → gumloop-0.5.2}/tests/cli/test_chat.py +0 -0
  112. {gumloop-0.5.0 → gumloop-0.5.2}/tests/cli/test_context.py +0 -0
  113. {gumloop-0.5.0 → gumloop-0.5.2}/tests/cli/test_credentials.py +0 -0
  114. {gumloop-0.5.0 → gumloop-0.5.2}/tests/cli/test_evaluations.py +0 -0
  115. {gumloop-0.5.0 → gumloop-0.5.2}/tests/cli/test_home_isolation.py +0 -0
  116. {gumloop-0.5.0 → gumloop-0.5.2}/tests/cli/test_login.py +0 -0
  117. {gumloop-0.5.0 → gumloop-0.5.2}/tests/cli/test_main.py +0 -0
  118. {gumloop-0.5.0 → gumloop-0.5.2}/tests/cli/test_mcp.py +0 -0
  119. {gumloop-0.5.0 → gumloop-0.5.2}/tests/cli/test_models.py +0 -0
  120. {gumloop-0.5.0 → gumloop-0.5.2}/tests/cli/test_plugin.py +0 -0
  121. {gumloop-0.5.0 → gumloop-0.5.2}/tests/cli/test_skills.py +0 -0
  122. {gumloop-0.5.0 → gumloop-0.5.2}/tests/cli/test_sync_bundle.py +0 -0
  123. {gumloop-0.5.0 → gumloop-0.5.2}/tests/cli/test_sync_command.py +0 -0
  124. {gumloop-0.5.0 → gumloop-0.5.2}/tests/cli/test_sync_command_safety.py +0 -0
  125. {gumloop-0.5.0 → gumloop-0.5.2}/tests/cli/test_sync_contract_fixtures.py +0 -0
  126. {gumloop-0.5.0 → gumloop-0.5.2}/tests/cli/test_sync_enrollment.py +0 -0
  127. {gumloop-0.5.0 → gumloop-0.5.2}/tests/cli/test_sync_fixture_corpus.py +0 -0
  128. {gumloop-0.5.0 → gumloop-0.5.2}/tests/cli/test_sync_hashing.py +0 -0
  129. {gumloop-0.5.0 → gumloop-0.5.2}/tests/cli/test_sync_installed.py +0 -0
  130. {gumloop-0.5.0 → gumloop-0.5.2}/tests/cli/test_sync_reconciliation_safety.py +0 -0
  131. {gumloop-0.5.0 → gumloop-0.5.2}/tests/cli/test_sync_scheduler_macos.py +0 -0
  132. {gumloop-0.5.0 → gumloop-0.5.2}/tests/cli/test_sync_targets.py +0 -0
  133. {gumloop-0.5.0 → gumloop-0.5.2}/tests/conftest.py +0 -0
  134. {gumloop-0.5.0 → gumloop-0.5.2}/tests/fixtures/skill_sync/v1/README.md +0 -0
  135. {gumloop-0.5.0 → gumloop-0.5.2}/tests/fixtures/skill_sync/v1/archives/generated-bundle-transfer-limit.json +0 -0
  136. {gumloop-0.5.0 → gumloop-0.5.2}/tests/fixtures/skill_sync/v1/archives/invalid-absolute-path.zip +0 -0
  137. {gumloop-0.5.0 → gumloop-0.5.2}/tests/fixtures/skill_sync/v1/archives/invalid-bytes-per-file-limit.zip +0 -0
  138. {gumloop-0.5.0 → gumloop-0.5.2}/tests/fixtures/skill_sync/v1/archives/invalid-device.zip +0 -0
  139. {gumloop-0.5.0 → gumloop-0.5.2}/tests/fixtures/skill_sync/v1/archives/invalid-duplicate-entry.zip +0 -0
  140. {gumloop-0.5.0 → gumloop-0.5.2}/tests/fixtures/skill_sync/v1/archives/invalid-fifo.zip +0 -0
  141. {gumloop-0.5.0 → gumloop-0.5.2}/tests/fixtures/skill_sync/v1/archives/invalid-files-per-skill-limit.zip +0 -0
  142. {gumloop-0.5.0 → gumloop-0.5.2}/tests/fixtures/skill_sync/v1/archives/invalid-nul-name.zip +0 -0
  143. {gumloop-0.5.0 → gumloop-0.5.2}/tests/fixtures/skill_sync/v1/archives/invalid-parent-traversal.zip +0 -0
  144. {gumloop-0.5.0 → gumloop-0.5.2}/tests/fixtures/skill_sync/v1/archives/invalid-reserved-marker.zip +0 -0
  145. {gumloop-0.5.0 → gumloop-0.5.2}/tests/fixtures/skill_sync/v1/archives/invalid-socket.zip +0 -0
  146. {gumloop-0.5.0 → gumloop-0.5.2}/tests/fixtures/skill_sync/v1/archives/invalid-symlink.zip +0 -0
  147. {gumloop-0.5.0 → gumloop-0.5.2}/tests/fixtures/skill_sync/v1/archives/invalid-total-uncompressed-limit.zip +0 -0
  148. {gumloop-0.5.0 → gumloop-0.5.2}/tests/fixtures/skill_sync/v1/archives/invalid-windows-drive-prefix.zip +0 -0
  149. {gumloop-0.5.0 → gumloop-0.5.2}/tests/fixtures/skill_sync/v1/archives/valid-single-skill-stored.zip +0 -0
  150. {gumloop-0.5.0 → gumloop-0.5.2}/tests/fixtures/skill_sync/v1/archives/valid-single-skill.zip +0 -0
  151. {gumloop-0.5.0 → gumloop-0.5.2}/tests/fixtures/skill_sync/v1/bundles/newer-than-plan.zip +0 -0
  152. {gumloop-0.5.0 → gumloop-0.5.2}/tests/fixtures/skill_sync/v1/bundles/normal.zip +0 -0
  153. {gumloop-0.5.0 → gumloop-0.5.2}/tests/fixtures/skill_sync/v1/catalog.json +0 -0
  154. {gumloop-0.5.0 → gumloop-0.5.2}/tests/fixtures/skill_sync/v1/corpus.sha256 +0 -0
  155. {gumloop-0.5.0 → gumloop-0.5.2}/tests/fixtures/skill_sync/v1/hashes/content-v1.json +0 -0
  156. {gumloop-0.5.0 → gumloop-0.5.2}/tests/fixtures/skill_sync/v1/hashes/manifest-v1.json +0 -0
  157. {gumloop-0.5.0 → gumloop-0.5.2}/tests/fixtures/skill_sync/v1/manifests/invalid-duplicate-identity.json +0 -0
  158. {gumloop-0.5.0 → gumloop-0.5.2}/tests/fixtures/skill_sync/v1/manifests/invalid-duplicate-install-name.json +0 -0
  159. {gumloop-0.5.0 → gumloop-0.5.2}/tests/fixtures/skill_sync/v1/manifests/invalid-manifest-hash.json +0 -0
  160. {gumloop-0.5.0 → gumloop-0.5.2}/tests/fixtures/skill_sync/v1/manifests/newer-than-plan.json +0 -0
  161. {gumloop-0.5.0 → gumloop-0.5.2}/tests/fixtures/skill_sync/v1/manifests/normal-bundle.json +0 -0
  162. {gumloop-0.5.0 → gumloop-0.5.2}/tests/fixtures/skill_sync/v1/responses/below-pro.json +0 -0
  163. {gumloop-0.5.0 → gumloop-0.5.2}/tests/fixtures/skill_sync/v1/responses/cli-upgrade-required.json +0 -0
  164. {gumloop-0.5.0 → gumloop-0.5.2}/tests/fixtures/skill_sync/v1/responses/lost-membership.json +0 -0
  165. {gumloop-0.5.0 → gumloop-0.5.2}/tests/fixtures/skill_sync/v1/responses/normal-empty-plan.json +0 -0
  166. {gumloop-0.5.0 → gumloop-0.5.2}/tests/fixtures/skill_sync/v1/responses/normal-plan.json +0 -0
  167. {gumloop-0.5.0 → gumloop-0.5.2}/tests/integration/conftest.py +0 -0
  168. {gumloop-0.5.0 → gumloop-0.5.2}/tests/integration/test_live.py +0 -0
  169. {gumloop-0.5.0 → gumloop-0.5.2}/tests/sdk/__init__.py +0 -0
  170. {gumloop-0.5.0 → gumloop-0.5.2}/tests/sdk/conftest.py +0 -0
  171. {gumloop-0.5.0 → gumloop-0.5.2}/tests/sdk/helpers.py +0 -0
  172. {gumloop-0.5.0 → gumloop-0.5.2}/tests/sdk/test_agents.py +0 -0
  173. {gumloop-0.5.0 → gumloop-0.5.2}/tests/sdk/test_artifacts.py +0 -0
  174. {gumloop-0.5.0 → gumloop-0.5.2}/tests/sdk/test_chat.py +0 -0
  175. {gumloop-0.5.0 → gumloop-0.5.2}/tests/sdk/test_client.py +0 -0
  176. {gumloop-0.5.0 → gumloop-0.5.2}/tests/sdk/test_errors.py +0 -0
  177. {gumloop-0.5.0 → gumloop-0.5.2}/tests/sdk/test_evaluations.py +0 -0
  178. {gumloop-0.5.0 → gumloop-0.5.2}/tests/sdk/test_gumcp_transport.py +0 -0
  179. {gumloop-0.5.0 → gumloop-0.5.2}/tests/sdk/test_mcp.py +0 -0
  180. {gumloop-0.5.0 → gumloop-0.5.2}/tests/sdk/test_models.py +0 -0
  181. {gumloop-0.5.0 → gumloop-0.5.2}/tests/sdk/test_oauth.py +0 -0
  182. {gumloop-0.5.0 → gumloop-0.5.2}/tests/sdk/test_organizations.py +0 -0
  183. {gumloop-0.5.0 → gumloop-0.5.2}/tests/sdk/test_sessions.py +0 -0
  184. {gumloop-0.5.0 → gumloop-0.5.2}/tests/sdk/test_skills.py +0 -0
  185. {gumloop-0.5.0 → gumloop-0.5.2}/tests/sdk/test_sync.py +0 -0
  186. {gumloop-0.5.0 → gumloop-0.5.2}/tests/sdk/test_teams.py +0 -0
  187. {gumloop-0.5.0 → gumloop-0.5.2}/tests/skill_sync_fixtures.py +0 -0
  188. {gumloop-0.5.0 → gumloop-0.5.2}/tests/test_client.py +0 -0
  189. {gumloop-0.5.0 → gumloop-0.5.2}/uv.lock +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: gumloop
3
- Version: 0.5.0
3
+ Version: 0.5.2
4
4
  Summary: Python client for the Gumloop API
5
5
  Project-URL: Homepage, https://github.com/gumloop/gumloop-py
6
6
  Project-URL: Bug Tracker, https://github.com/gumloop/gumloop-py/issues
@@ -62,6 +62,22 @@ curl -fsSL https://gumloop.com/cli/import-logins.sh | sh
62
62
 
63
63
  Cookies only: local storage and IndexedDB stay on your machine, so sites that keep the session there ask the agent to sign in once, after which the agent's browser keeps it. Imported cookies are encrypted with the profile's own key before storage and are never shown back in the UI or API; sign-ins the agent picks up while running are saved back to the same profile. Rename, remove sites from, or delete profiles on the Secrets page in Gumloop.
64
64
 
65
+ ### Sync a folder into your Brain
66
+
67
+ Mirror a local directory into a file-upload Brain source. Files are compared by name and sha256, so a re-run uploads only what changed; indexing starts on its own after each upload.
68
+
69
+ ```bash
70
+ gumloop brain sync ./docs --create "Engineering docs" # personal source
71
+ gumloop brain sync ./docs --source <source_id> --prune # existing source, delete remote files that are gone locally
72
+ gumloop brain sync ./runbooks --create "Runbooks" --team-id <team_id> # team source
73
+ gumloop brain sync ./policies --create "Policies" --scope organization --require-approval --approve
74
+ gumloop brain sources list
75
+ gumloop brain files list <source_id>
76
+ gumloop brain search "expense policy"
77
+ ```
78
+
79
+ `--require-approval` creates the source as a draft that only estimates credits; `gumloop brain sources estimate <source_id>` shows the number and `gumloop brain sources approve <source_id>` (or `--approve`) starts indexing. Without it the source is active and indexes on the first upload.
80
+
65
81
  ## SDK
66
82
 
67
83
  To use the client as a library in your own Python project:
@@ -155,3 +171,19 @@ print(decision.route.model, decision.route.lane, decision.route.fallback_models)
155
171
  ```
156
172
 
157
173
  Omit `models` to route across the full Chew catalog. Each call bills one small classifier completion.
174
+
175
+ ## Put files into your Brain
176
+
177
+ ```python
178
+ from gumloop import Gumloop
179
+
180
+ client = Gumloop(api_key="your_api_key", user_id="your_user_id")
181
+
182
+ source = client.brain.create_source("Engineering docs").source
183
+ upload = client.brain.upload_files(source.id, {"handbook.pdf": open("handbook.pdf", "rb").read()})
184
+
185
+ print([f.status for f in upload.files], upload.rejected)
186
+ print(client.brain.search("onboarding checklist").results[0].title)
187
+ ```
188
+
189
+ Every file carries its `sha256`, so `client.brain.list_files(source.id)` is enough to diff a local folder against the source. Only file-upload sources can be created through the API; Notion, Drive and other connected sources are set up in the Gumloop app.
@@ -36,6 +36,22 @@ curl -fsSL https://gumloop.com/cli/import-logins.sh | sh
36
36
 
37
37
  Cookies only: local storage and IndexedDB stay on your machine, so sites that keep the session there ask the agent to sign in once, after which the agent's browser keeps it. Imported cookies are encrypted with the profile's own key before storage and are never shown back in the UI or API; sign-ins the agent picks up while running are saved back to the same profile. Rename, remove sites from, or delete profiles on the Secrets page in Gumloop.
38
38
 
39
+ ### Sync a folder into your Brain
40
+
41
+ Mirror a local directory into a file-upload Brain source. Files are compared by name and sha256, so a re-run uploads only what changed; indexing starts on its own after each upload.
42
+
43
+ ```bash
44
+ gumloop brain sync ./docs --create "Engineering docs" # personal source
45
+ gumloop brain sync ./docs --source <source_id> --prune # existing source, delete remote files that are gone locally
46
+ gumloop brain sync ./runbooks --create "Runbooks" --team-id <team_id> # team source
47
+ gumloop brain sync ./policies --create "Policies" --scope organization --require-approval --approve
48
+ gumloop brain sources list
49
+ gumloop brain files list <source_id>
50
+ gumloop brain search "expense policy"
51
+ ```
52
+
53
+ `--require-approval` creates the source as a draft that only estimates credits; `gumloop brain sources estimate <source_id>` shows the number and `gumloop brain sources approve <source_id>` (or `--approve`) starts indexing. Without it the source is active and indexes on the first upload.
54
+
39
55
  ## SDK
40
56
 
41
57
  To use the client as a library in your own Python project:
@@ -129,3 +145,19 @@ print(decision.route.model, decision.route.lane, decision.route.fallback_models)
129
145
  ```
130
146
 
131
147
  Omit `models` to route across the full Chew catalog. Each call bills one small classifier completion.
148
+
149
+ ## Put files into your Brain
150
+
151
+ ```python
152
+ from gumloop import Gumloop
153
+
154
+ client = Gumloop(api_key="your_api_key", user_id="your_user_id")
155
+
156
+ source = client.brain.create_source("Engineering docs").source
157
+ upload = client.brain.upload_files(source.id, {"handbook.pdf": open("handbook.pdf", "rb").read()})
158
+
159
+ print([f.status for f in upload.files], upload.rejected)
160
+ print(client.brain.search("onboarding checklist").results[0].title)
161
+ ```
162
+
163
+ Every file carries its `sha256`, so `client.brain.list_files(source.id)` is enough to diff a local folder against the source. Only file-upload sources can be created through the API; Notion, Drive and other connected sources are set up in the Gumloop app.
@@ -59,6 +59,20 @@ class ResponseSizeExceededError(Exception):
59
59
  """Raised when a streamed response body exceeds the caller's byte limit."""
60
60
 
61
61
 
62
+ UploadFile = tuple[str, bytes | str] | tuple[str, bytes | str, str]
63
+
64
+
65
+ def multipart_files(files: Mapping[str, bytes | str] | list[UploadFile]) -> list[tuple[str, Any]]:
66
+ """``(filename, content[, media_type])`` items or a ``{filename: content}`` map as the ``files`` multipart field."""
67
+ items = files.items() if isinstance(files, Mapping) else files
68
+ multipart = []
69
+ for item in items:
70
+ filename, content, *rest = item
71
+ media_type = rest[0] if rest else "application/octet-stream"
72
+ multipart.append(("files", (filename, content, media_type)))
73
+ return multipart
74
+
75
+
62
76
  def _omit_none_params(params: Mapping[str, Any] | None) -> dict[str, Any] | None:
63
77
  # Backend treats absent ``?foo`` and ``?foo=`` as different signals
64
78
  # ("not provided" vs "empty string"); drop None values so the wire URL
@@ -1,3 +1,3 @@
1
1
  from __future__ import annotations
2
2
 
3
- __version__ = "0.5.0"
3
+ __version__ = "0.5.2"
@@ -0,0 +1,59 @@
1
+ from __future__ import annotations
2
+
3
+ import hashlib
4
+ from dataclasses import dataclass
5
+ from dataclasses import field
6
+ from pathlib import Path
7
+
8
+ from gumloop.types import BrainFile
9
+
10
+ _HASH_CHUNK_BYTES = 1024 * 1024
11
+
12
+
13
+ @dataclass(frozen=True)
14
+ class LocalFile:
15
+ name: str
16
+ path: Path
17
+ sha256: str
18
+
19
+
20
+ def scan_directory(directory: Path) -> list[LocalFile]:
21
+ """Every regular file under ``directory``, named by its POSIX path relative to the root."""
22
+ files = []
23
+ for path in sorted(p for p in directory.rglob("*") if p.is_file()):
24
+ if any(part.startswith(".") for part in path.relative_to(directory).parts):
25
+ continue
26
+ digest = hashlib.sha256()
27
+ with path.open("rb") as handle:
28
+ for chunk in iter(lambda: handle.read(_HASH_CHUNK_BYTES), b""):
29
+ digest.update(chunk)
30
+ files.append(LocalFile(name=path.relative_to(directory).as_posix(), path=path, sha256=digest.hexdigest()))
31
+ return files
32
+
33
+
34
+ @dataclass(frozen=True)
35
+ class BrainSyncPlan:
36
+ upload: list[LocalFile] = field(default_factory=list)
37
+ replace: list[BrainFile] = field(default_factory=list)
38
+ prune: list[BrainFile] = field(default_factory=list)
39
+ unchanged: list[LocalFile] = field(default_factory=list)
40
+
41
+ @classmethod
42
+ def build(cls, local: list[LocalFile], remote: list[BrainFile]) -> BrainSyncPlan:
43
+ """Diff by (name, sha256). A remote file without ``sha256`` predates hashing and is re-uploaded."""
44
+ remote_by_name = {remote_file.file_name: remote_file for remote_file in remote}
45
+ upload, replace, unchanged = [], [], []
46
+ for local_file in local:
47
+ remote_file = remote_by_name.pop(local_file.name, None)
48
+ if remote_file is None:
49
+ upload.append(local_file)
50
+ elif remote_file.sha256 == local_file.sha256:
51
+ unchanged.append(local_file)
52
+ else:
53
+ replace.append(remote_file)
54
+ upload.append(local_file)
55
+ return cls(upload=upload, replace=replace, prune=list(remote_by_name.values()), unchanged=unchanged)
56
+
57
+ @property
58
+ def is_noop(self) -> bool:
59
+ return not (self.upload or self.replace or self.prune)