@inneranimalmedia/agentsam-sdk 1.7.0 → 1.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/DEVELOPMENT.md +25 -5
- package/README.md +2 -0
- package/docs/RELEASES.iam-mirror.md +20 -0
- package/docs/RELEASES.md +9 -0
- package/package.json +9 -4
- package/protocol/README.md +51 -0
- package/protocol/dual-repo-sync.md +35 -0
- package/python/README.md +12 -0
- package/python/agentsam_sdk/__init__.py +9 -0
- package/python/agentsam_sdk/cli.py +262 -0
- package/python/agentsam_sdk/data/__init__.py +0 -0
- package/python/agentsam_sdk/data/agentsam_walk.py +157 -0
- package/python/agentsam_sdk/data/d1_adapter.py +124 -0
- package/python/agentsam_sdk/data/d1_bloat.py +445 -0
- package/python/agentsam_sdk/repository/__init__.py +26 -0
- package/python/agentsam_sdk/repository/__main__.py +3 -0
- package/python/agentsam_sdk/repository/inspect.py +496 -0
- package/python/agentsam_sdk/repository/inventory.py +351 -0
- package/python/agentsam_sdk/repository/scan_bloat.py +173 -0
- package/python/agentsam_sdk/runtime/__init__.py +0 -0
- package/python/agentsam_sdk/runtime/contract.py +105 -0
- package/python/docs/gaps.md +63 -0
- package/python/docs/tooling.md +67 -0
- package/python/protocol/README.md +51 -0
- package/python/protocol/dual-repo-sync.md +35 -0
- package/python/pyproject.toml +16 -0
- package/python/scripts/check-host-tooling.sh +65 -0
- package/python/tests/__init__.py +0 -0
- package/python/tests/fixtures/sample_tables.json +17 -0
- package/python/tests/fixtures.py +95 -0
- package/python/tests/test_agentsam_walk.py +31 -0
- package/python/tests/test_contract.py +32 -0
- package/python/tests/test_d1_bloat.py +93 -0
- package/python/tests/test_repository_inspect.py +84 -0
- package/python/tests/test_repository_inventory.py +53 -0
- package/python/tests/test_scan_bloat.py +31 -0
package/DEVELOPMENT.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
#
|
|
1
|
+
# DEVELOPING AGENTSAM-SDK WITH INNER ANIMAL MEDIA
|
|
2
2
|
|
|
3
3
|
## Local smoke test
|
|
4
4
|
|
|
@@ -43,10 +43,30 @@ npx agentsam init \
|
|
|
43
43
|
|
|
44
44
|
## Publish checklist
|
|
45
45
|
|
|
46
|
-
1.
|
|
47
|
-
2.
|
|
48
|
-
3.
|
|
49
|
-
4.
|
|
46
|
+
1. Root `package.json` name is **`@inneranimalmedia/agentsam-sdk`** (never a workspace kit)
|
|
47
|
+
2. `npm test` passes
|
|
48
|
+
3. Bump version in root `package.json`
|
|
49
|
+
4. Confirm `files` includes consumer surfaces (`src`, `bin`, `python`, `protocol`, …)
|
|
50
|
+
5. `npm publish --access public` (manual; requires `npm login` to org)
|
|
51
|
+
6. Tag: `git tag vX.Y.Z && git push origin vX.Y.Z`
|
|
52
|
+
7. Record pairing in [`docs/RELEASES.md`](./docs/RELEASES.md): `iam@<40-sha> ↔ @inneranimalmedia/agentsam-sdk@X.Y.Z`
|
|
53
|
+
|
|
54
|
+
Workspace packages under `packages/*` (e.g. `@inneranimalmedia/agentsam-shell-kit`) stay
|
|
55
|
+
`private: true` until separately ready — they are **not** the root publish identity.
|
|
56
|
+
|
|
57
|
+
## Python tooling (`agentsam_sdk`)
|
|
58
|
+
|
|
59
|
+
Portable stdlib audits live under [`python/`](./python/) (import path `agentsam_sdk.*`).
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
cd python
|
|
63
|
+
python3 -m pip install -e ".[dev]"
|
|
64
|
+
python3 -m pytest
|
|
65
|
+
python3 -m agentsam_sdk.repository.inventory --json --root /path/to/repo
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
Inner Animal Media keeps a thin shim at `scripts/repo-size-inventory.py` that adds
|
|
69
|
+
`AGENTSAM_SDK_ROOT/python` (or a sibling/`~/agentsam-sdk` checkout) to `sys.path`.
|
|
50
70
|
|
|
51
71
|
## Known gaps (roadmap)
|
|
52
72
|
|
package/README.md
CHANGED
|
@@ -6,6 +6,8 @@ Agent Sam is a full-stack autonomous agent SDK built on Cloudflare Workers, D1,
|
|
|
6
6
|
|
|
7
7
|
**Repo:** [github.com/SamPrimeaux/agentsam-sdk](https://github.com/SamPrimeaux/agentsam-sdk) · **npm:** `@inneranimalmedia/agentsam-sdk`
|
|
8
8
|
|
|
9
|
+
**Protocol (dual-home, no drift):** [`protocol/README.md`](./protocol/README.md) — every tool/feature lands in this repo **and** `inneranimalmedia/agentsam-sdk/`; npm publish is manual after mirror. Python lane: [`python/`](./python/).
|
|
10
|
+
|
|
9
11
|
---
|
|
10
12
|
|
|
11
13
|
## What is Agent Sam?
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# Agent Sam SDK — release pairings (IAM home)
|
|
2
|
+
|
|
3
|
+
**Status:** Evergreen receipts · **Product:** [README.md](./README.md)
|
|
4
|
+
|
|
5
|
+
Canonical npm-side table also lives in the SDK repo: `docs/RELEASES.md`.
|
|
6
|
+
**Keep both updated** when publishing (dual-home of the receipt).
|
|
7
|
+
|
|
8
|
+
| npm version | Published (UTC) | IAM git SHA (40) | Notes |
|
|
9
|
+
|-------------|-----------------|------------------|-------|
|
|
10
|
+
| 1.7.0 | 2026-07-14 | _(pre-receipt)_ | Prior publish |
|
|
11
|
+
| 1.9.0 | _(pending publish)_ | `fc1628505cb3c9946c149e4c468c2e264d6f381e` | `repository.inspect` + `--dupes`; mirrored from IAM agentsam-sdk |
|
|
12
|
+
| 1.8.0 | 2026-08-02T13:57:12.080Z | `580547301e370b77ec26bd72558979d335feedd9` | `python/` + `protocol/` in tarball; `repository.scan_bloat`; shell-kit under `packages/` |
|
|
13
|
+
|
|
14
|
+
**Latest pairing (ready to publish):** `iam@fc1628505cb3c9946c149e4c468c2e264d6f381e` ↔ `@inneranimalmedia/agentsam-sdk@1.9.0`
|
|
15
|
+
|
|
16
|
+
### Receipt rules
|
|
17
|
+
|
|
18
|
+
- Full 40-char SHA only (AGENTS.md §3)
|
|
19
|
+
- Record the IAM SHA that introduced or mirrored the consumer-facing change
|
|
20
|
+
- Tag SDK repo `vX.Y.Z` on publish
|
package/docs/RELEASES.md
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
# `@inneranimalmedia/agentsam-sdk` release receipts
|
|
2
|
+
|
|
3
|
+
| npm version | Published (UTC) | IAM git SHA (40) | Notes |
|
|
4
|
+
|-------------|-----------------|------------------|-------|
|
|
5
|
+
| 1.7.0 | 2026-07-14 | _(pre-receipt)_ | Prior publish |
|
|
6
|
+
| 1.9.0 | _(pending publish)_ | `fc1628505cb3c9946c149e4c468c2e264d6f381e` | `python/agentsam_sdk/repository/inspect.py` (`--dupes`); CLI `agentsam repository inspect` |
|
|
7
|
+
| 1.8.0 | 2026-08-02T13:57:12.080Z | `580547301e370b77ec26bd72558979d335feedd9` | `python/` + `protocol/` in npm `files`; `repository.scan_bloat`; shell-kit folded to `packages/agentsam-shell-kit` (private). SDK git at publish: `ac52669b89ddb16fad87c42fe53f2c93ef13bd1a`. |
|
|
8
|
+
|
|
9
|
+
**Pairing (ready to publish):** `iam@fc1628505cb3c9946c149e4c468c2e264d6f381e` ↔ `@inneranimalmedia/agentsam-sdk@1.9.0`
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@inneranimalmedia/agentsam-sdk",
|
|
3
|
-
"version": "1.
|
|
4
|
-
"description": "Agent Sam is a full-stack AI agent SDK for autonomous task execution
|
|
3
|
+
"version": "1.9.0",
|
|
4
|
+
"description": "Agent Sam is a full-stack AI agent SDK for autonomous task execution \u2014 covering data management, creative workflows, design commands, and multi-step agentic pipelines.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./src/index.js",
|
|
7
7
|
"exports": {
|
|
@@ -18,10 +18,15 @@
|
|
|
18
18
|
"docs",
|
|
19
19
|
"examples",
|
|
20
20
|
"test",
|
|
21
|
+
"python",
|
|
22
|
+
"protocol",
|
|
23
|
+
"bin",
|
|
21
24
|
"README.md",
|
|
22
25
|
"LICENSE",
|
|
23
|
-
"DEVELOPMENT.md"
|
|
24
|
-
|
|
26
|
+
"DEVELOPMENT.md"
|
|
27
|
+
],
|
|
28
|
+
"workspaces": [
|
|
29
|
+
"packages/*"
|
|
25
30
|
],
|
|
26
31
|
"scripts": {
|
|
27
32
|
"test": "node test/smoke.mjs",
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# agentsam-sdk protocol (LOCKED)
|
|
2
|
+
|
|
3
|
+
Every Agent Sam **tool / feature SDK surface** is dual-homed. There is no
|
|
4
|
+
“Python audits only in IAM” vs “JS CLI only on npm” split for productized
|
|
5
|
+
tooling.
|
|
6
|
+
|
|
7
|
+
| Home | Path | Role |
|
|
8
|
+
|------|------|------|
|
|
9
|
+
| **Main platform repo** | `inneranimalmedia/agentsam-sdk/` | Source of truth while building; ships with platform deploy |
|
|
10
|
+
| **Published SDK repo** | `github.com/SamPrimeaux/agentsam-sdk` → npm `@inneranimalmedia/agentsam-sdk` | Same contract + modules for external / CLI consumers |
|
|
11
|
+
|
|
12
|
+
## Rules
|
|
13
|
+
|
|
14
|
+
1. **Author once, land twice.** New `agentsam_sdk.*` tools (data, repository,
|
|
15
|
+
readiness, history, code-intel, …) land in the monorepo package **and** are
|
|
16
|
+
mirrored into the published `agentsam-sdk` repo in the same change set / PR
|
|
17
|
+
pair. No silent one-sided land.
|
|
18
|
+
2. **Contract is shared.** `runtime/contract` (`ToolInput` / `ToolResult` /
|
|
19
|
+
receipts, unixepoch, no hardcoded identity) is the protocol. Language may
|
|
20
|
+
differ (Python stdlib vs JS), but CLI verbs and JSON shapes must stay
|
|
21
|
+
aligned — see `protocol/dual-repo-sync.md`.
|
|
22
|
+
3. **No drift.** Publishing npm without updating the monorepo copy (or the
|
|
23
|
+
reverse) is a protocol violation. Gate: version bump + changelog note that
|
|
24
|
+
lists mirrored paths.
|
|
25
|
+
4. **Ship is two-sided.**
|
|
26
|
+
- Platform: Mac `npm run deploy:full` / `deploy:fast` (or GCP `ship:remote`)
|
|
27
|
+
when the monorepo copy or Worker wiring changed.
|
|
28
|
+
- SDK: **manual** npm publish / version bump on `agentsam-sdk` after the
|
|
29
|
+
mirror lands (operator-owned — do not assume CI auto-publishes).
|
|
30
|
+
5. **Inspiration ≠ copy-paste secrets.** In-app surfaces (e.g. `src/core/code-indexer.js`,
|
|
31
|
+
AST-RAG Phase 1/2) are the reference implementations to port into portable
|
|
32
|
+
SDK modules — strip platform-only bindings, keep adapters for D1 / git /
|
|
33
|
+
Hyperdrive.
|
|
34
|
+
|
|
35
|
+
## Layout (both homes)
|
|
36
|
+
|
|
37
|
+
```
|
|
38
|
+
agentsam-sdk/
|
|
39
|
+
protocol/ ← this law
|
|
40
|
+
python/agentsam_sdk ← Python portable tools (stdlib-first audits / inventory)
|
|
41
|
+
src/ ← JS CLI / scaffold (published npm entry)
|
|
42
|
+
packages/ ← optional workspace packages (not always in npm tarball)
|
|
43
|
+
agentsam-shell-kit/ ← @inneranimalmedia/agentsam-shell-kit (private until ready)
|
|
44
|
+
docs/gaps.md ← port status vs IAM scripts + in-app indexers
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
**npm identity (LOCKED):** root publishable package is always
|
|
48
|
+
`@inneranimalmedia/agentsam-sdk`. Do not overwrite root `package.json` with a
|
|
49
|
+
workspace kit name. Fold UI kits under `packages/*`.
|
|
50
|
+
|
|
51
|
+
Exact folder names may evolve; the dual-home + root-identity rules do not.
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# Dual-repo sync checklist
|
|
2
|
+
|
|
3
|
+
Use this every time you add or change an `agentsam_sdk` tool/feature.
|
|
4
|
+
|
|
5
|
+
## Before coding
|
|
6
|
+
|
|
7
|
+
- [ ] Name the module (`agentsam_sdk.<domain>.<tool>`) and CLI verb
|
|
8
|
+
- [ ] Confirm it belongs in SDK (portable) vs platform-only Worker hot path
|
|
9
|
+
- [ ] Note in-app inspiration path (if any), e.g. `src/core/code-indexer.js`
|
|
10
|
+
|
|
11
|
+
## Land
|
|
12
|
+
|
|
13
|
+
- [ ] Implement + tests in **`inneranimalmedia/agentsam-sdk/`**
|
|
14
|
+
- [ ] Mirror the same module/CLI/docs into **`agentsam-sdk`** (npm repo)
|
|
15
|
+
- [ ] Update `docs/gaps.md` (or equivalent) in **both** trees
|
|
16
|
+
- [ ] Update `protocol/` only in monorepo if law changed; copy README blurb to npm
|
|
17
|
+
|
|
18
|
+
## Publish / deploy (operator)
|
|
19
|
+
|
|
20
|
+
- [ ] Platform: commit/push IAM → deploy by host (`deploy:full` / `ship:remote`)
|
|
21
|
+
- [ ] SDK: bump `package.json` version in npm repo → `npm publish` (manual)
|
|
22
|
+
- [ ] Record versions next to each other (PR description or receipt):
|
|
23
|
+
`iam@<sha>` ↔ `@inneranimalmedia/agentsam-sdk@<semver>`
|
|
24
|
+
|
|
25
|
+
## Drift signals (fail the PR)
|
|
26
|
+
|
|
27
|
+
- Module exists only in one repo
|
|
28
|
+
- CLI flag / JSON field renamed on one side only
|
|
29
|
+
- README claims “this is not the npm package” / “audits don’t belong here”
|
|
30
|
+
- npm publish without a same-day IAM mirror commit (or documented lag ticket)
|
|
31
|
+
|
|
32
|
+
## jq / host tools
|
|
33
|
+
|
|
34
|
+
Host tooling (`jq`, `wrangler`, Python ≥3.10) is documented in
|
|
35
|
+
`docs/tooling.md`. Keep that file mirrored when recipes change.
|
package/python/README.md
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# agentsam_sdk (Python) — npm mirror
|
|
2
|
+
|
|
3
|
+
Exact dual-home of `inneranimalmedia/agentsam-sdk/`.
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
cd python && pip install -e .
|
|
7
|
+
agentsam repository inventory --repo-root /path/to/repo --format json
|
|
8
|
+
python3 -m unittest discover -s tests -v
|
|
9
|
+
./scripts/check-host-tooling.sh
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
Protocol: [`../protocol/README.md`](../protocol/README.md). Do not advance this tree without mirroring the monorepo (or the reverse).
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
"""agentsam_sdk — stdlib-only toolkit for inneranimalmedia D1/repo audits.
|
|
2
|
+
|
|
3
|
+
Every tool follows the runtime.contract pattern: ToolInput in, ToolResult out,
|
|
4
|
+
plus a receipt written to output_dir for audit trail. No hardcoded
|
|
5
|
+
identity/repo/workspace/tenant values anywhere in this package — see
|
|
6
|
+
runtime.contract.HARD_LAW_NOTE.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
__version__ = "0.1.0"
|
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
"""agentsam CLI -- stdlib argparse only (no click/typer dep, per project
|
|
2
|
+
convention: Python tooling here is stdlib-only).
|
|
3
|
+
|
|
4
|
+
agentsam data d1-bloat --quick --format markdown --output-dir /tmp/d1-bloat
|
|
5
|
+
agentsam data agentsam-walk --prefix agentsam_ --output-dir /tmp/walk
|
|
6
|
+
agentsam repository inventory --repo-root .. --output-dir /tmp/scan --format json
|
|
7
|
+
agentsam repository inventory --repo-root .. --format json | jq '.data.totals'
|
|
8
|
+
agentsam repository scan-bloat --root src --min-kb 10 --top 30 --format json
|
|
9
|
+
agentsam repository inspect --repo-root . --json --dupes | jq '.duplicates'
|
|
10
|
+
"""
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
import argparse
|
|
14
|
+
import json
|
|
15
|
+
import sys
|
|
16
|
+
|
|
17
|
+
from agentsam_sdk.runtime.contract import ToolInput, ToolResult
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def _print_result(result: ToolResult, fmt: str) -> None:
|
|
21
|
+
if fmt == "json":
|
|
22
|
+
print(json.dumps(result.to_dict(), indent=2, default=str))
|
|
23
|
+
else:
|
|
24
|
+
status = "OK" if result.ok else "FAIL"
|
|
25
|
+
print(f"[{status}] {result.tool} ({result.mode}) — {result.summary}", file=sys.stderr)
|
|
26
|
+
if result.error:
|
|
27
|
+
print(f" error: {result.error}", file=sys.stderr)
|
|
28
|
+
for a in result.artifacts:
|
|
29
|
+
print(f" wrote: {a}", file=sys.stderr)
|
|
30
|
+
# When markdown artifacts were written, surface path; else dump summary data keys
|
|
31
|
+
if fmt == "markdown" and result.ok and result.data.get("categories"):
|
|
32
|
+
# stdout stays quiet when artifacts exist; callers use the .md file
|
|
33
|
+
if not result.artifacts:
|
|
34
|
+
from agentsam_sdk.repository.inventory import _markdown
|
|
35
|
+
|
|
36
|
+
sys.stdout.write(_markdown(result.data))
|
|
37
|
+
if fmt == "markdown" and result.ok and result.data.get("files") is not None:
|
|
38
|
+
from agentsam_sdk.repository.scan_bloat import human_table
|
|
39
|
+
|
|
40
|
+
sys.stdout.write(
|
|
41
|
+
human_table(
|
|
42
|
+
result.data.get("files") or [],
|
|
43
|
+
scanned=int(result.data.get("file_count") or 0),
|
|
44
|
+
total_kb=float(result.data.get("total_kb") or 0),
|
|
45
|
+
total_tokens=int(result.data.get("total_est_tokens") or 0),
|
|
46
|
+
)
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def _cmd_data_d1_bloat(args: argparse.Namespace) -> int:
|
|
51
|
+
from agentsam_sdk.data import d1_bloat
|
|
52
|
+
|
|
53
|
+
mode = "full" if args.full else "quick"
|
|
54
|
+
ti = ToolInput(
|
|
55
|
+
mode=mode,
|
|
56
|
+
params={
|
|
57
|
+
"db": args.db, "config": args.config, "repo_root": args.repo_root,
|
|
58
|
+
"prefix": args.prefix, "workers": args.workers, "top": args.top,
|
|
59
|
+
},
|
|
60
|
+
output_dir=args.output_dir,
|
|
61
|
+
)
|
|
62
|
+
result = d1_bloat.run(ti)
|
|
63
|
+
_print_result(result, args.format)
|
|
64
|
+
return 0 if result.ok else 1
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def _cmd_data_agentsam_walk(args: argparse.Namespace) -> int:
|
|
68
|
+
from agentsam_sdk.data import agentsam_walk
|
|
69
|
+
|
|
70
|
+
ti = ToolInput(
|
|
71
|
+
mode="default",
|
|
72
|
+
params={"db": args.db, "config": args.config, "repo_root": args.repo_root, "prefix": args.prefix},
|
|
73
|
+
output_dir=args.output_dir,
|
|
74
|
+
)
|
|
75
|
+
result = agentsam_walk.run(ti)
|
|
76
|
+
_print_result(result, args.format)
|
|
77
|
+
return 0 if result.ok else 1
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def _cmd_repository_inventory(args: argparse.Namespace) -> int:
|
|
81
|
+
from agentsam_sdk.repository import inventory
|
|
82
|
+
|
|
83
|
+
ti = ToolInput(
|
|
84
|
+
mode="read-only",
|
|
85
|
+
params={
|
|
86
|
+
"repo_root": args.repo_root,
|
|
87
|
+
"top": args.top,
|
|
88
|
+
"min_bytes": args.min_bytes,
|
|
89
|
+
"by_ext": args.by_ext,
|
|
90
|
+
"include_node_modules": args.include_node_modules,
|
|
91
|
+
"include_venvs": args.include_venvs,
|
|
92
|
+
"include_git": args.include_git,
|
|
93
|
+
"include_dist": args.include_dist,
|
|
94
|
+
"follow_symlinks": args.follow_symlinks,
|
|
95
|
+
},
|
|
96
|
+
output_dir=args.output_dir,
|
|
97
|
+
)
|
|
98
|
+
result = inventory.run(ti)
|
|
99
|
+
_print_result(result, args.format)
|
|
100
|
+
return 0 if result.ok else 1
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def _cmd_repository_scan_bloat(args: argparse.Namespace) -> int:
|
|
104
|
+
from agentsam_sdk.repository import scan_bloat
|
|
105
|
+
|
|
106
|
+
ti = ToolInput(
|
|
107
|
+
mode="read-only",
|
|
108
|
+
params={
|
|
109
|
+
"root": args.root,
|
|
110
|
+
"top": args.top,
|
|
111
|
+
"min_kb": args.min_kb,
|
|
112
|
+
"ext": args.ext,
|
|
113
|
+
"exclude": args.exclude,
|
|
114
|
+
},
|
|
115
|
+
output_dir=args.output_dir,
|
|
116
|
+
)
|
|
117
|
+
result = scan_bloat.run(ti)
|
|
118
|
+
# Agent capture: --json-envelope prints data payload only (legacy tools/scan_bloat.py)
|
|
119
|
+
if args.json_envelope:
|
|
120
|
+
print(json.dumps(result.data if result.ok else {"ok": False, "error": result.error}, indent=2))
|
|
121
|
+
return 0 if result.ok else 1
|
|
122
|
+
_print_result(result, args.format)
|
|
123
|
+
return 0 if result.ok else 1
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def _cmd_repository_inspect(args: argparse.Namespace) -> int:
|
|
127
|
+
from agentsam_sdk.repository import inspect as repo_inspect
|
|
128
|
+
|
|
129
|
+
argv: list[str] = []
|
|
130
|
+
if args.repo_root:
|
|
131
|
+
argv.extend(["--repo-root", args.repo_root])
|
|
132
|
+
want_text = bool(args.text) or args.format in ("text", "markdown")
|
|
133
|
+
want_json = bool(args.json) or args.format == "json" or not want_text
|
|
134
|
+
if want_json and not want_text:
|
|
135
|
+
argv.append("--json")
|
|
136
|
+
if want_text and not want_json:
|
|
137
|
+
argv.append("--text")
|
|
138
|
+
if want_text and want_json:
|
|
139
|
+
# Explicit both → JSON wins (machine default) unless only --text
|
|
140
|
+
argv.append("--json")
|
|
141
|
+
if args.dupes:
|
|
142
|
+
argv.append("--dupes")
|
|
143
|
+
if args.all:
|
|
144
|
+
argv.append("--all")
|
|
145
|
+
if args.since:
|
|
146
|
+
argv.extend(["--since", args.since])
|
|
147
|
+
if args.recent is not None:
|
|
148
|
+
argv.extend(["--recent", str(args.recent)])
|
|
149
|
+
if args.largest is not None:
|
|
150
|
+
argv.extend(["--largest", str(args.largest)])
|
|
151
|
+
if args.out:
|
|
152
|
+
argv.extend(["--out", args.out])
|
|
153
|
+
return repo_inspect.main_cli(argv)
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
def build_parser() -> argparse.ArgumentParser:
|
|
157
|
+
ap = argparse.ArgumentParser(prog="agentsam", description="agentsam_sdk CLI")
|
|
158
|
+
sub = ap.add_subparsers(dest="group", required=True)
|
|
159
|
+
|
|
160
|
+
data = sub.add_parser("data", help="D1 data audits")
|
|
161
|
+
data_sub = data.add_subparsers(dest="cmd", required=True)
|
|
162
|
+
|
|
163
|
+
bloat = data_sub.add_parser(
|
|
164
|
+
"d1-bloat",
|
|
165
|
+
help="database-scoped D1 audit (--quick=counts, --full=text sizes)",
|
|
166
|
+
)
|
|
167
|
+
bloat.add_argument("--db", help="D1 database name (else AGENTSAM_D1_DB_NAME)")
|
|
168
|
+
bloat.add_argument("--config", help="wrangler config path (else AGENTSAM_WRANGLER_CONFIG)")
|
|
169
|
+
bloat.add_argument("--repo-root", help="repo root wrangler runs from")
|
|
170
|
+
bloat.add_argument("--quick", action="store_true", default=True,
|
|
171
|
+
help="All tables: COUNT(*) only (default)")
|
|
172
|
+
bloat.add_argument("--full", action="store_true",
|
|
173
|
+
help="All tables: row counts + text/JSON LENGTH estimates")
|
|
174
|
+
bloat.add_argument("--prefix", help="Only tables whose name starts with this prefix")
|
|
175
|
+
bloat.add_argument("--workers", type=int, default=6)
|
|
176
|
+
bloat.add_argument("--top", type=int, default=40)
|
|
177
|
+
bloat.add_argument("--output-dir")
|
|
178
|
+
bloat.add_argument("--format", choices=["json", "markdown"], default="markdown")
|
|
179
|
+
bloat.set_defaults(func=_cmd_data_d1_bloat)
|
|
180
|
+
|
|
181
|
+
walk = data_sub.add_parser("agentsam-walk", help="walk agentsam_* tables")
|
|
182
|
+
walk.add_argument("--db")
|
|
183
|
+
walk.add_argument("--config")
|
|
184
|
+
walk.add_argument("--repo-root")
|
|
185
|
+
walk.add_argument("--prefix", default="agentsam_")
|
|
186
|
+
walk.add_argument("--output-dir")
|
|
187
|
+
walk.add_argument("--format", choices=["json", "markdown"], default="markdown")
|
|
188
|
+
walk.set_defaults(func=_cmd_data_agentsam_walk)
|
|
189
|
+
|
|
190
|
+
repo = sub.add_parser("repository", help="repository-level audits")
|
|
191
|
+
repo_sub = repo.add_subparsers(dest="cmd", required=True)
|
|
192
|
+
inv = repo_sub.add_parser(
|
|
193
|
+
"inventory",
|
|
194
|
+
help="file counts + sizes by logical category (jq-friendly JSON)",
|
|
195
|
+
)
|
|
196
|
+
inv.add_argument("--repo-root", default=".")
|
|
197
|
+
inv.add_argument("--output-dir")
|
|
198
|
+
inv.add_argument("--format", choices=["json", "markdown"], default="json")
|
|
199
|
+
inv.add_argument("--top", type=int, default=20, help="N largest files (0 to omit)")
|
|
200
|
+
inv.add_argument("--min-bytes", type=int, default=0)
|
|
201
|
+
inv.add_argument(
|
|
202
|
+
"--by-ext",
|
|
203
|
+
action=argparse.BooleanOptionalAction,
|
|
204
|
+
default=True,
|
|
205
|
+
help="Include by_extension_detail with byte rollups (default: on)",
|
|
206
|
+
)
|
|
207
|
+
inv.add_argument("--include-node-modules", action="store_true")
|
|
208
|
+
inv.add_argument("--include-venvs", action="store_true")
|
|
209
|
+
inv.add_argument("--include-git", action="store_true")
|
|
210
|
+
inv.add_argument("--include-dist", action="store_true")
|
|
211
|
+
inv.add_argument("--follow-symlinks", action="store_true")
|
|
212
|
+
inv.set_defaults(func=_cmd_repository_inventory)
|
|
213
|
+
|
|
214
|
+
sb = repo_sub.add_parser(
|
|
215
|
+
"scan-bloat",
|
|
216
|
+
help="largest runtime source files (KB/lines/est. tokens)",
|
|
217
|
+
)
|
|
218
|
+
sb.add_argument("--root", default=".", help="directory to scan (default: cwd)")
|
|
219
|
+
sb.add_argument("--top", type=int, default=30)
|
|
220
|
+
sb.add_argument("--min-kb", type=float, default=0)
|
|
221
|
+
sb.add_argument(
|
|
222
|
+
"--ext",
|
|
223
|
+
default=".js,.ts,.jsx,.tsx,.mjs,.cjs",
|
|
224
|
+
help="comma-separated extensions",
|
|
225
|
+
)
|
|
226
|
+
sb.add_argument("--exclude", default="", help="extra dir names to exclude")
|
|
227
|
+
sb.add_argument("--output-dir")
|
|
228
|
+
sb.add_argument("--format", choices=["json", "markdown"], default="markdown")
|
|
229
|
+
sb.add_argument(
|
|
230
|
+
"--json-envelope",
|
|
231
|
+
action="store_true",
|
|
232
|
+
help="print ToolResult.data JSON only (agent/terminal capture)",
|
|
233
|
+
)
|
|
234
|
+
sb.set_defaults(func=_cmd_repository_scan_bloat)
|
|
235
|
+
|
|
236
|
+
insp = repo_sub.add_parser(
|
|
237
|
+
"inspect",
|
|
238
|
+
help="file walk: sizes + dates (+ optional --dupes SHA-256 groups)",
|
|
239
|
+
)
|
|
240
|
+
insp.add_argument("--repo-root", default=None)
|
|
241
|
+
insp.add_argument("--format", choices=["json", "markdown", "text"], default="json")
|
|
242
|
+
insp.add_argument("--json", action="store_true")
|
|
243
|
+
insp.add_argument("--text", action="store_true")
|
|
244
|
+
insp.add_argument("--dupes", action="store_true")
|
|
245
|
+
insp.add_argument("--all", action="store_true")
|
|
246
|
+
insp.add_argument("--since", default=None)
|
|
247
|
+
insp.add_argument("--recent", type=int, default=50)
|
|
248
|
+
insp.add_argument("--largest", type=int, default=30)
|
|
249
|
+
insp.add_argument("--out", default=None)
|
|
250
|
+
insp.set_defaults(func=_cmd_repository_inspect)
|
|
251
|
+
|
|
252
|
+
return ap
|
|
253
|
+
|
|
254
|
+
|
|
255
|
+
def main(argv: list[str] | None = None) -> int:
|
|
256
|
+
parser = build_parser()
|
|
257
|
+
args = parser.parse_args(argv)
|
|
258
|
+
return args.func(args)
|
|
259
|
+
|
|
260
|
+
|
|
261
|
+
if __name__ == "__main__":
|
|
262
|
+
sys.exit(main())
|
|
File without changes
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
"""agentsam_sdk.data.agentsam_walk -- condensed port of
|
|
2
|
+
scripts/walk_agentsam_tables.py (+ schema-focus of scripts/d1_schema_audit.py).
|
|
3
|
+
|
|
4
|
+
Walks every `agentsam_%` sqlite_master object: schema, indexes, foreign
|
|
5
|
+
keys, row count, freshness (via *_at / *_at_epoch columns if present), and
|
|
6
|
+
groups tables into capability buckets by name-substring heuristics.
|
|
7
|
+
|
|
8
|
+
Note: this is a condensed re-implementation, not a byte-for-byte port of the
|
|
9
|
+
801-line original -- see docs/gaps.md for what's folded in vs deferred
|
|
10
|
+
(duplicate-detection heuristics, per-feature markdown chunking into db/*.md
|
|
11
|
+
files a la d1_schema_audit.py are deferred to a follow-up pass).
|
|
12
|
+
"""
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
from dataclasses import dataclass, field, asdict
|
|
16
|
+
from datetime import datetime, timezone
|
|
17
|
+
from pathlib import Path
|
|
18
|
+
from typing import Optional
|
|
19
|
+
|
|
20
|
+
from agentsam_sdk.data.d1_adapter import D1Adapter, D1AdapterError
|
|
21
|
+
from agentsam_sdk.runtime.contract import ToolInput, ToolResult, write_receipt, start_timer
|
|
22
|
+
|
|
23
|
+
TOOL_NAME = "data.agentsam_walk"
|
|
24
|
+
|
|
25
|
+
CAPABILITY_RULES: dict[str, list[str]] = {
|
|
26
|
+
"agent_run_spine": ["run", "runs", "execution", "executions", "session", "sessions", "step", "steps", "message", "messages"],
|
|
27
|
+
"model_routing": ["model", "catalog", "routing", "route", "routes", "arm", "arms", "requirement", "provider", "prompt"],
|
|
28
|
+
"tools_commands_mcp": ["tool", "tools", "mcp", "command", "commands", "skill", "skills", "invocation", "chain", "script", "scripts"],
|
|
29
|
+
"workflow_dag": ["workflow", "workflows", "node", "nodes", "edge", "edges", "approval", "approvals", "task", "tasks"],
|
|
30
|
+
"memory_rag": ["memory", "embedding", "vector", "chunk", "chunks", "document", "documents", "rag"],
|
|
31
|
+
"observability": ["log", "logs", "trace", "traces", "metric", "metrics", "health", "analytics", "otlp", "error", "errors"],
|
|
32
|
+
"cms_content": ["cms_", "page", "pages", "content", "nav", "template"],
|
|
33
|
+
"security_policy": ["policy", "policies", "guardrail", "trusted", "origin", "auth", "credential"],
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
@dataclass
|
|
38
|
+
class TableWalk:
|
|
39
|
+
name: str
|
|
40
|
+
columns: list[dict] = field(default_factory=list)
|
|
41
|
+
indexes: list[dict] = field(default_factory=list)
|
|
42
|
+
foreign_keys: list[dict] = field(default_factory=list)
|
|
43
|
+
row_count: int = 0
|
|
44
|
+
has_freshness_col: bool = False
|
|
45
|
+
capability: str = "uncategorized"
|
|
46
|
+
error: Optional[str] = None
|
|
47
|
+
flags: list[str] = field(default_factory=list)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def _capability_for(table: str) -> str:
|
|
51
|
+
"""Score by total matched-keyword *length*, not match count -- a single
|
|
52
|
+
specific hit ("workflow") should outrank two generic ones ("run", "runs").
|
|
53
|
+
"""
|
|
54
|
+
lname = table.lower()
|
|
55
|
+
best, best_score = "uncategorized", 0
|
|
56
|
+
for cap, keywords in CAPABILITY_RULES.items():
|
|
57
|
+
score = sum(len(kw) for kw in keywords if kw in lname)
|
|
58
|
+
if score > best_score:
|
|
59
|
+
best, best_score = cap, score
|
|
60
|
+
return best
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def _walk_table(adapter: D1Adapter, table: str) -> TableWalk:
|
|
64
|
+
tw = TableWalk(name=table, capability=_capability_for(table))
|
|
65
|
+
try:
|
|
66
|
+
cols = adapter.table_columns(table)
|
|
67
|
+
tw.columns = [{"name": n, "type": t} for n, t in cols]
|
|
68
|
+
tw.indexes = adapter.table_indexes(table)
|
|
69
|
+
tw.foreign_keys = adapter.foreign_keys(table)
|
|
70
|
+
tw.row_count = adapter.row_count(table)
|
|
71
|
+
tw.has_freshness_col = any(
|
|
72
|
+
n.lower() in ("updated_at", "created_at", "updated_at_epoch", "created_at_epoch")
|
|
73
|
+
for n, _ in cols
|
|
74
|
+
)
|
|
75
|
+
if tw.row_count == 0:
|
|
76
|
+
tw.flags.append("empty")
|
|
77
|
+
if not tw.has_freshness_col:
|
|
78
|
+
tw.flags.append("no_freshness_column")
|
|
79
|
+
if not tw.indexes and tw.row_count > 1000:
|
|
80
|
+
tw.flags.append("no_index_high_row_count")
|
|
81
|
+
except D1AdapterError as e:
|
|
82
|
+
tw.error = str(e)[:200]
|
|
83
|
+
except Exception as e: # noqa: BLE001
|
|
84
|
+
tw.error = str(e)[:200]
|
|
85
|
+
return tw
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def _render_markdown(walks: list[TableWalk], prefix: str) -> str:
|
|
89
|
+
lines = [
|
|
90
|
+
f"# agentsam walk -- `{prefix}%` objects",
|
|
91
|
+
"",
|
|
92
|
+
f"- **Generated:** {datetime.now(timezone.utc).strftime('%Y-%m-%d %H:%M UTC')}",
|
|
93
|
+
f"- **Tables walked:** {len(walks)}",
|
|
94
|
+
"",
|
|
95
|
+
"## By capability",
|
|
96
|
+
"",
|
|
97
|
+
]
|
|
98
|
+
by_cap: dict[str, list[TableWalk]] = {}
|
|
99
|
+
for w in walks:
|
|
100
|
+
by_cap.setdefault(w.capability, []).append(w)
|
|
101
|
+
for cap in sorted(by_cap):
|
|
102
|
+
lines.append(f"### {cap} ({len(by_cap[cap])})")
|
|
103
|
+
lines.append("")
|
|
104
|
+
lines.append("| Table | Rows | Indexes | FKs | Flags |")
|
|
105
|
+
lines.append("|-------|------|---------|-----|-------|")
|
|
106
|
+
for w in sorted(by_cap[cap], key=lambda x: x.row_count, reverse=True):
|
|
107
|
+
flags = ", ".join(w.flags) or "-"
|
|
108
|
+
err = f" ⚠ {w.error}" if w.error else ""
|
|
109
|
+
lines.append(f"| `{w.name}` | {w.row_count:,} | {len(w.indexes)} | {len(w.foreign_keys)} | {flags}{err} |")
|
|
110
|
+
lines.append("")
|
|
111
|
+
return "\n".join(lines)
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def run(tool_input: ToolInput) -> ToolResult:
|
|
115
|
+
started = start_timer()
|
|
116
|
+
p = tool_input.params
|
|
117
|
+
prefix = p.get("prefix", "agentsam_")
|
|
118
|
+
output_dir = tool_input.output_path()
|
|
119
|
+
|
|
120
|
+
try:
|
|
121
|
+
adapter = D1Adapter.from_env(
|
|
122
|
+
db_name=p.get("db"), wrangler_config=p.get("config"), repo_root=p.get("repo_root")
|
|
123
|
+
)
|
|
124
|
+
tables = adapter.list_tables(like=f"{prefix}%")
|
|
125
|
+
walks = [_walk_table(adapter, t) for t in tables]
|
|
126
|
+
md = _render_markdown(walks, prefix)
|
|
127
|
+
json_payload = {
|
|
128
|
+
"prefix": prefix,
|
|
129
|
+
"database": adapter.db_name,
|
|
130
|
+
"table_count": len(walks),
|
|
131
|
+
"tables": [asdict(w) for w in walks],
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
artifacts: list[str] = []
|
|
135
|
+
if output_dir:
|
|
136
|
+
output_dir.mkdir(parents=True, exist_ok=True)
|
|
137
|
+
(output_dir / "agentsam-walk.md").write_text(md, encoding="utf-8")
|
|
138
|
+
(output_dir / "agentsam-walk.json").write_text(
|
|
139
|
+
__import__("json").dumps(json_payload, indent=2), encoding="utf-8"
|
|
140
|
+
)
|
|
141
|
+
artifacts = [str(output_dir / "agentsam-walk.md"), str(output_dir / "agentsam-walk.json")]
|
|
142
|
+
|
|
143
|
+
result = ToolResult(
|
|
144
|
+
ok=True, tool=TOOL_NAME, mode=tool_input.mode, request_id=tool_input.request_id,
|
|
145
|
+
started_at=started, finished_at=start_timer(),
|
|
146
|
+
summary=f"Walked {len(walks)} `{prefix}%` tables.",
|
|
147
|
+
data=json_payload, artifacts=artifacts,
|
|
148
|
+
)
|
|
149
|
+
except D1AdapterError as e:
|
|
150
|
+
result = ToolResult(
|
|
151
|
+
ok=False, tool=TOOL_NAME, mode=tool_input.mode, request_id=tool_input.request_id,
|
|
152
|
+
started_at=started, finished_at=start_timer(),
|
|
153
|
+
summary="D1 adapter error", error=str(e),
|
|
154
|
+
)
|
|
155
|
+
|
|
156
|
+
write_receipt(result, output_dir)
|
|
157
|
+
return result
|