toga-ai 1.0.222 → 1.0.224

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.
@@ -12,6 +12,6 @@
12
12
  | [NetSuite REST Client (_Component_Api_Netsuite) — record writes & SuiteQL](features/netsuite-rest-client.md) | `_Component_Api_Netsuite` is the **2.0 `_underscore` NetSuite REST client** — the shared primitive every worker2/api2 NetSuite caller uses for record GETs, Suit | _underscore/Component/Api/Netsuite/Netsuite.php |
13
13
  | [Per-Client Database Connections & the Local Logs Trap](features/per-client-database-connections.md) | When `_underscore` serves a request for a client it opens **three distinct per-client database connections**, not one. | _underscore/Database.php, _underscore/ApiRequest.php, _underscore/Model/Client/Logs/Api.php |
14
14
  | [Recursive Item Fulfillments (upstream mirroring)](features/recursive-item-fulfillments.md) | In a multi-tier supply chain a sales order (SO) spawns a purchase order (PO) that becomes another SO downstream, and so on. | _underscore/Model/Client/ItemFulfillment.php, _underscore/Model/Client/ItemFulfillmentItem.php, _underscore/Model/Client/ItemFulfillmentItemUnit.php, _underscore/Model/Client/ItemFulfillmentPackage.php, _underscore/Model/Compass/AdvanceShippingNotice.php, dbchanges2/Core/2026-02-13 - 75601 - RecursiveItemFulfillmentCreation.sql, dbchanges2/Core/2026-06-04 - RecursiveItemFulfillmentPut.sql |
