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.
- {gumloop-0.5.0 → gumloop-0.5.2}/PKG-INFO +33 -1
- {gumloop-0.5.0 → gumloop-0.5.2}/README.md +32 -0
- {gumloop-0.5.0 → gumloop-0.5.2}/src/gumloop/_http.py +14 -0
- {gumloop-0.5.0 → gumloop-0.5.2}/src/gumloop/_version.py +1 -1
- gumloop-0.5.2/src/gumloop/brain_sync.py +59 -0
- gumloop-0.5.2/src/gumloop/cli/commands/brain.py +530 -0
- {gumloop-0.5.0 → gumloop-0.5.2}/src/gumloop/cli/commands/sessions.py +11 -3
- {gumloop-0.5.0 → gumloop-0.5.2}/src/gumloop/cli/plugin_assets/gumloop/skills/gumloop-cli/references/commands.md +5 -1
- gumloop-0.5.2/src/gumloop/resources/brain.py +216 -0
- {gumloop-0.5.0 → gumloop-0.5.2}/src/gumloop/resources/skills.py +7 -15
- {gumloop-0.5.0 → gumloop-0.5.2}/src/gumloop/types.py +80 -0
- gumloop-0.5.2/tests/cli/test_brain_sync.py +181 -0
- {gumloop-0.5.0 → gumloop-0.5.2}/tests/cli/test_sessions.py +32 -0
- gumloop-0.5.2/tests/sdk/test_brain.py +177 -0
- gumloop-0.5.0/src/gumloop/cli/commands/brain.py +0 -66
- gumloop-0.5.0/src/gumloop/resources/brain.py +0 -47
- gumloop-0.5.0/tests/sdk/test_brain.py +0 -75
- {gumloop-0.5.0 → gumloop-0.5.2}/.env.example +0 -0
- {gumloop-0.5.0 → gumloop-0.5.2}/.github/workflows/ci.yml +0 -0
- {gumloop-0.5.0 → gumloop-0.5.2}/.github/workflows/release.yml +0 -0
- {gumloop-0.5.0 → gumloop-0.5.2}/.gitignore +0 -0
- {gumloop-0.5.0 → gumloop-0.5.2}/.pre-commit-config.yaml +0 -0
- {gumloop-0.5.0 → gumloop-0.5.2}/.python-version +0 -0
- {gumloop-0.5.0 → gumloop-0.5.2}/LICENSE +0 -0
- {gumloop-0.5.0 → gumloop-0.5.2}/pyproject.toml +0 -0
- {gumloop-0.5.0 → gumloop-0.5.2}/scripts/check +0 -0
- {gumloop-0.5.0 → gumloop-0.5.2}/scripts/fmt +0 -0
- {gumloop-0.5.0 → gumloop-0.5.2}/scripts/import-logins.sh +0 -0
- {gumloop-0.5.0 → gumloop-0.5.2}/scripts/install.sh +0 -0
- {gumloop-0.5.0 → gumloop-0.5.2}/scripts/sdk_examples.py +0 -0
- {gumloop-0.5.0 → gumloop-0.5.2}/src/gumloop/__init__.py +0 -0
- {gumloop-0.5.0 → gumloop-0.5.2}/src/gumloop/_auth.py +0 -0
- {gumloop-0.5.0 → gumloop-0.5.2}/src/gumloop/_client.py +0 -0
- {gumloop-0.5.0 → gumloop-0.5.2}/src/gumloop/_gumcp_transport.py +0 -0
- {gumloop-0.5.0 → gumloop-0.5.2}/src/gumloop/browser_logins/__init__.py +0 -0
- {gumloop-0.5.0 → gumloop-0.5.2}/src/gumloop/browser_logins/chromium_cookies.py +0 -0
- {gumloop-0.5.0 → gumloop-0.5.2}/src/gumloop/browser_logins/discovery.py +0 -0
- {gumloop-0.5.0 → gumloop-0.5.2}/src/gumloop/browser_logins/extract.py +0 -0
- {gumloop-0.5.0 → gumloop-0.5.2}/src/gumloop/browser_logins/filter.py +0 -0
- {gumloop-0.5.0 → gumloop-0.5.2}/src/gumloop/browser_logins/firefox_cookies.py +0 -0
- {gumloop-0.5.0 → gumloop-0.5.2}/src/gumloop/cli/__init__.py +0 -0
- {gumloop-0.5.0 → gumloop-0.5.2}/src/gumloop/cli/commands/__init__.py +0 -0
- {gumloop-0.5.0 → gumloop-0.5.2}/src/gumloop/cli/commands/_args_input.py +0 -0
- {gumloop-0.5.0 → gumloop-0.5.2}/src/gumloop/cli/commands/_downloads.py +0 -0
- {gumloop-0.5.0 → gumloop-0.5.2}/src/gumloop/cli/commands/_sync_output.py +0 -0
- {gumloop-0.5.0 → gumloop-0.5.2}/src/gumloop/cli/commands/agents.py +0 -0
- {gumloop-0.5.0 → gumloop-0.5.2}/src/gumloop/cli/commands/artifacts.py +0 -0
- {gumloop-0.5.0 → gumloop-0.5.2}/src/gumloop/cli/commands/auth.py +0 -0
- {gumloop-0.5.0 → gumloop-0.5.2}/src/gumloop/cli/commands/browser.py +0 -0
- {gumloop-0.5.0 → gumloop-0.5.2}/src/gumloop/cli/commands/chat.py +0 -0
- {gumloop-0.5.0 → gumloop-0.5.2}/src/gumloop/cli/commands/evaluations.py +0 -0
- {gumloop-0.5.0 → gumloop-0.5.2}/src/gumloop/cli/commands/mcp.py +0 -0
- {gumloop-0.5.0 → gumloop-0.5.2}/src/gumloop/cli/commands/models.py +0 -0
- {gumloop-0.5.0 → gumloop-0.5.2}/src/gumloop/cli/commands/plugin.py +0 -0
- {gumloop-0.5.0 → gumloop-0.5.2}/src/gumloop/cli/commands/skills.py +0 -0
- {gumloop-0.5.0 → gumloop-0.5.2}/src/gumloop/cli/commands/sync.py +0 -0
- {gumloop-0.5.0 → gumloop-0.5.2}/src/gumloop/cli/commands/update.py +0 -0
- {gumloop-0.5.0 → gumloop-0.5.2}/src/gumloop/cli/console.py +0 -0
- {gumloop-0.5.0 → gumloop-0.5.2}/src/gumloop/cli/context.py +0 -0
- {gumloop-0.5.0 → gumloop-0.5.2}/src/gumloop/cli/credentials.py +0 -0
- {gumloop-0.5.0 → gumloop-0.5.2}/src/gumloop/cli/errors.py +0 -0
- {gumloop-0.5.0 → gumloop-0.5.2}/src/gumloop/cli/main.py +0 -0
- {gumloop-0.5.0 → gumloop-0.5.2}/src/gumloop/cli/oauth.py +0 -0
- {gumloop-0.5.0 → gumloop-0.5.2}/src/gumloop/cli/plugin_assets/gumloop/plugin.json +0 -0
- {gumloop-0.5.0 → gumloop-0.5.2}/src/gumloop/cli/plugin_assets/gumloop/skills/gumloop-cli/SKILL.md +0 -0
- {gumloop-0.5.0 → gumloop-0.5.2}/src/gumloop/errors.py +0 -0
- {gumloop-0.5.0 → gumloop-0.5.2}/src/gumloop/oauth.py +0 -0
- {gumloop-0.5.0 → gumloop-0.5.2}/src/gumloop/py.typed +0 -0
- {gumloop-0.5.0 → gumloop-0.5.2}/src/gumloop/resources/__init__.py +0 -0
- {gumloop-0.5.0 → gumloop-0.5.2}/src/gumloop/resources/agents.py +0 -0
- {gumloop-0.5.0 → gumloop-0.5.2}/src/gumloop/resources/artifacts.py +0 -0
- {gumloop-0.5.0 → gumloop-0.5.2}/src/gumloop/resources/browser_profiles.py +0 -0
- {gumloop-0.5.0 → gumloop-0.5.2}/src/gumloop/resources/chat.py +0 -0
- {gumloop-0.5.0 → gumloop-0.5.2}/src/gumloop/resources/evaluations.py +0 -0
- {gumloop-0.5.0 → gumloop-0.5.2}/src/gumloop/resources/mcp.py +0 -0
- {gumloop-0.5.0 → gumloop-0.5.2}/src/gumloop/resources/organizations.py +0 -0
- {gumloop-0.5.0 → gumloop-0.5.2}/src/gumloop/resources/sessions.py +0 -0
- {gumloop-0.5.0 → gumloop-0.5.2}/src/gumloop/resources/sync.py +0 -0
- {gumloop-0.5.0 → gumloop-0.5.2}/src/gumloop/resources/teams.py +0 -0
- {gumloop-0.5.0 → gumloop-0.5.2}/src/gumloop/spec/__init__.py +0 -0
- {gumloop-0.5.0 → gumloop-0.5.2}/src/gumloop/spec/_compat.py +0 -0
- {gumloop-0.5.0 → gumloop-0.5.2}/src/gumloop/spec/_extensions.py +0 -0
- {gumloop-0.5.0 → gumloop-0.5.2}/src/gumloop/sync/__init__.py +0 -0
- {gumloop-0.5.0 → gumloop-0.5.2}/src/gumloop/sync/archive.py +0 -0
- {gumloop-0.5.0 → gumloop-0.5.2}/src/gumloop/sync/bundle.py +0 -0
- {gumloop-0.5.0 → gumloop-0.5.2}/src/gumloop/sync/bundle_validation.py +0 -0
- {gumloop-0.5.0 → gumloop-0.5.2}/src/gumloop/sync/errors.py +0 -0
- {gumloop-0.5.0 → gumloop-0.5.2}/src/gumloop/sync/hashing.py +0 -0
- {gumloop-0.5.0 → gumloop-0.5.2}/src/gumloop/sync/installed.py +0 -0
- {gumloop-0.5.0 → gumloop-0.5.2}/src/gumloop/sync/local_state.py +0 -0
- {gumloop-0.5.0 → gumloop-0.5.2}/src/gumloop/sync/lock.py +0 -0
- {gumloop-0.5.0 → gumloop-0.5.2}/src/gumloop/sync/markers.py +0 -0
- {gumloop-0.5.0 → gumloop-0.5.2}/src/gumloop/sync/reconcile.py +0 -0
- {gumloop-0.5.0 → gumloop-0.5.2}/src/gumloop/sync/reporting.py +0 -0
- {gumloop-0.5.0 → gumloop-0.5.2}/src/gumloop/sync/results.py +0 -0
- {gumloop-0.5.0 → gumloop-0.5.2}/src/gumloop/sync/run.py +0 -0
- {gumloop-0.5.0 → gumloop-0.5.2}/src/gumloop/sync/scheduler.py +0 -0
- {gumloop-0.5.0 → gumloop-0.5.2}/src/gumloop/sync/target_files.py +0 -0
- {gumloop-0.5.0 → gumloop-0.5.2}/src/gumloop/sync/targets.py +0 -0
- {gumloop-0.5.0 → gumloop-0.5.2}/src/gumloop/sync/wire.py +0 -0
- {gumloop-0.5.0 → gumloop-0.5.2}/tests/__init__.py +0 -0
- {gumloop-0.5.0 → gumloop-0.5.2}/tests/cli/__init__.py +0 -0
- {gumloop-0.5.0 → gumloop-0.5.2}/tests/cli/conftest.py +0 -0
- {gumloop-0.5.0 → gumloop-0.5.2}/tests/cli/sync_scenario_helpers.py +0 -0
- {gumloop-0.5.0 → gumloop-0.5.2}/tests/cli/sync_test_fakes.py +0 -0
- {gumloop-0.5.0 → gumloop-0.5.2}/tests/cli/test_agents.py +0 -0
- {gumloop-0.5.0 → gumloop-0.5.2}/tests/cli/test_args_input.py +0 -0
- {gumloop-0.5.0 → gumloop-0.5.2}/tests/cli/test_artifacts.py +0 -0
- {gumloop-0.5.0 → gumloop-0.5.2}/tests/cli/test_brain.py +0 -0
- {gumloop-0.5.0 → gumloop-0.5.2}/tests/cli/test_browser_logins.py +0 -0
- {gumloop-0.5.0 → gumloop-0.5.2}/tests/cli/test_chat.py +0 -0
- {gumloop-0.5.0 → gumloop-0.5.2}/tests/cli/test_context.py +0 -0
- {gumloop-0.5.0 → gumloop-0.5.2}/tests/cli/test_credentials.py +0 -0
- {gumloop-0.5.0 → gumloop-0.5.2}/tests/cli/test_evaluations.py +0 -0
- {gumloop-0.5.0 → gumloop-0.5.2}/tests/cli/test_home_isolation.py +0 -0
- {gumloop-0.5.0 → gumloop-0.5.2}/tests/cli/test_login.py +0 -0
- {gumloop-0.5.0 → gumloop-0.5.2}/tests/cli/test_main.py +0 -0
- {gumloop-0.5.0 → gumloop-0.5.2}/tests/cli/test_mcp.py +0 -0
- {gumloop-0.5.0 → gumloop-0.5.2}/tests/cli/test_models.py +0 -0
- {gumloop-0.5.0 → gumloop-0.5.2}/tests/cli/test_plugin.py +0 -0
- {gumloop-0.5.0 → gumloop-0.5.2}/tests/cli/test_skills.py +0 -0
- {gumloop-0.5.0 → gumloop-0.5.2}/tests/cli/test_sync_bundle.py +0 -0
- {gumloop-0.5.0 → gumloop-0.5.2}/tests/cli/test_sync_command.py +0 -0
- {gumloop-0.5.0 → gumloop-0.5.2}/tests/cli/test_sync_command_safety.py +0 -0
- {gumloop-0.5.0 → gumloop-0.5.2}/tests/cli/test_sync_contract_fixtures.py +0 -0
- {gumloop-0.5.0 → gumloop-0.5.2}/tests/cli/test_sync_enrollment.py +0 -0
- {gumloop-0.5.0 → gumloop-0.5.2}/tests/cli/test_sync_fixture_corpus.py +0 -0
- {gumloop-0.5.0 → gumloop-0.5.2}/tests/cli/test_sync_hashing.py +0 -0
- {gumloop-0.5.0 → gumloop-0.5.2}/tests/cli/test_sync_installed.py +0 -0
- {gumloop-0.5.0 → gumloop-0.5.2}/tests/cli/test_sync_reconciliation_safety.py +0 -0
- {gumloop-0.5.0 → gumloop-0.5.2}/tests/cli/test_sync_scheduler_macos.py +0 -0
- {gumloop-0.5.0 → gumloop-0.5.2}/tests/cli/test_sync_targets.py +0 -0
- {gumloop-0.5.0 → gumloop-0.5.2}/tests/conftest.py +0 -0
- {gumloop-0.5.0 → gumloop-0.5.2}/tests/fixtures/skill_sync/v1/README.md +0 -0
- {gumloop-0.5.0 → gumloop-0.5.2}/tests/fixtures/skill_sync/v1/archives/generated-bundle-transfer-limit.json +0 -0
- {gumloop-0.5.0 → gumloop-0.5.2}/tests/fixtures/skill_sync/v1/archives/invalid-absolute-path.zip +0 -0
- {gumloop-0.5.0 → gumloop-0.5.2}/tests/fixtures/skill_sync/v1/archives/invalid-bytes-per-file-limit.zip +0 -0
- {gumloop-0.5.0 → gumloop-0.5.2}/tests/fixtures/skill_sync/v1/archives/invalid-device.zip +0 -0
- {gumloop-0.5.0 → gumloop-0.5.2}/tests/fixtures/skill_sync/v1/archives/invalid-duplicate-entry.zip +0 -0
- {gumloop-0.5.0 → gumloop-0.5.2}/tests/fixtures/skill_sync/v1/archives/invalid-fifo.zip +0 -0
- {gumloop-0.5.0 → gumloop-0.5.2}/tests/fixtures/skill_sync/v1/archives/invalid-files-per-skill-limit.zip +0 -0
- {gumloop-0.5.0 → gumloop-0.5.2}/tests/fixtures/skill_sync/v1/archives/invalid-nul-name.zip +0 -0
- {gumloop-0.5.0 → gumloop-0.5.2}/tests/fixtures/skill_sync/v1/archives/invalid-parent-traversal.zip +0 -0
- {gumloop-0.5.0 → gumloop-0.5.2}/tests/fixtures/skill_sync/v1/archives/invalid-reserved-marker.zip +0 -0
- {gumloop-0.5.0 → gumloop-0.5.2}/tests/fixtures/skill_sync/v1/archives/invalid-socket.zip +0 -0
- {gumloop-0.5.0 → gumloop-0.5.2}/tests/fixtures/skill_sync/v1/archives/invalid-symlink.zip +0 -0
- {gumloop-0.5.0 → gumloop-0.5.2}/tests/fixtures/skill_sync/v1/archives/invalid-total-uncompressed-limit.zip +0 -0
- {gumloop-0.5.0 → gumloop-0.5.2}/tests/fixtures/skill_sync/v1/archives/invalid-windows-drive-prefix.zip +0 -0
- {gumloop-0.5.0 → gumloop-0.5.2}/tests/fixtures/skill_sync/v1/archives/valid-single-skill-stored.zip +0 -0
- {gumloop-0.5.0 → gumloop-0.5.2}/tests/fixtures/skill_sync/v1/archives/valid-single-skill.zip +0 -0
- {gumloop-0.5.0 → gumloop-0.5.2}/tests/fixtures/skill_sync/v1/bundles/newer-than-plan.zip +0 -0
- {gumloop-0.5.0 → gumloop-0.5.2}/tests/fixtures/skill_sync/v1/bundles/normal.zip +0 -0
- {gumloop-0.5.0 → gumloop-0.5.2}/tests/fixtures/skill_sync/v1/catalog.json +0 -0
- {gumloop-0.5.0 → gumloop-0.5.2}/tests/fixtures/skill_sync/v1/corpus.sha256 +0 -0
- {gumloop-0.5.0 → gumloop-0.5.2}/tests/fixtures/skill_sync/v1/hashes/content-v1.json +0 -0
- {gumloop-0.5.0 → gumloop-0.5.2}/tests/fixtures/skill_sync/v1/hashes/manifest-v1.json +0 -0
- {gumloop-0.5.0 → gumloop-0.5.2}/tests/fixtures/skill_sync/v1/manifests/invalid-duplicate-identity.json +0 -0
- {gumloop-0.5.0 → gumloop-0.5.2}/tests/fixtures/skill_sync/v1/manifests/invalid-duplicate-install-name.json +0 -0
- {gumloop-0.5.0 → gumloop-0.5.2}/tests/fixtures/skill_sync/v1/manifests/invalid-manifest-hash.json +0 -0
- {gumloop-0.5.0 → gumloop-0.5.2}/tests/fixtures/skill_sync/v1/manifests/newer-than-plan.json +0 -0
- {gumloop-0.5.0 → gumloop-0.5.2}/tests/fixtures/skill_sync/v1/manifests/normal-bundle.json +0 -0
- {gumloop-0.5.0 → gumloop-0.5.2}/tests/fixtures/skill_sync/v1/responses/below-pro.json +0 -0
- {gumloop-0.5.0 → gumloop-0.5.2}/tests/fixtures/skill_sync/v1/responses/cli-upgrade-required.json +0 -0
- {gumloop-0.5.0 → gumloop-0.5.2}/tests/fixtures/skill_sync/v1/responses/lost-membership.json +0 -0
- {gumloop-0.5.0 → gumloop-0.5.2}/tests/fixtures/skill_sync/v1/responses/normal-empty-plan.json +0 -0
- {gumloop-0.5.0 → gumloop-0.5.2}/tests/fixtures/skill_sync/v1/responses/normal-plan.json +0 -0
- {gumloop-0.5.0 → gumloop-0.5.2}/tests/integration/conftest.py +0 -0
- {gumloop-0.5.0 → gumloop-0.5.2}/tests/integration/test_live.py +0 -0
- {gumloop-0.5.0 → gumloop-0.5.2}/tests/sdk/__init__.py +0 -0
- {gumloop-0.5.0 → gumloop-0.5.2}/tests/sdk/conftest.py +0 -0
- {gumloop-0.5.0 → gumloop-0.5.2}/tests/sdk/helpers.py +0 -0
- {gumloop-0.5.0 → gumloop-0.5.2}/tests/sdk/test_agents.py +0 -0
- {gumloop-0.5.0 → gumloop-0.5.2}/tests/sdk/test_artifacts.py +0 -0
- {gumloop-0.5.0 → gumloop-0.5.2}/tests/sdk/test_chat.py +0 -0
- {gumloop-0.5.0 → gumloop-0.5.2}/tests/sdk/test_client.py +0 -0
- {gumloop-0.5.0 → gumloop-0.5.2}/tests/sdk/test_errors.py +0 -0
- {gumloop-0.5.0 → gumloop-0.5.2}/tests/sdk/test_evaluations.py +0 -0
- {gumloop-0.5.0 → gumloop-0.5.2}/tests/sdk/test_gumcp_transport.py +0 -0
- {gumloop-0.5.0 → gumloop-0.5.2}/tests/sdk/test_mcp.py +0 -0
- {gumloop-0.5.0 → gumloop-0.5.2}/tests/sdk/test_models.py +0 -0
- {gumloop-0.5.0 → gumloop-0.5.2}/tests/sdk/test_oauth.py +0 -0
- {gumloop-0.5.0 → gumloop-0.5.2}/tests/sdk/test_organizations.py +0 -0
- {gumloop-0.5.0 → gumloop-0.5.2}/tests/sdk/test_sessions.py +0 -0
- {gumloop-0.5.0 → gumloop-0.5.2}/tests/sdk/test_skills.py +0 -0
- {gumloop-0.5.0 → gumloop-0.5.2}/tests/sdk/test_sync.py +0 -0
- {gumloop-0.5.0 → gumloop-0.5.2}/tests/sdk/test_teams.py +0 -0
- {gumloop-0.5.0 → gumloop-0.5.2}/tests/skill_sync_fixtures.py +0 -0
- {gumloop-0.5.0 → gumloop-0.5.2}/tests/test_client.py +0 -0
- {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.
|
|
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
|
|
@@ -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)
|