commit
d1b0ddee4e
@ -6,6 +6,13 @@ All notable changes to this project will be documented in this file.
|
|||||||
|
|
||||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
|
||||||
|
|
||||||
|
## [0.18.1] - 2026-02-18
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- Added multi-tenant data isolation section and authorization-resolver consistency code examples to security knowledge
|
||||||
|
- Added "prefer project scripts" rule to coding policy — detects direct tool invocation (e.g., `npx vitest`) when equivalent npm scripts exist
|
||||||
|
|
||||||
## [0.18.0] - 2026-02-17
|
## [0.18.0] - 2026-02-17
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|||||||
@ -148,6 +148,61 @@ if (!safePath.startsWith(path.resolve(baseDir))) {
|
|||||||
- Resource exhaustion attack possibility → Warning
|
- Resource exhaustion attack possibility → Warning
|
||||||
- Infinite loop possibility → REJECT
|
- Infinite loop possibility → REJECT
|
||||||
|
|
||||||
|
## Multi-Tenant Data Isolation
|
||||||
|
|
||||||
|
Prevent data access across tenant boundaries. Authorization (who can operate) and scoping (which tenant's data) are separate concerns.
|
||||||
|
|
||||||
|
| Criteria | Verdict |
|
||||||
|
|----------|---------|
|
||||||
|
| Reads are tenant-scoped but writes are not | REJECT |
|
||||||
|
| Write operations use client-provided tenant ID | REJECT |
|
||||||
|
| Endpoint using tenant resolver has no authorization control | REJECT |
|
||||||
|
| Some paths in role-based branching don't account for tenant resolution | REJECT |
|
||||||
|
|
||||||
|
### Read-Write Consistency
|
||||||
|
|
||||||
|
Apply tenant scoping to both reads and writes. Scoping only one side creates a state where data cannot be viewed but can be modified.
|
||||||
|
|
||||||
|
When adding a tenant filter to reads, always add tenant verification to corresponding writes.
|
||||||
|
|
||||||
|
### Write-Side Tenant Verification
|
||||||
|
|
||||||
|
For write operations, use the tenant ID resolved from the authenticated user, not from the request body.
|
||||||
|
|
||||||
|
```kotlin
|
||||||
|
// NG - Trusting client-provided tenant ID
|
||||||
|
fun create(request: CreateRequest) {
|
||||||
|
service.create(request.tenantId, request.data)
|
||||||
|
}
|
||||||
|
|
||||||
|
// OK - Resolve tenant from authentication
|
||||||
|
fun create(request: CreateRequest) {
|
||||||
|
val tenantId = tenantResolver.resolve()
|
||||||
|
service.create(tenantId, request.data)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Authorization-Resolver Alignment
|
||||||
|
|
||||||
|
When a tenant resolver assumes a specific role (e.g., staff), the endpoint must have corresponding authorization controls. Without authorization, unexpected roles can access the endpoint and cause the resolver to fail.
|
||||||
|
|
||||||
|
```kotlin
|
||||||
|
// NG - Resolver assumes STAFF but no authorization control
|
||||||
|
fun getSettings(): SettingsResponse {
|
||||||
|
val tenantId = tenantResolver.resolve() // Fails for non-STAFF
|
||||||
|
return settingsService.getByTenant(tenantId)
|
||||||
|
}
|
||||||
|
|
||||||
|
// OK - Authorization ensures correct role
|
||||||
|
@Authorized(roles = ["STAFF"])
|
||||||
|
fun getSettings(): SettingsResponse {
|
||||||
|
val tenantId = tenantResolver.resolve()
|
||||||
|
return settingsService.getByTenant(tenantId)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
For endpoints with role-based branching, verify that tenant resolution succeeds on all paths.
|
||||||
|
|
||||||
## OWASP Top 10 Checklist
|
## OWASP Top 10 Checklist
|
||||||
|
|
||||||
| Category | Check Items |
|
| Category | Check Items |
|
||||||
|
|||||||
@ -13,6 +13,7 @@ Prioritize correctness over speed, and code accuracy over ease of implementation
|
|||||||
| File size | ~300 lines as a guideline. Be flexible depending on the task |
|
| File size | ~300 lines as a guideline. Be flexible depending on the task |
|
||||||
| Boy Scout | Leave touched areas a little better than you found them |
|
| Boy Scout | Leave touched areas a little better than you found them |
|
||||||
| Fail Fast | Detect errors early. Never swallow them |
|
| Fail Fast | Detect errors early. Never swallow them |
|
||||||
|
| Project scripts first | Use project-defined scripts for tool execution. Direct invocation is a last resort |
|
||||||
|
|
||||||
## No Fallbacks or Default Arguments
|
## No Fallbacks or Default Arguments
|
||||||
|
|
||||||
@ -288,3 +289,4 @@ function formatPercentage(value: number): string { ... }
|
|||||||
- **Internal implementation exported from public API** - Only export domain-level functions and types. Do not export infrastructure functions or internal classes
|
- **Internal implementation exported from public API** - Only export domain-level functions and types. Do not export infrastructure functions or internal classes
|
||||||
- **Replaced code surviving after refactoring** - Remove replaced code and exports. Do not keep unless explicitly told to
|
- **Replaced code surviving after refactoring** - Remove replaced code and exports. Do not keep unless explicitly told to
|
||||||
- **Workarounds that bypass safety mechanisms** - If the root fix is correct, no additional bypass is needed
|
- **Workarounds that bypass safety mechanisms** - If the root fix is correct, no additional bypass is needed
|
||||||
|
- **Direct tool execution bypassing project scripts** - `npx tool` and similar bypass the lockfile, causing version mismatches. Look for project-defined scripts (npm scripts, Makefile, etc.) first. Only consider direct execution when no script exists
|
||||||
|
|||||||
@ -148,6 +148,61 @@ if (!safePath.startsWith(path.resolve(baseDir))) {
|
|||||||
- リソース枯渇攻撃の可能性 → 警告
|
- リソース枯渇攻撃の可能性 → 警告
|
||||||
- 無限ループの可能性 → REJECT
|
- 無限ループの可能性 → REJECT
|
||||||
|
|
||||||
|
## マルチテナントデータ分離
|
||||||
|
|
||||||
|
テナント境界を超えたデータアクセスを防ぐ。認可(誰が操作できるか)とスコーピング(どのテナントのデータか)は別の関心事。
|
||||||
|
|
||||||
|
| 基準 | 判定 |
|
||||||
|
|------|------|
|
||||||
|
| 読み取りはテナントスコープだが書き込みはスコープなし | REJECT |
|
||||||
|
| 書き込み操作でクライアント提供のテナントIDを使用 | REJECT |
|
||||||
|
| テナントリゾルバーを使うエンドポイントに認可制御がない | REJECT |
|
||||||
|
| ロール分岐の一部パスでテナント解決が未考慮 | REJECT |
|
||||||
|
|
||||||
|
### 読み書きの一貫性
|
||||||
|
|
||||||
|
テナントスコーピングは読み取りと書き込みの両方に適用する。片方だけでは、参照できないが変更できる状態が生まれる。
|
||||||
|
|
||||||
|
読み取りにテナントフィルタを追加したら、対応する書き込みも必ずテナント検証する。
|
||||||
|
|
||||||
|
### 書き込みのテナント検証
|
||||||
|
|
||||||
|
書き込み操作では、リクエストボディのテナントIDではなく認証済みユーザーから解決したテナントIDを使う。
|
||||||
|
|
||||||
|
```kotlin
|
||||||
|
// NG - クライアント提供のテナントIDを信頼
|
||||||
|
fun create(request: CreateRequest) {
|
||||||
|
service.create(request.tenantId, request.data)
|
||||||
|
}
|
||||||
|
|
||||||
|
// OK - 認証情報からテナントを解決
|
||||||
|
fun create(request: CreateRequest) {
|
||||||
|
val tenantId = tenantResolver.resolve()
|
||||||
|
service.create(tenantId, request.data)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 認可とリゾルバーの整合性
|
||||||
|
|
||||||
|
テナントリゾルバーが特定ロール(例: スタッフ)を前提とする場合、エンドポイントに対応する認可制御が必要。認可なしだと、前提外のロールがアクセスしてリゾルバーが失敗する。
|
||||||
|
|
||||||
|
```kotlin
|
||||||
|
// NG - リゾルバーが STAFF を前提とするが認可制御なし
|
||||||
|
fun getSettings(): SettingsResponse {
|
||||||
|
val tenantId = tenantResolver.resolve() // STAFF 以外で失敗
|
||||||
|
return settingsService.getByTenant(tenantId)
|
||||||
|
}
|
||||||
|
|
||||||
|
// OK - 認可制御でロールを保証
|
||||||
|
@Authorized(roles = ["STAFF"])
|
||||||
|
fun getSettings(): SettingsResponse {
|
||||||
|
val tenantId = tenantResolver.resolve()
|
||||||
|
return settingsService.getByTenant(tenantId)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
ロール分岐があるエンドポイントでは、全パスでテナント解決が成功するか検証する。
|
||||||
|
|
||||||
## OWASP Top 10 チェックリスト
|
## OWASP Top 10 チェックリスト
|
||||||
|
|
||||||
| カテゴリ | 確認事項 |
|
| カテゴリ | 確認事項 |
|
||||||
|
|||||||
@ -13,6 +13,7 @@
|
|||||||
| ファイルサイズ | 目安として300行。タスクに応じて柔軟に |
|
| ファイルサイズ | 目安として300行。タスクに応じて柔軟に |
|
||||||
| ボーイスカウト | 触った箇所は少し改善して去る |
|
| ボーイスカウト | 触った箇所は少し改善して去る |
|
||||||
| Fail Fast | エラーは早期に検出。握りつぶさない |
|
| Fail Fast | エラーは早期に検出。握りつぶさない |
|
||||||
|
| プロジェクトスクリプト優先 | ツール実行はプロジェクト定義のスクリプトを使う。直接実行は最後の手段 |
|
||||||
|
|
||||||
## フォールバック・デフォルト引数の禁止
|
## フォールバック・デフォルト引数の禁止
|
||||||
|
|
||||||
@ -288,3 +289,4 @@ function formatPercentage(value: number): string { ... }
|
|||||||
- **内部実装のパブリック API エクスポート** - 公開するのはドメイン操作の関数・型のみ。インフラ層の関数や内部クラスをエクスポートしない
|
- **内部実装のパブリック API エクスポート** - 公開するのはドメイン操作の関数・型のみ。インフラ層の関数や内部クラスをエクスポートしない
|
||||||
- **リファクタリング後の旧コード残存** - 置き換えたコード・エクスポートは削除する。明示的に残すよう指示されない限り残さない
|
- **リファクタリング後の旧コード残存** - 置き換えたコード・エクスポートは削除する。明示的に残すよう指示されない限り残さない
|
||||||
- **安全機構を迂回するワークアラウンド** - 根本修正が正しいなら追加の迂回は不要
|
- **安全機構を迂回するワークアラウンド** - 根本修正が正しいなら追加の迂回は不要
|
||||||
|
- **プロジェクトスクリプトを迂回するツール直接実行** - `npx tool` 等の直接実行は lockfile を迂回しバージョン不一致を起こす。プロジェクトが定義したスクリプト(npm scripts, Makefile 等)を探して使う。見つからない場合のみ直接実行を検討する
|
||||||
|
|||||||
@ -6,6 +6,13 @@
|
|||||||
|
|
||||||
フォーマットは [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) に基づいています。
|
フォーマットは [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) に基づいています。
|
||||||
|
|
||||||
|
## [0.18.1] - 2026-02-18
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- セキュリティナレッジにマルチテナントデータ分離セクションと認可・リゾルバー整合性のコード例を追加
|
||||||
|
- コーディングポリシーに「プロジェクトスクリプト優先」ルールを追加 — npm スクリプトが存在するのに直接ツール呼び出し(例: `npx vitest`)を検出
|
||||||
|
|
||||||
## [0.18.0] - 2026-02-17
|
## [0.18.0] - 2026-02-17
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|||||||
139
package-lock.json
generated
139
package-lock.json
generated
@ -1,16 +1,16 @@
|
|||||||
{
|
{
|
||||||
"name": "takt",
|
"name": "takt",
|
||||||
"version": "0.18.0",
|
"version": "0.18.1",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "takt",
|
"name": "takt",
|
||||||
"version": "0.18.0",
|
"version": "0.18.1",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@anthropic-ai/claude-agent-sdk": "^0.2.37",
|
"@anthropic-ai/claude-agent-sdk": "^0.2.37",
|
||||||
"@openai/codex-sdk": "^0.98.0",
|
"@openai/codex-sdk": "^0.103.0",
|
||||||
"@opencode-ai/sdk": "^1.1.53",
|
"@opencode-ai/sdk": "^1.1.53",
|
||||||
"chalk": "^5.3.0",
|
"chalk": "^5.3.0",
|
||||||
"commander": "^12.1.0",
|
"commander": "^12.1.0",
|
||||||
@ -928,15 +928,140 @@
|
|||||||
"integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==",
|
"integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"node_modules/@openai/codex-sdk": {
|
"node_modules/@openai/codex": {
|
||||||
"version": "0.98.0",
|
"version": "0.103.0",
|
||||||
"resolved": "https://registry.npmjs.org/@openai/codex-sdk/-/codex-sdk-0.98.0.tgz",
|
"resolved": "https://registry.npmjs.org/@openai/codex/-/codex-0.103.0.tgz",
|
||||||
"integrity": "sha512-TbPgrBpuSNMJyOXys0HNsh6UoP5VIHu1fVh2KDdACi5XyB0vuPtzBZC+qOsxHz7WXEQPFlomPLyxS6JnE5Okmg==",
|
"integrity": "sha512-kf7sytd/2mMUKYK8eKgNrwujgNjWrfWFMFfRCOVIFBeByQXmtxe2giVEo44paKDapaS7dQnxqwcUY2OOYrdfWA==",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
|
"bin": {
|
||||||
|
"codex": "bin/codex.js"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=16"
|
||||||
|
},
|
||||||
|
"optionalDependencies": {
|
||||||
|
"@openai/codex-darwin-arm64": "npm:@openai/codex@0.103.0-darwin-arm64",
|
||||||
|
"@openai/codex-darwin-x64": "npm:@openai/codex@0.103.0-darwin-x64",
|
||||||
|
"@openai/codex-linux-arm64": "npm:@openai/codex@0.103.0-linux-arm64",
|
||||||
|
"@openai/codex-linux-x64": "npm:@openai/codex@0.103.0-linux-x64",
|
||||||
|
"@openai/codex-win32-arm64": "npm:@openai/codex@0.103.0-win32-arm64",
|
||||||
|
"@openai/codex-win32-x64": "npm:@openai/codex@0.103.0-win32-x64"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@openai/codex-darwin-arm64": {
|
||||||
|
"name": "@openai/codex",
|
||||||
|
"version": "0.103.0-darwin-arm64",
|
||||||
|
"resolved": "https://registry.npmjs.org/@openai/codex/-/codex-0.103.0-darwin-arm64.tgz",
|
||||||
|
"integrity": "sha512-TGMMiB/A8CxG7ghhimEBUCk8nTEXef9cCSh+wUuq3dizAS/PuuHHf6m8LUeZCtkl/XMUxxDqEv3+i/jcB0Jo4A==",
|
||||||
|
"cpu": [
|
||||||
|
"arm64"
|
||||||
|
],
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"darwin"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=16"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@openai/codex-darwin-x64": {
|
||||||
|
"name": "@openai/codex",
|
||||||
|
"version": "0.103.0-darwin-x64",
|
||||||
|
"resolved": "https://registry.npmjs.org/@openai/codex/-/codex-0.103.0-darwin-x64.tgz",
|
||||||
|
"integrity": "sha512-1aJnXu6dYsu25fBX+76I150gh1BaKoni9wq6QWEPQ3x+FDqsCgGWG5G6G0v4uVMvTgXB0A6IhwPj88ZYi0BsWw==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"darwin"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=16"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@openai/codex-linux-arm64": {
|
||||||
|
"name": "@openai/codex",
|
||||||
|
"version": "0.103.0-linux-arm64",
|
||||||
|
"resolved": "https://registry.npmjs.org/@openai/codex/-/codex-0.103.0-linux-arm64.tgz",
|
||||||
|
"integrity": "sha512-LdrBhfhV2ZiJBof5h1zxKgmEdt2vm5m8/xEtZShiqSDkgfsTUHNoOAb0WmgITF8+4kyWZtpMjBLw3XjkmNYQJw==",
|
||||||
|
"cpu": [
|
||||||
|
"arm64"
|
||||||
|
],
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=16"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@openai/codex-linux-x64": {
|
||||||
|
"name": "@openai/codex",
|
||||||
|
"version": "0.103.0-linux-x64",
|
||||||
|
"resolved": "https://registry.npmjs.org/@openai/codex/-/codex-0.103.0-linux-x64.tgz",
|
||||||
|
"integrity": "sha512-wRjcldkssdCUFQ/YoHRAbgQhszhGyt14Qa/V5ouQp4i3j5dPItVwl/y64u2Fe6fc3OAJkw6W3MJCSO1F323y3g==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=16"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@openai/codex-sdk": {
|
||||||
|
"version": "0.103.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@openai/codex-sdk/-/codex-sdk-0.103.0.tgz",
|
||||||
|
"integrity": "sha512-wMuYUIs5ajZsE2mnacYQKubt61cykMCAyNhPHBINpf9AJkiuvpAHtuz7npXzk9z4wmfqXPMXIQ3x/v1QMmd0cQ==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"dependencies": {
|
||||||
|
"@openai/codex": "0.103.0"
|
||||||
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=18"
|
"node": ">=18"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@openai/codex-win32-arm64": {
|
||||||
|
"name": "@openai/codex",
|
||||||
|
"version": "0.103.0-win32-arm64",
|
||||||
|
"resolved": "https://registry.npmjs.org/@openai/codex/-/codex-0.103.0-win32-arm64.tgz",
|
||||||
|
"integrity": "sha512-d5yXLt9m4/3Wf3hiUcygr7voPXgLrvTgbtoYiAqZl/rtoNVRhChjt09EJKRlFQusq3aUGyQduqTLND2uF5dwBA==",
|
||||||
|
"cpu": [
|
||||||
|
"arm64"
|
||||||
|
],
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"win32"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=16"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@openai/codex-win32-x64": {
|
||||||
|
"name": "@openai/codex",
|
||||||
|
"version": "0.103.0-win32-x64",
|
||||||
|
"resolved": "https://registry.npmjs.org/@openai/codex/-/codex-0.103.0-win32-x64.tgz",
|
||||||
|
"integrity": "sha512-WtR5y3KeaGjLeYWQnRhMYBptm+eRMvwVlHJpIlFSw7DzD1idvL6ALZ4+kBL1rXBZMJzzpXomax7vMNTlXFjCOg==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"win32"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=16"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@opencode-ai/sdk": {
|
"node_modules/@opencode-ai/sdk": {
|
||||||
"version": "1.1.53",
|
"version": "1.1.53",
|
||||||
"resolved": "https://registry.npmjs.org/@opencode-ai/sdk/-/sdk-1.1.53.tgz",
|
"resolved": "https://registry.npmjs.org/@opencode-ai/sdk/-/sdk-1.1.53.tgz",
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "takt",
|
"name": "takt",
|
||||||
"version": "0.18.0",
|
"version": "0.18.1",
|
||||||
"description": "TAKT: TAKT Agent Koordination Topology - AI Agent Piece Orchestration",
|
"description": "TAKT: TAKT Agent Koordination Topology - AI Agent Piece Orchestration",
|
||||||
"main": "dist/index.js",
|
"main": "dist/index.js",
|
||||||
"types": "dist/index.d.ts",
|
"types": "dist/index.d.ts",
|
||||||
@ -60,7 +60,7 @@
|
|||||||
],
|
],
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@anthropic-ai/claude-agent-sdk": "^0.2.37",
|
"@anthropic-ai/claude-agent-sdk": "^0.2.37",
|
||||||
"@openai/codex-sdk": "^0.98.0",
|
"@openai/codex-sdk": "^0.103.0",
|
||||||
"@opencode-ai/sdk": "^1.1.53",
|
"@opencode-ai/sdk": "^1.1.53",
|
||||||
"chalk": "^5.3.0",
|
"chalk": "^5.3.0",
|
||||||
"commander": "^12.1.0",
|
"commander": "^12.1.0",
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user