15
- | [Surface Resolver (_Model_Core_Surface::resolve — replaces Page::meta)](features/surface-resolver.md) | The runtime for the platform-wide **Surface** UI presentation layer: 9 `_underscore` models plus a cached resolver, `_Model_Core_Surface::resolve(&$api, string | _underscore/Model/Core/Surface.php, _underscore/Model/Core/SurfaceElement.php, _underscore/Model/Core/Action.php, _underscore/Model/Core/Vocabulary.php, _underscore/Model/Core/VocabularyTerm.php, _underscore/Model/Core/Message.php, _underscore/Model/Client/SurfaceOverride.php, _underscore/Model/Client/MessageTranslation.php, _underscore/Model/Client/ThemeToken.php, _underscore/Model/Core/Page.php |
15
+ | [Surface Resolver (_Model_Core_Surface::resolve — replaces Page::meta)](features/surface-resolver.md) | The runtime for the platform-wide **Surface** UI presentation layer: 9 `_underscore` models plus a cached resolver, `_Model_Core_Surface::resolve(&$api, string | _underscore/Model/Core/Surface.php, dbchanges2/Core/2026-06-29b - SurfaceMetaPublicReadAcl.sql, _underscore/Model/Core/SurfaceElement.php, _underscore/Model/Core/Action.php, _underscore/Model/Core/Vocabulary.php, _underscore/Model/Core/VocabularyTerm.php, _underscore/Model/Core/Message.php, _underscore/Model/Client/SurfaceOverride.php, _underscore/Model/Client/MessageTranslation.php, _underscore/Model/Client/ThemeToken.php, _underscore/Model/Core/Page.php |
16
16
  | [Tracking-Number Bridge Migration (ASN / Item Fulfillment / Item Receipt)](features/tracking-number-bridges.md) | Shipment tracking numbers used to live as **scalar FK columns** (`trackingNumberId`, `returnTrackingNumberId`) directly on the lowest-level "unit"/"item" tables | api2/Component/Api/V2/V2.php, _underscore/Model/Client/AdvanceShippingNoticeItemUnit.php, _underscore/Model/Client/AdvanceShippingNoticeItemUnits/TrackingNumber.php, _underscore/Model/Client/ItemFulfillmentItemUnits/TrackingNumber.php, _underscore/Model/Client/ItemFulfillment.php, _underscore/Model/Prudential/AdvanceShippingNotice.php, _underscore/Model/Compass/AdvanceShippingNotice.php, _underscore/Trait/Netsuite/ItemFulfillment.php, api2/Component/Api/Cxml/Cxml.php, dbchanges2/Client/2026-06-10 - TrackingNumberBridges.sql, dbchanges2/Core/2026-06-10 - TrackingNumberBridges.sql, dbchanges2/Client_Prudential/2026-06-15 - ItemFulfillmentTrackingNumberAclLogicGroups.sql, dbchanges2/Client_Quad/2026-06-18a - ItemFulfillmentItemReceiptTrackingNumberAclLogicGroups.sql, dbchanges2/Client_Nychh/2026-06-19b - ItemFulfillmentItemReceiptTrackingNumberAclLogicGroups.sql, dbchanges2/Client_Nychh/2026-06-19a - FixItemFulfillmentTableViewTrackingAndRoot.sql, dbchanges2/Client_Quad/2026-06-19a - FixItemFulfillmentTableViewTrackingAndRoot.sql |
17
17
  | [Units for Items for Purchase Orders — Data Structure](features/units-for-items-for-purchase-orders.md) | Describes how unit (serialized inventory) data is linked to sales-order and purchase-order line items behind the `units-for-items-for-purchase-orders` TableView | |
@@ -6,7 +6,7 @@ project: _Underscore
6
6
  client: shared
7
7
  type: architecture
8
8
  status: active
9
- updated: 2026-06-25
9
+ updated: 2026-06-29
10
10
  owners: ["jcardinal", "rgirish"]
11
11
  files:
12
12
  - _underscore/_underscore.php
@@ -71,7 +71,7 @@ Tier-1 trivial declarative rules (frozen `all/any/none + {field,op,value}` gramm
71
71
  client-side; Tier-2 business logic computed in PHP returning booleans (lives in
72
72
  `_Model_<Client>_*` overrides + interceptors). Config/logic boundary: **config describes,
73
73
  PHP decides.** M2M-safe delivery via opt-in `surface=<slug>` request option → response
74
- `meta.surface`, never `data`. Generic FE machinery destined for `@agilant/toga-blox`.
74
+ `meta.surface`, never `data`. Generic FE machinery destined for `@agilant/toga-blox`. **Surface is the enforced presentation path — no feature flag.** The `SURFACE_ENABLED`/`VITE_SURFACE_ENABLED` flag and all legacy pre-Surface fallback branches were removed; legacy per-screen FIELDS config is deleted as each screen migrates (no dual-path fallback). The meta endpoint is query-string form: `GET /v2/surfaces/meta?slug=<slug>`.
75
75
 
76
76
  **Rejected alternatives:** a generic EAV settings cascade (the old `*Settings` tables —
77
77
  proven near-empty in prod); raw-CSS-in-config (the old "Dummy Fields" anti-pattern).
@@ -80,7 +80,7 @@ proven near-empty in prod); raw-CSS-in-config (the old "Dummy Fields" anti-patte
80
80
  allowlist that throws on unknown ops, with a CI test asserting the op-set; defensive
81
81
  resolver with logged fallbacks; debug-bundle endpoint ships WITH the resolver; `c_longValue`
82
82
  on overrides with strict per-attribute casting; per-surface meta() cutover with a parity
83
- diff (do NOT delete meta() until every page is migrated); `BLANK_CLIENT_DATABASE` as a CI gate.
83
+ diff (do NOT delete meta() until every page is migrated); `BLANK_CLIENT_DATABASE` as a CI gate. Reserved Core id blocks: `Records` 333–341, `RecordFields` 2246–2433 (renumbered 2026-06-29, seed files only — a known file-vs-DB id drift in already-provisioned envs, to reconcile by re-provisioning). Anything spanning both id schemes must be id-agnostic (match by `route=`, not id).
84
84
 
85
85
  ## Naming convention & autoloader
86
86
 
@@ -324,3 +324,4 @@ multi-file UI components (`.php`/`.html`/`.css`/`.js`) invoked as `<_ComponentNa
324
324
  ## Change history
325
325
  - 2026-06-11 — Documented lazy transaction gotcha in `_Database::register()` (rgirish)
326
326
  - 2026-06-25 — Added the Surface platform UI presentation/configuration layer (DB-driven UI config replacing `Page::meta()`, CTO-reviewed AGREE-WITH-ADJUSTMENTS) (jcardinal)
327
+ - 2026-06-29 — Surface made the enforced (un-flagged) presentation path; reserved id blocks renumbered to Records 333–341 / RecordFields 2246–2433 (known seed-vs-provisioned drift). (jcardinal)
@@ -6,10 +6,11 @@ project: _Underscore
6
6
  client: shared
7
7
  type: feature
8
8
  status: active
9
- updated: 2026-06-25
9
+ updated: 2026-06-29
10
10
  owners: [jcardinal]
11
11
  files:
12
12
  - _underscore/Model/Core/Surface.php
13
+ - dbchanges2/Core/2026-06-29b - SurfaceMetaPublicReadAcl.sql
13
14
  - _underscore/Model/Core/SurfaceElement.php
14
15
  - _underscore/Model/Core/Action.php
15
16
  - _underscore/Model/Core/Vocabulary.php
@@ -30,8 +31,9 @@ related:
30
31
  The runtime for the platform-wide **Surface** UI presentation layer: 9 `_underscore` models plus a
31
32
  cached resolver, `_Model_Core_Surface::resolve(&$api, string $slug)`, that merges base config +
32
33
  cascade overrides + ACL + messages + theme tokens into one flat, serializable bundle. It is the
33
- **replacement for `_Model_Core_Page::meta()`** and is shipped as a scripted-API method behind
34
- `GET /v2/surfaces/{slug}/meta`. Platform/shared — not client-specific. Schema:
34
+ **replacement for `_Model_Core_Page::meta()`** and is shipped as a scripted-API method called as
35
+ `GET /v2/surfaces/meta?slug=<slug>` (RecordScript args come from the **query string**, not the
36
+ path — see gotchas). Platform/shared — not client-specific. Schema:
35
37
  [surface-layer-schema](../../dbchanges2/features/surface-layer-schema.md).
36
38
 
37
39
  ## The 9 models
@@ -62,8 +64,8 @@ Inputs come from `$api` (the same accessor `_Model_Client_TableView::meta` uses)
62
64
  table pipeline (referenced, never duplicated).
63
65
  7. Assemble the flat bundle. **Zero INSERTs.**
64
66
 
65
- A `debug()` inspector returns the resolved bundle plus the raw cascade layers (shipped with the
66
- resolver, not "later").
67
+ A `metaDebug()` inspector returns the resolved bundle plus the raw cascade layers (shipped with the
68
+ resolver, not "later"). It is **not** named `debug()` — see gotchas.
67
69
 
68
70
  ## Caching & invalidation
69
71
 
@@ -97,8 +99,29 @@ resolver, not "later").
97
99
  falls back to sane defaults and never crashes a page. Strict per-`attribute` casting with logged
98
100
  fallback on bad `SurfaceOverrides` data.
99
101
  - **Cross-DB reads are batched** (DB_CORE vs DB_CLIENT) and FKs are soft — tolerate dangling refs.
102
+ - **A scripted-API method MUST NOT collide with a non-static `_Model` base method.** Naming the
103
+ inspector `debug()` caused a fatal *"Cannot make non static method _Model::debug() static"* — the
104
+ framework base `_Model` defines a NON-static `debug()` (Model.php:167) and the scripted-API
105
+ dispatch calls the method statically. Renamed to `metaDebug` and remapped the RecordScript
106
+ (`route 'debug' → phpMethod 'metaDebug'`). Vet any new scripted-API method name against the
107
+ `_Model` base (e.g. `debug`, and check others) before mapping it.
108
+ - **The endpoint is `GET /v2/surfaces/meta?slug=<slug>`, not `/v2/surfaces/<slug>/meta`.** TOGA
109
+ RecordScripts read their args from the **query string**; the path form makes the engine parse the
110
+ slug as a record uuid and return 404 EV-6. Pattern for any RecordScript:
111
+ `/<route>/<scriptRoute>?<arg>=...`.
112
+ - **Core metadata records exposed to the app must grant CORE role Public (id 1) READ.** The meta
113
+ RecordScript is ACL-gated on the `surfaces` Core record, and authenticated app users carry CORE
114
+ role Public(1); a seed that grants only Super User(3)/Base(4) returns 403 EZ-1. Grant Public(1)
115
+ `allowRead` on the `surfaces` record (full AclRecordPermissions → AclLogicGroups →
116
+ AclLogicGroupExpressions → AclRecordExpressions 'all' chain), mirroring how `apps`/`records`/
117
+ `record-fields` already expose Public READ. Only the `surfaces` record gets Public read — the
118
+ admin-CRUD sibling records stay Super-User-only (the resolver reads their tables server-side).
100
119
 
101
120
  ## Change history
102
- - 2026-06-25 — Built the 9 models + `resolve()`/`debug()` as the cached, parameterized,
121
+ - 2026-06-29 — Deploy fixes: renamed the scripted inspector `debug()`→`metaDebug()` (name collided
122
+ with non-static `_Model::debug()`, fatal); corrected the endpoint to query-string form
123
+ `GET /v2/surfaces/meta?slug=` (path form 404'd EV-6); granted CORE Public(1) READ on the `surfaces`
124
+ record so app users stop getting 403 EZ-1 on meta (id-agnostic migration matches by `route='surfaces'`). (jcardinal)
125
+ - 2026-06-25 — Built the 9 models + `resolve()`/`metaDebug()` as the cached, parameterized,
103
126
  zero-write-on-read replacement for `_Model_Core_Page::meta()`; cache-bust interceptors on all 9
104
127
  models (outside the transaction). meta() retained until per-page cutover with parity diff. (jcardinal)
@@ -6,7 +6,7 @@ project: API
6
6
  client: shared
7
7
  type: feature
8
8
  status: active
9
- updated: 2026-06-25
9
+ updated: 2026-06-29
10
10
  owners: [jcardinal]
11
11
  files:
12
12
  - api2/Component/Api/V2/V2.php
@@ -42,6 +42,17 @@ traffic), behavior is **byte-for-byte unchanged** — pure data, zero extra work
42
42
  the logic.
43
43
  - **One call, no extra round-trip** — the state pass rides the existing data fetch.
44
44
 
45
+ ## Gotchas
46
+
47
+ - **Meta endpoint 403 EZ-1 for normal app users → grant CORE Public(1) READ on the `surfaces`
48
+ record.** Authenticated app users carry CORE role Public (id 1); the meta RecordScript is
49
+ ACL-gated on the `surfaces` Core record, and a seed granting only Super User(3)/Base(4) rejects
50
+ them. Grant Public(1) `allowRead` on `surfaces` only (the admin-CRUD sibling records stay
51
+ Super-User-only — the resolver reads their tables server-side). See
52
+ [surface-resolver](../../_underscore/features/surface-resolver.md) gotchas for the full chain.
53
+
45
54
  ## Change history
55
+ - 2026-06-29 — Documented the meta-endpoint ACL: normal app users (CORE Public role 1) need
56
+ `allowRead` on the `surfaces` record or meta returns 403 EZ-1. (jcardinal)
46
57
  - 2026-06-25 — Added the `surface=<slug>` opt-in option to the V2 engine; attaches per-record action
47
58
  state under `meta.surface` (M2M-safe, defensive, absent = unchanged). (jcardinal)
@@ -3,5 +3,5 @@
3
3
  | Doc | Summary | Files |
4
4
  |-----|---------|-------|
5
5
  | [Database Changes (dbchanges2) Repository Architecture](architecture.md) | `dbchanges2` is the **schema-migration / SQL change-set repository** for the entire 2.0 platform. | Core/, Client/, Client_<Tenant>/, Logs/, Logs_Client/, _modules/ |
6
- | [Surface Layer Schema (UI presentation/config tables)](features/surface-layer-schema.md) | The persistent schema for the platform-wide **Surface** UI presentation/configuration layer (see the `_underscore` [surface-resolver](../../_underscore/features | dbchanges2/Core/2026-06-25a - SurfaceCoreTables.sql, dbchanges2/Core/2026-06-25b - SurfaceRecordsAndFields.sql, dbchanges2/Core/2026-06-25c - SalesOrderLoginSurfaceSeed.sql, dbchanges2/Client/2026-06-25a - SurfaceClientTables.sql, dbchanges2/Client/2026-06-25b - SurfaceClientSeed.sql, dbchanges2/Client/2026-06-25c - SurfaceClientAcl.sql, dbchanges2/Client/2026-06-03- BLANK_CLIENT_DATABASE.sql |
6
+ | [Surface Layer Schema (UI presentation/config tables)](features/surface-layer-schema.md) | The persistent schema for the platform-wide **Surface** UI presentation/configuration layer (see the `_underscore` [surface-resolver](../../_underscore/features | dbchanges2/Core/2026-06-25a - SurfaceCoreTables.sql, dbchanges2/Core/2026-06-25b - SurfaceRecordsAndFields.sql, dbchanges2/Core/2026-06-25c - SalesOrderLoginSurfaceSeed.sql, dbchanges2/Core/2026-06-29a - ItemsSurfaceSeed.sql, dbchanges2/Core/2026-06-29b - SurfaceMetaPublicReadAcl.sql, dbchanges2/Client/2026-06-25a - SurfaceClientTables.sql, dbchanges2/Client/2026-06-25b - SurfaceClientSeed.sql, dbchanges2/Client/2026-06-25c - SurfaceClientAcl.sql, dbchanges2/Client/2026-06-03- BLANK_CLIENT_DATABASE.sql |
7
7
  | [2.0 New-Client Onboarding (manual process)](workflows/client-onboarding.md) | How to manually stand up a new 2.0 client (tenant). | Client/, Client_<Tenant>/, Core/, Logs_Client/ |
@@ -6,12 +6,14 @@ project: Database Changes
6
6
  client: shared
7
7
  type: feature
8
8
  status: active
9
- updated: 2026-06-25
9
+ updated: 2026-06-29
10
10
  owners: [jcardinal]
11
11
  files:
12
12
  - dbchanges2/Core/2026-06-25a - SurfaceCoreTables.sql
13
13
  - dbchanges2/Core/2026-06-25b - SurfaceRecordsAndFields.sql
14
14
  - dbchanges2/Core/2026-06-25c - SalesOrderLoginSurfaceSeed.sql
15
+ - dbchanges2/Core/2026-06-29a - ItemsSurfaceSeed.sql
16
+ - dbchanges2/Core/2026-06-29b - SurfaceMetaPublicReadAcl.sql
15
17
  - dbchanges2/Client/2026-06-25a - SurfaceClientTables.sql
16
18
  - dbchanges2/Client/2026-06-25b - SurfaceClientSeed.sql
17
19
  - dbchanges2/Client/2026-06-25c - SurfaceClientAcl.sql
@@ -60,11 +62,13 @@ SurfaceElements`). Core migrations run first; Client after. The session built/se
60
62
  **SalesOrders + login** vertical (not Tickets) as the review proof:
61
63
 
62
64
  - `Core/2026-06-25a - SurfaceCoreTables.sql` — the 6 Core tables.
63
- - `Core/2026-06-25b - SurfaceRecordsAndFields.sql` — `Core.Records`+`RecordFields` for all 9 tables (reserved id block **2300–2399**; `recordId` via `model=` subselect).
65
+ - `Core/2026-06-25b - SurfaceRecordsAndFields.sql` — `Core.Records`+`RecordFields` for all 9 tables (`Records` ids **333–341**, `RecordFields` ids **2246–2433**; `recordId` via `model=` subselect). Also maps the meta `RecordScript` `route 'debug' → phpMethod 'metaDebug'` (the inspector cannot be named `debug` — see [surface-resolver](../../_underscore/features/surface-resolver.md)).
64
66
  - `Core/2026-06-25c - SalesOrderLoginSurfaceSeed.sql` — Core seed + the `RecordScripts` resolve row.
67
+ - `Core/2026-06-29a - ItemsSurfaceSeed.sql` — the Items LIST screen seed: surfaces `items-list` (TABLE → existing `items` TableView id 9, recordId 21) + `items-list-actions` (BUTTON_BAR: refresh/columns/newItem) + Actions + ~11 Messages. Single DEFAULT bundle, no Client overrides.
68
+ - `Core/2026-06-29b - SurfaceMetaPublicReadAcl.sql` — grants CORE role Public(1) `allowRead` on the `surfaces` record (so app users stop getting 403 EZ-1 on meta). **Id-agnostic**: matches the record by `route='surfaces'`, so it works whether the record id is 333 or the legacy 2300.
65
69
  - `Client/2026-06-25a - SurfaceClientTables.sql` — the 3 Client tables + `INSERT IGNORE Languages('en','English')`.
66
70
  - `Client/2026-06-25b - SurfaceClientSeed.sql` — `ThemeTokens` + `MessageTranslations(en)`.
67
- - `Client/2026-06-25c - SurfaceClientAcl.sql` — full ACL chain for the 3 CLIENT-aclDatabase records.
71
+ - `Client/2026-06-25c - SurfaceClientAcl.sql` — full ACL chain for the 3 CLIENT-aclDatabase records (recordId refs updated to the 333-block renumber).
68
72
 
69
73
  ## Gotchas
70
74
 
@@ -79,12 +83,22 @@ SurfaceElements`). Core migrations run first; Client after. The session built/se
79
83
  - **`SurfaceOverrides.value` is a stringly-typed escape hatch** — a long `CONFIG`/label override
80
84
  would silently truncate at varchar(255); that is why `c_longValue mediumtext` exists, and the
81
85
  resolver MUST cast strictly per `attribute` (bool/int/string/json) with a logged fallback.
82
- - **Reserved id block 2300–2399** for the Surface `Records`/`RecordFields` rows (Core max was 331);
83
- use `model=` subselects for `recordId`, never hardcoded ids.
86
+ - **Reserved id blocks (renumbered 2026-06-29):** Surface `Core.Records` ids **333–341** (9 tables),
87
+ `Core.RecordFields` ids **2246–2433**. (Originally seeded at 2300–2308 / 2400+, renumbered at the
88
+ team's request.) `RecordScripts.recordId` and the Core+Client ACL `recordId` refs were updated to
89
+ match. Use `model=` subselects for `recordId`, never hardcoded ids.
90
+ - **Known file-vs-DB id drift.** The renumber changed the **seed files only** — already-provisioned
91
+ envs still hold the old 2300/2400 ids. This is intentional/known, to be reconciled by
92
+ re-provisioning later. Anything that must work across both (e.g. the Public-READ ACL grant) must
93
+ be **id-agnostic** — match the record by `route='surfaces'`, not by id.
84
94
  - **CLIENT-aclDatabase records** (`SurfaceOverrides`, `MessageTranslations`, `ThemeTokens`) need the
85
95
  full 4-step ACL chain + field permissions in **each** client DB; resolve `roleId` by subselect.
86
96
 
87
97
  ## Change history
88
- - 2026-06-25 — Initial schema for the Surface layer: 6 Core + 3 Client tables, `Records`/`RecordFields`
89
- (ids 2300–2399), SalesOrders+login seed, ACL chain, and `BLANK_CLIENT_DATABASE` append. Typed
98
+ - 2026-06-29 — Renumbered the reserved seed id blocks (Records 2300-2308→**333–341**, RecordFields
99
+ 2400+→**2246–2433**) in the seed files only — known file-vs-DB drift in provisioned envs. Added
100
+ the Items LIST seed (`ItemsSurfaceSeed.sql`) and the id-agnostic CORE Public(1) READ ACL grant on
101
+ `surfaces` (`SurfaceMetaPublicReadAcl.sql`, matches by `route='surfaces'`). (jcardinal)
102
+ - 2026-06-25 — Initial schema for the Surface layer: 6 Core + 3 Client tables, `Records`/`RecordFields`,
103
+ SalesOrders+login seed, ACL chain, and `BLANK_CLIENT_DATABASE` append. Typed
90
104
  columns chosen over EAV (the old `*Settings` cascade was empirically near-empty in prod). (jcardinal)
@@ -7,4 +7,5 @@
7
7
  | [Deployment — Docker, Compose, Entrypoint, External PG/Redis](features/deployment.md) | TOGa IQ ships as a **single container** (`aegra` service) wrapping the `aegra-api` FastAPI server. | talos/docker-compose.yml, talos/deployments/docker/Dockerfile, talos/deployments/docker/entrypoint.sh |
8
8
  | [MCP Servers — clickup-mcp and toga-db-mcp](features/mcp-servers.md) | Two internal **FastMCP** servers exposed over **HTTP** with API-key auth and PM2 process management: - **`clickup-mcp`** — ClickUp workspace surface (spaces / f | talos/mcp-servers/clickup-mcp/src, talos/mcp-servers/clickup-mcp/ecosystem.config.js, talos/mcp-servers/clickup-mcp/ecosystem.dev.config.js, talos/mcp-servers/clickup-mcp/pyproject.toml, talos/mcp-servers/clickup-mcp/.env.example, talos/mcp-servers/toga-db-mcp/src, talos/mcp-servers/toga-db-mcp/clusters.yaml, talos/mcp-servers/toga-db-mcp/ecosystem.config.js, talos/mcp-servers/toga-db-mcp/pyproject.toml, talos/mcp-servers/toga-db-mcp/.env.example |
9
9
  | [Observability — Langfuse, OTEL, Prometheus, OneUptime](features/observability.md) | TOGa IQ uses **two complementary tracing planes** plus optional Prometheus metrics and external uptime monitoring: - **Langfuse (native v3 SDK)** — LLM-shaped t | talos/libs/aegra-api/src/aegra_api/observability/__init__.py, talos/libs/aegra-api/src/aegra_api/observability/setup.py, talos/libs/aegra-api/src/aegra_api/observability/base.py, talos/libs/aegra-api/src/aegra_api/observability/langfuse_provider.py, talos/libs/aegra-api/src/aegra_api/observability/langfuse_client.py, talos/libs/aegra-api/src/aegra_api/observability/otel.py, talos/libs/aegra-api/src/aegra_api/observability/metrics.py, talos/libs/aegra-api/src/aegra_api/observability/span_enrichment.py, talos/libs/aegra-api/src/aegra_api/observability/targets |
10
+ | [Pricing & COGS Model (Talos / TOGa IQ Pricing Calculator)](features/pricing-cogs-model.md) | The cost basis and pricing methodology for selling Talos (TOGa IQ) chat and voice deployments. | |
10
11
  | [talos_agent — LangGraph ReAct Agent with Plan, BLP, MCP, Canvas](features/talos-agent.md) | `talos_agent` is the **reference LangGraph agent** shipped under `agents/talos_agent/`. | talos/agents/talos_agent/graph.py, talos/agents/talos_agent/state.py, talos/agents/talos_agent/context.py, talos/agents/talos_agent/prompts.py, talos/agents/talos_agent/tool_router.py, talos/agents/talos_agent/model_helpers.py, talos/agents/talos_agent/config/context.py, talos/agents/talos_agent/config/model_config.py, talos/agents/talos_agent/config/tool_config.py, talos/agents/talos_agent/config/plan_config.py, talos/agents/talos_agent/config/memory_config.py, talos/agents/talos_agent/config/mcp_config.py, talos/agents/talos_agent/config/blp_config.py, talos/agents/talos_agent/config/interrupt_config.py, talos/agents/talos_agent/config/artifact_config.py, talos/agents/talos_agent/plan/planner.py, talos/agents/talos_agent/plan/nodes.py, talos/agents/talos_agent/llm/system_prompt.py, talos/agents/talos_agent/llm/tool_binding.py, talos/agents/talos_agent/llm/message_ops.py, talos/agents/talos_agent/mcp/client.py, talos/agents/talos_agent/mcp/security.py, talos/agents/talos_agent/mcp/session_manager.py, talos/agents/talos_agent/blp/registry.py, talos/agents/talos_agent/blp/embedder.py, talos/agents/talos_agent/blp/nodes.py, talos/agents/talos_agent/tools/__init__.py, talos/agents/talos_agent/tools/code_interpreter.py, talos/agents/talos_agent/tools/canvas.py, talos/agents/talos_agent/tools/knowledge_base.py, talos/agents/talos_agent/tools/skill_injector.py, talos/agents/talos_agent/thread_name/nodes.py |
@@ -0,0 +1,153 @@
1
+ ---
2
+ title: Pricing & COGS Model (Talos / TOGa IQ Pricing Calculator)
3
+ framework: "2.0"
4
+ repo: talos
5
+ project: TOGa IQ
6
+ client: shared
7
+ type: feature
8
+ status: active
9
+ updated: 2026-06-29
10
+ owners: [jcardinal]
11
+ files: []
12
+ related:
13
+ - ../architecture.md
14
+ - aegra-api.md
15
+ - observability.md
16
+ ---
17
+
18
+ ## Summary
19
+
20
+ The cost basis and pricing methodology for selling Talos (TOGa IQ) chat and
21
+ voice deployments. The deliverable is a **per-client Excel workbook — TALOS
22
+ Pricing Calculator v9** (replaces v8) — that lives on the team's shared AI drive
23
+ (under `P:\Work\AI\`), **not** in any repo. This doc captures the durable
24
+ knowledge: the cost drivers, measured production rates, and the pricing
25
+ decisions, so anyone can re-price Talos without re-deriving the model.
26
+
27
+ There is no Talos source code behind this — it is a sales/finance model. Treat
28
+ the numbers below as the **April 2026 baseline**; re-measure before relying on
29
+ them in a later period.
30
+
31
+ ## Chat COGS model
32
+
33
+ Chat AI cost is driven primarily by **LLM tokens on Amazon Bedrock**.
34
+
35
+ ### Bedrock token rates (per 1M tokens)
36
+
37
+ | Token class | Rate |
38
+ |---|---|
39
+ | Fresh input | $3.00 |
40
+ | Cached-instruction read | $0.30 |
41
+ | Cache write | $3.75 |
42
+ | Output | $15.00 |
43
+
44
+ ### Measured per-exchange production averages
45
+
46
+ Source: tenant DB, April 2026, ~3,610 exchanges.
47
+
48
+ | Metric | Tokens / exchange |
49
+ |---|---|
50
+ | Cached instructions | 15,013 |
51
+ | Cache write | 2,830 |
52
+ | Output | 527 |
53
+
54
+ ### Per-feature token adders (measured)
55
+
56
+ | Feature | Tokens per use | Sample size |
57
+ |---|---|---|
58
+ | Document search | 4,747 / search | 542 searches |
59
+ | Web search | 765 / search | 120 searches |
60
+ | Code execution | 205 / run | 929 runs |
61
+ | App action | 1,086 / action | 71 actions |
62
+
63
+ **Assumed feature occurrences per conversation:** document search 2, web search
64
+ 1, code execution 2, app actions 5 **per connected app**.
65
+
66
+ ### Compute and fixed infrastructure
67
+
68
+ - **Code session compute** — Bedrock AgentCore ~$0.05 / session; ~80% of
69
+ code-enabled conversations trigger a session.
70
+ - **Platform / hosting** — $910 / month fixed.
71
+ - **Document-search index** — OpenSearch Serverless, +$350 / month, charged
72
+ **only when Document Search is enabled**.
73
+
74
+ ### Conversation model constants (v8/v9 engine)
75
+
76
+ - System overhead: 200 tokens / conversation
77
+ - Base fresh: 727 tokens / exchange
78
+ - History accumulation factor: ×7
79
+ - Exchanges per conversation: 15
80
+
81
+ > **Gotcha (flagged for technical review):** history accumulates over ×7 but
82
+ > the per-exchange cost is multiplied by 15 exchanges. This mismatch is a known
83
+ > quirk, **preserved as-is in v9 for number parity with v8** — do not "fix" it
84
+ > silently, as it would change every quoted price. See Change history.
85
+
86
+ ### Measured cost-per-exchange benchmarks by assistant type (April 2026)
87
+
88
+ | Assistant type | Cost / exchange |
89
+ |---|---|
90
+ | Contact Centre | $0.0223 |
91
+ | HR / Compliance | $0.0354 |
92
+ | General Purpose | $0.0435 |
93
+ | Sales / CRM | $0.0829 |
94
+ | Technical / Data-heavy | $0.1139 |
95
+ | Enterprise heavy | $0.1261 |
96
+
97
+ ## Voice COGS model
98
+
99
+ Talos Voice (voice-to-voice) is priced **per minute**. It uses the AWS STT/TTS
100
+ approach. Per-minute drivers:
101
+
102
+ | Driver | Rate / basis |
103
+ |---|---|
104
+ | LiveKit | $50/mo plan incl. 5,000 min; $0.01/min overage |
105
+ | Amazon Transcribe (STT) | $0.024 / min |
106
+ | Amazon Polly Generative (TTS) | $30 / 1M chars at ~400 chars/min |
107
+ | LLM tokens | ~800 fresh, ~5,000 cached, ~300 output per minute, at the chat token rates above |
108
+ | S3 recording storage | $0.023 / GB-mo at ~0.5 MB/min over the retention window |
109
+
110
+ ## Pricing methodology (decision)
111
+
112
+ Adopted to **reduce client-facing price fluctuation while protecting margin**:
113
+
114
+ 1. **Two-part price.** Client pays a **flat monthly org fee** PLUS a **fixed
115
+ monthly per-user fee**. The per-user fee is a stable rate-card value and does
116
+ **not** change.
117
+ 2. **Margin band.** Sales sets a min and max gross-margin band. The recommended
118
+ flat fee is solved so projected margin lands at the **midpoint** of the band:
119
+
120
+ ```
121
+ flat = COGS / (1 - mid_margin) - per_user_fee * users (floored at 0)
122
+ ```
123
+ 3. **Smoothing rule.** Actual COGS is tracked monthly per client. The flat fee
124
+ is changed **only after** actual margin sits outside the band for a
125
+ configurable number of **consecutive** months (**default 3**). The per-user
126
+ fee is **never** adjusted.
127
+
128
+ ### Workbook structure (TALOS Pricing Calculator v9)
129
+
130
+ | Tab | Purpose |
131
+ |---|---|
132
+ | Sales | Rep-facing inputs + recommended flat fee + min/mid/max margin-band options |
133
+ | Voice | Per-minute band pricing |
134
+ | Technical | Single source of truth for all raw rates — sales never edits |
135
+ | Actuals | Monthly actual-AWS-COGS entry, margin-vs-band status, consecutive-breach flags, suggested mid-band flat fee |
136
+ | Engine | Hidden calculation engine |
137
+
138
+ The workbook is a **per-client** file on the shared AI drive (`P:\Work\AI\`).
139
+
140
+ ## Gotchas / known issues
141
+
142
+ - **Numbers are an April 2026 baseline.** Token rates, AWS prices, and measured
143
+ per-exchange averages drift — re-measure from the tenant DB before quoting in
144
+ a later period.
145
+ - **History ×7 vs. ×15 exchanges quirk** (see Chat model) — intentional parity
146
+ hack, not a bug to silently correct.
147
+ - **OpenSearch +$350/mo is conditional** — only counts when Document Search is
148
+ enabled; do not bake it into every quote.
149
+ - **Do not embed the absolute drive path as canonical** — the calculator lives
150
+ under the team shared AI drive; the path can move.
151
+
152
+ ## Change history
153
+ - 2026-06-29 — Initial pricing/COGS model doc: chat token + feature drivers, voice per-minute drivers, two-part flat+per-user methodology with mid-band solve and consecutive-month smoothing. Captured from TALOS Pricing Calculator v9 (replaces v8). v9 verified to reproduce v8 exactly ($0.811182/conversation, $2,071.18/mo for 50 users × 20 conversations, Document Search only). History ×7 vs ×15-exchanges quirk preserved for parity and flagged for technical review. (jcardinal)
@@ -8,4 +8,4 @@
8
8
  | [Column Visibility (URL-driven show/hide columns)](features/column-visibility.md) | A "Columns" header button that opens a modal listing every column from the table meta, lets the user show/hide columns, adjusts the table live, and persists the | toga25-supply/src/components/ColumnVisibilityModal/, toga25-supply/src/pages/SalesOrders/SalesOrders.tsx, toga25-supply/src/pages/SalesOrders/viewModel/useSalesOrdersPageViewModel.tsx, toga25-supply/src/pages/SalesOrders/hooks/useSalesOrdersTableData.tsx |
9
9
  | [Meta-Driven Page & Table Setup](features/meta-driven-table-data.md) | A page in this app is **meta-driven end to end**: the page view model fetches *page meta* (labels, sections, ACL) and *table meta* (the columns/fields + table s | toga25-supply/src/pages/SalesOrders/viewModel/useSalesOrdersPageViewModel.tsx, toga25-supply/src/pages/SalesOrders/hooks/useSalesOrdersTableState.ts, toga25-supply/src/hooks/useTablePageMeta.ts, toga-blox-npm/dist/hooks/useFetchPageMeta.d.ts, toga-blox-npm/dist/hooks/useFetchTablePageMeta.d.ts, toga-blox-npm/dist/hooks/useAssignTableFieldLabels.d.ts, toga-blox-npm/dist/components/Table/hooks/useTableData.d.ts |
10
10
  | [Record Modals & Nested Tables](features/record-modals-and-nested-tables.md) | The repo's family of modal + nested-table patterns layered over toga-blox `TableRecordModal` and `PrimaryTable*Layout`. | toga25-supply/src/layout/ItemRecordModalLayout/, toga25-supply/src/layout/SalesOrderRecordModalLayout/, toga25-supply/src/layout/SalesOrderItemsTableLayout/, toga25-supply/src/layout/ItemFulfillmentModal/, toga25-supply/src/layout/GenericNestedTables/, toga25-supply/src/hooks/useTableCellInteractions.ts |
11
- | [Surface Frontend (DB-driven UI consumption, src/surface/)](features/surface-frontend.md) | The frontend consumer of the platform-wide Surface layer — DB-driven UI config fetched from `GET /v2/surfaces/{slug}/meta` instead of statically-imported JSON f | toga25-supply/src/surface/useFetchSurfaceMeta.ts, toga25-supply/src/surface/evaluateSurfaceRule.ts, toga25-supply/src/surface/actionRegistry.ts, toga25-supply/src/surface/SurfaceActionBar.tsx, toga25-supply/src/surface/SurfaceSection.tsx, toga25-supply/src/surface/resolve.ts, toga25-supply/src/surface/types.ts, toga25-supply/src/surface/featureFlag.ts, toga25-supply/src/surface/index.ts, toga25-supply/src/pages/Login/LoginPage.tsx, toga25-supply/src/pages/SalesOrders/SalesOrders.tsx, toga25-supply/src/pages/SalesOrders/view/SalesOrderRecordModalLayout/view/sections/SalesOrderTopBar.tsx |
11
+ | [Surface Frontend (DB-driven UI consumption, src/surface/)](features/surface-frontend.md) | The frontend consumer of the platform-wide Surface layer — DB-driven UI config fetched from `GET /v2/surfaces/meta?slug=<slug>` instead of statically-imported J | toga25-supply/src/surface/useFetchSurfaceMeta.ts, toga25-supply/src/surface/evaluateSurfaceRule.ts, toga25-supply/src/surface/actionRegistry.ts, toga25-supply/src/surface/SurfaceActionBar.tsx, toga25-supply/src/surface/SurfaceSection.tsx, toga25-supply/src/surface/resolve.ts, toga25-supply/src/surface/types.ts, toga25-supply/src/surface/index.ts, toga25-supply/src/pages/Login/LoginPage.tsx, toga25-supply/src/pages/SalesOrders/SalesOrders.tsx, toga25-supply/src/pages/SalesOrders/view/SalesOrderRecordModalLayout/view/sections/SalesOrderTopBar.tsx, toga25-supply/src/pages/Items/ItemsPage.tsx, toga25-supply/src/pages/Items/viewModel/useItemsPageViewModel.tsx, toga25-supply/src/fieldsConfig/index.ts |
@@ -6,7 +6,7 @@ project: TOGa 2.5 Supply
6
6
  client: shared
7
7
  type: feature
8
8
  status: active
9
- updated: 2026-06-25
9
+ updated: 2026-06-29
10
10
  owners: [jcardinal]
11
11
  files:
12
12
  - toga25-supply/src/surface/useFetchSurfaceMeta.ts
@@ -16,11 +16,13 @@ files:
16
16
  - toga25-supply/src/surface/SurfaceSection.tsx
17
17
  - toga25-supply/src/surface/resolve.ts
18
18
  - toga25-supply/src/surface/types.ts
19
- - toga25-supply/src/surface/featureFlag.ts
20
19
  - toga25-supply/src/surface/index.ts
21
20
  - toga25-supply/src/pages/Login/LoginPage.tsx
22
21
  - toga25-supply/src/pages/SalesOrders/SalesOrders.tsx
23
22
  - toga25-supply/src/pages/SalesOrders/view/SalesOrderRecordModalLayout/view/sections/SalesOrderTopBar.tsx
23
+ - toga25-supply/src/pages/Items/ItemsPage.tsx
24
+ - toga25-supply/src/pages/Items/viewModel/useItemsPageViewModel.tsx
25
+ - toga25-supply/src/fieldsConfig/index.ts
24
26
  related:
25
27
  - ../../_underscore/features/surface-resolver.md
26
28
  - meta-driven-table-data.md
@@ -29,14 +31,17 @@ related:
29
31
  ## What it is
30
32
 
31
33
  The frontend consumer of the platform-wide Surface layer — DB-driven UI config fetched from
32
- `GET /v2/surfaces/{slug}/meta` instead of statically-imported JSON field bundles. Lives in
34
+ `GET /v2/surfaces/meta?slug=<slug>` instead of statically-imported JSON field bundles. Lives in
33
35
  `src/surface/` for now with a `TODO(blox)` to extract the generic machinery into `@agilant/toga-blox`
34
- so all 2.0 apps inherit it. Backend: [surface-resolver](../../_underscore/features/surface-resolver.md).
36
+ so all 2.0 apps inherit it. Surface is now the **enforced, unconditional presentation path** (no
37
+ feature flag — see below). Backend: [surface-resolver](../../_underscore/features/surface-resolver.md).
35
38
 
36
39
  ## How it works
37
40
 
38
41
  - **`useFetchSurfaceMeta`** — the single fetch-once/React-Query-cached choke point for a surface's
39
- meta. Loading a different *record* into the same surface reuses cached meta.
42
+ meta. Loading a different *record* into the same surface reuses cached meta. Builds the URL as
43
+ `/surfaces/meta?slug=...` (query string), **not** `/surfaces/<slug>/meta` (the path form makes the
44
+ engine parse the slug as a record uuid → 404 EV-6).
40
45
  - **`evaluateSurfaceRule`** — the Tier-1 rule evaluator: a **frozen `all/any/none` + `{field,op,value}`
41
46
  grammar** that **throws on an unknown op** (generalized from the SalesOrders
42
47
  `buildPatchedTenantFields`/`evaluateEnableRule`/`resolveFlag` helpers). Evaluated client-side against
@@ -47,25 +52,53 @@ so all 2.0 apps inherit it. Backend: [surface-resolver](../../_underscore/featur
47
52
  - **theme/message resolvers** (`resolve.ts`) — token → CSS and ICU message rendering.
48
53
  - **`SurfaceActionBar`/`SurfaceActions` + `SurfaceSection`** — generic renderers.
49
54
 
50
- ## What's wired (review proof)
55
+ ## Enforced, not flagged
51
56
 
52
- Login + sales-orders list + the sales-order modal, all behind the **`SURFACE_ENABLED`** flag (env
53
- `VITE_SURFACE_ENABLED`, **default OFF**). The SalesOrder action bar reproduces
54
- `orderViewFields.json` approve/deny/approvalWorkflow/viewLog/editOrder by delegating to the existing
55
- `onActionClick`/`onOpenLog` handlers. The table stays on the existing TableView pipeline via
56
- `tableViewSlug` (see [meta-driven-table-data](meta-driven-table-data.md)) — the Surface layer
57
- references the TableView, never replaces it.
57
+ Surface is the **unconditional** presentation path. The `VITE_SURFACE_ENABLED` / `SURFACE_ENABLED`
58
+ feature flag was **removed entirely** (`featureFlag.ts` deleted, all gating conditionals and the
59
+ legacy pre-Surface fallback branches at the wired screens removed). Legacy per-screen `FIELDS` config
60
+ + rendering are deleted as each screen migrates — there is no dual-path fallback.
61
+
62
+ ## What's wired
63
+
64
+ Login + sales-orders list + the sales-order modal + the **Items LIST** screen. The SalesOrder action
65
+ bar reproduces `orderViewFields.json` approve/deny/approvalWorkflow/viewLog/editOrder by delegating
66
+ to the existing `onActionClick`/`onOpenLog` handlers. TABLE-type surfaces stay on the existing
67
+ TableView pipeline via `tableViewSlug` (see [meta-driven-table-data](meta-driven-table-data.md)) —
68
+ the Surface layer references the TableView, never replaces it.
69
+
70
+ ## Per-screen migration recipe (proven on SalesOrders + Items)
71
+
72
+ The screen-by-screen full-app refactor follows one recipe:
73
+ 1. Seed the screen's surface(s) in a `Core/*SurfaceSeed.sql` — a TABLE surface that references the
74
+ screen's existing `TableView` id via `tableViewSlug`, plus a BUTTON_BAR/actions surface + its
75
+ Actions + Messages (e.g. Items: `items-list` TABLE → TableView id 9 / recordId 21, and
76
+ `items-list-actions` refresh/columns/newItem).
77
+ 2. Rewire the page + its view-model to consume the surface bundle (`ItemsPage.tsx` /
78
+ `useItemsPageViewModel.tsx`).
79
+ 3. **Delete** the legacy `FIELDS` config: the page's `*Fields.json` and its `fieldsConfig/index.ts`
80
+ entries.
81
+ Keep TABLE rendering on the existing TableView pipeline; only the chrome (actions/labels/visibility)
82
+ moves to Surface. A screen with no client/role/lang variance ships a single DEFAULT bundle (no
83
+ Client overrides), as Items did.
58
84
 
59
85
  ## Gotchas
60
86
 
61
- - **Default OFF.** Nothing renders through the Surface path unless `VITE_SURFACE_ENABLED` is set —
62
- the existing JSON-bundle path remains live until per-surface cutover.
87
+ - **No feature flag, no fallback.** Surface is the enforced path; a missing/broken surface bundle
88
+ does not silently fall back to legacy JSON — that path is deleted per screen as it migrates.
89
+ - **Endpoint is query-string form** — `GET /v2/surfaces/meta?slug=...`, not `/surfaces/<slug>/meta`
90
+ (the path form 404s EV-6).
63
91
  - **Tier-1 only on the client.** Business-logic gates (Tier-2) arrive as resolved booleans from the
64
92
  backend (`meta.surface`); never re-encode business rules in the FE evaluator.
65
93
  - **`tsc` not yet run** this session (the private `@agilant/toga-blox` registry needs npm creds);
66
94
  treat type-checking as pending. Runtime `GET /v2/surfaces/{slug}/meta` also not yet exercised.
67
95
 
68
96
  ## Change history
97
+ - 2026-06-29 — DECISION: Surface is now enforced, not flagged — removed `VITE_SURFACE_ENABLED`/
98
+ `SURFACE_ENABLED` (deleted `featureFlag.ts` + all gating/legacy-fallback branches). Fixed
99
+ `useFetchSurfaceMeta` to the query-string endpoint `/surfaces/meta?slug=` (path form 404'd EV-6).
100
+ Migrated the Items LIST screen (second proof of the per-screen recipe; deleted `itemsPageFields.json`
101
+ + its fieldsConfig entries). (jcardinal)
69
102
  - 2026-06-25 — Built `src/surface/` (fetch hook, frozen Tier-1 rule evaluator, action registry, theme/
70
103
  message resolvers, action-bar + section renderers); wired login + SalesOrders list + SO modal behind
71
104
  the OFF-by-default `SURFACE_ENABLED` flag. Generic machinery destined for toga-blox. (jcardinal)
@@ -24,7 +24,7 @@ _Auto-generated by `knowledge.js index`. Do not hand-edit._
24
24
  - **saml** (SAML SSO Gateway) — 3 doc(s) → [2.0/apps/saml/INDEX.md](2.0/apps/saml/INDEX.md)
25
25
  - **toga2-view** (TOGa View Frontend) — 4 doc(s) → [2.0/apps/toga2-view/INDEX.md](2.0/apps/toga2-view/INDEX.md)
26
26
  - **toga2-hub** (TOGa Hub) — 2 doc(s) → [2.0/apps/toga2-hub/INDEX.md](2.0/apps/toga2-hub/INDEX.md)
27
- - **talos** (TOGa IQ) — 6 doc(s) → [2.0/apps/talos/INDEX.md](2.0/apps/talos/INDEX.md)
27
+ - **talos** (TOGa IQ) — 7 doc(s) → [2.0/apps/talos/INDEX.md](2.0/apps/talos/INDEX.md)
28
28
  - **voice-to-voice** (TOGa Voice) — 4 doc(s) → [2.0/apps/voice-to-voice/INDEX.md](2.0/apps/voice-to-voice/INDEX.md)
29
29
  - **ai-bdr** (AI-BDR) — 4 doc(s) → [2.0/apps/ai-bdr/INDEX.md](2.0/apps/ai-bdr/INDEX.md)
30
30
  - **toga2-commerce** (TOGa Commerce) — 6 doc(s) → [2.0/apps/toga2-commerce/INDEX.md](2.0/apps/toga2-commerce/INDEX.md)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "toga-ai",
3
- "version": "1.0.222",
3
+ "version": "1.0.224",
4
4
  "description": "TOGA Technology Team Claude Knowledge System — shared AI coding harness with skills, knowledge base CLI, and project installer for Claude Code.",
5
5
  "keywords": [
6
6
  "claude",