fieldsmith 0.1.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.
Files changed (47) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +843 -0
  3. package/assets/fonts/DotGothic16.ttf +0 -0
  4. package/assets/fonts/OFL.txt +93 -0
  5. package/dist/cli/exit.js +37 -0
  6. package/dist/cli/exit.js.map +1 -0
  7. package/dist/cli/index.js +699 -0
  8. package/dist/cli/index.js.map +1 -0
  9. package/dist/cli/output.js +61 -0
  10. package/dist/cli/output.js.map +1 -0
  11. package/dist/cli/progress.js +83 -0
  12. package/dist/cli/progress.js.map +1 -0
  13. package/dist/cli/promptInput.js +57 -0
  14. package/dist/cli/promptInput.js.map +1 -0
  15. package/dist/cli/schema.js +140 -0
  16. package/dist/cli/schema.js.map +1 -0
  17. package/dist/config.js +149 -0
  18. package/dist/config.js.map +1 -0
  19. package/dist/icon/emojiFont.js +163 -0
  20. package/dist/icon/emojiFont.js.map +1 -0
  21. package/dist/icon/render.js +136 -0
  22. package/dist/icon/render.js.map +1 -0
  23. package/dist/kintone/client.js +157 -0
  24. package/dist/kintone/client.js.map +1 -0
  25. package/dist/kintone/deploy.js +519 -0
  26. package/dist/kintone/deploy.js.map +1 -0
  27. package/dist/kintone/oauth.js +156 -0
  28. package/dist/kintone/oauth.js.map +1 -0
  29. package/dist/kintone/tokenStore.js +87 -0
  30. package/dist/kintone/tokenStore.js.map +1 -0
  31. package/dist/llm/generate.js +317 -0
  32. package/dist/llm/generate.js.map +1 -0
  33. package/dist/llm/prompt.js +142 -0
  34. package/dist/llm/prompt.js.map +1 -0
  35. package/dist/spec/appSpec.js +283 -0
  36. package/dist/spec/appSpec.js.map +1 -0
  37. package/dist/spec/diff.js +218 -0
  38. package/dist/spec/diff.js.map +1 -0
  39. package/dist/spec/fieldSpec.js +353 -0
  40. package/dist/spec/fieldSpec.js.map +1 -0
  41. package/dist/spec/fromKintone.js +230 -0
  42. package/dist/spec/fromKintone.js.map +1 -0
  43. package/dist/spec/layout.js +215 -0
  44. package/dist/spec/layout.js.map +1 -0
  45. package/dist/spec/toKintone.js +170 -0
  46. package/dist/spec/toKintone.js.map +1 -0
  47. package/package.json +59 -0
package/README.md ADDED
@@ -0,0 +1,843 @@
1
+ # fieldsmith
2
+
3
+ **AppSpec(JSON)1 つで、kintone アプリを何度でも、いくつでも作れる CLI。**
4
+
5
+ ```bash
6
+ fieldsmith deploy bookshelf.json # 作る
7
+ fieldsmith deploy bookshelf.json --space 12 # 同じものを、別のスペースにもう 1 つ
8
+ KINTONE_BASE_URL=https://other.cybozu.com \
9
+ fieldsmith deploy bookshelf.json # 同じものを、別のドメインにも
10
+ ```
11
+
12
+ アプリの形をファイルとして持てるので、レビューでき、差分が見え、
13
+ 検証環境と本番で同じものを作れる。手で 18 個のフィールドを並べ直す作業が消える。
14
+
15
+ **その AppSpec は、要件を書いた文章からも作れる。**
16
+
17
+ ```bash
18
+ fieldsmith plan -f requirements.md -o bookshelf.json
19
+ ```
20
+
21
+ こちらは付加価値であって、中心ではない。生成された AppSpec は普通の JSON なので、
22
+ 気に入らなければ手で直せばよいし、最初から手で書いてもよい。
23
+
24
+ ```
25
+ 要件 (文章) ──[plan / LLM]──> AppSpec (JSON) ──[deploy]──> kintone
26
+ ↑
27
+ 手で書いても、手で直してもよい
28
+ ```
29
+
30
+ ### deploy は LLM を呼ばない
31
+
32
+ | コマンド | kintone | Claude |
33
+ |---|---|---|
34
+ | `deploy` `status` | 触る | **呼ばない** |
35
+ | `plan` | 触らない | 呼ぶ |
36
+ | `create` | 触る | 呼ぶ(= `plan` + `deploy`) |
37
+
38
+ 同じ AppSpec からは常に同じ API 呼び出しが出る。意味によるフィールドのまとめ方(`group`)
39
+ のような判断も、`plan` の時点で AppSpec に書き込まれた**データ**として渡るので、
40
+ デプロイ側は判断せずそれを使うだけ。
41
+
42
+ この境界は `tests/architecture.test.ts` で固定している。
43
+
44
+ - `src/kintone` `src/spec` `src/icon` が `src/llm` や `@anthropic-ai/sdk` に依存しないこと
45
+ - 逆に `src/llm` が `src/kintone` に依存しないこと(両者は CLI が繋ぐ)
46
+ - デプロイ経路に乱数が無いこと
47
+ - 同じ AppSpec を 2 回デプロイすると、同じ API 呼び出しの列になること
48
+
49
+ なお、アプリ ID・リビジョン・`fileKey` は kintone が採番するので当然変わる。
50
+ 決定的なのは「fieldsmith が何を送るか」であって、サーバーが返す値ではない。
51
+
52
+ ### 毎回「新しいアプリ」を作る
53
+
54
+ `deploy` は実行のたびに新しいアプリを作る。**既存アプリの更新はしない。**
55
+ 同じ spec を 2 回流せば、同じ形のアプリが 2 つできる。
56
+
57
+ これは「テンプレートから量産する」用途に向く。
58
+ 既存アプリに変更を反映したい場合は `update` を使う。
59
+
60
+ ## お試し手順
61
+
62
+ 全機能を一通り動かす手順書がある。副作用の小さい順(接続なし → kintone → Claude API)に
63
+ 並べてあるので、途中でやめてもよい。
64
+
65
+ 1. セットアップ: **[パスワード認証](docs/setup-password.md)** か **[OAuth](docs/setup-oauth.md)**
66
+ 2. **[お試し手順書](docs/walkthrough.md)**(どちらの認証でも内容は同じ)
67
+
68
+ ## 使い方
69
+
70
+ ```bash
71
+ # 1. kintone に認可する(初回のみ)
72
+ npm run fieldsmith -- login
73
+
74
+ # 2. 送信予定の内容を確認する(kintone には触らない)
75
+ npm run fieldsmith -- deploy bookshelf.json --dry-run
76
+
77
+ # 3. デプロイする
78
+ npm run fieldsmith -- deploy bookshelf.json
79
+
80
+ # 反映状況を確認する
81
+ npm run fieldsmith -- status 752
82
+ ```
83
+
84
+ AppSpec を文章から作る場合:
85
+
86
+ ```bash
87
+ # 設計だけ作る(kintone には接続しない)
88
+ npm run fieldsmith -- plan -f requirements.md -o bookshelf.json
89
+
90
+ # 生成からデプロイまで一気に(デプロイ前に確認が入る)
91
+ npm run fieldsmith -- create -f requirements.md
92
+ ```
93
+
94
+ | コマンド | 説明 |
95
+ |---|---|
96
+ | `deploy <spec.json>` | AppSpec を kintone にデプロイする |
97
+ | `pull <appId>` | 既存アプリを AppSpec として取り出す(読み取りのみ)|
98
+ | `diff <appId> <spec.json>` | 既存アプリと AppSpec の差分を表示する(読み取りのみ)|
99
+ | `update <appId> <spec.json>` | 既存アプリを AppSpec の内容に近づける |
100
+ | `revise <appId> [指示]` | 指示に沿って書き換えた AppSpec を作る(要 Claude API)|
101
+ | `status <appId>` | 運用環境への反映状況を確認する |
102
+ | `login` / `logout` | kintone の OAuth トークンの取得・破棄 |
103
+ | `schema` | AppSpec の書き方を出力する(AI エージェント向け)|
104
+ | `plan [prompt]` | 要件から AppSpec を生成する。`-o` で保存 |
105
+ | `create [prompt]` | 生成 → 確認 → デプロイ |
106
+
107
+ 主なオプション: `--dry-run` / `--space <id>` / `--thread <id>` / `--guest-space <id>` /
108
+ `--revert-on-failure` / `--model <model>` / `-f` / `-o` / `-y` / `-v` / `--json`
109
+
110
+ ## セットアップ
111
+
112
+ ```bash
113
+ npm install
114
+ cp .env.example .env # 値を埋める
115
+ ```
116
+
117
+ 必要な認証情報は用途で分かれる。**`deploy` だけ使うなら kintone 側だけでよい。**
118
+
119
+ | 用途 | 設定するもの |
120
+ |---|---|
121
+ | `deploy` `pull` `diff` `update` `status` | kintone の認証情報(下記) |
122
+ | `plan` `create` `revise` | 上記 + `ANTHROPIC_API_KEY` または `ant auth login` |
123
+
124
+ ### kintone の認証は 2 通りから選ぶ
125
+
126
+ | | パスワード認証 | OAuth |
127
+ |---|---|---|
128
+ | 事前の登録 | **不要** | OAuth クライアントの登録が要る |
129
+ | `fieldsmith login` | **不要** | 必要(ブラウザで認可)|
130
+ | 設定に置くもの | ログイン名とパスワード | クライアント ID / シークレット |
131
+ | パスワードの保存 | **`.env` に置く** | 置かない(トークンのみ)|
132
+ | CI などの非対話環境 | そのまま動く | 事前に認可してトークンを配る必要がある |
133
+
134
+ **手早く試すならパスワード認証**、パスワードを設定に置きたくないなら OAuth。
135
+ どちらでもアプリの作成・更新に必要な API は使える。
136
+
137
+ ```bash
138
+ # パスワード認証
139
+ KINTONE_BASE_URL=https://example.cybozu.com
140
+ KINTONE_USERNAME=taro
141
+ KINTONE_PASSWORD=...
142
+ ```
143
+
144
+ ```bash
145
+ # OAuth(値は登録時に払い出される)
146
+ KINTONE_BASE_URL=https://example.cybozu.com
147
+ KINTONE_OAUTH_CLIENT_ID=...
148
+ KINTONE_OAUTH_CLIENT_SECRET=...
149
+ KINTONE_OAUTH_REDIRECT_URI=https://example.com/callback
150
+ KINTONE_OAUTH_AUTHORIZATION_ENDPOINT=...
151
+ KINTONE_OAUTH_TOKEN_ENDPOINT=...
152
+ ```
153
+
154
+ 両方あるときはパスワード認証を使う。`KINTONE_AUTH=oauth` で明示的に選べる。
155
+ いま何で繋いでいるかは `--verbose` で分かる。
156
+
157
+ いずれの場合も、実行するユーザーに **「アプリの作成」権限**が必要。
158
+
159
+ ### kintone 側の準備
160
+
161
+ 1. cybozu.com 共通管理 → 外部サービス連携 → **OAuth クライアント** でクライアントを登録する。
162
+ リダイレクトエンドポイントには、認可後に転送される URL を指定する
163
+ (fieldsmith は転送先の URL を手で貼り付ける方式なので、実際にページが存在しなくても構わない)。
164
+ 2. 登録すると **クライアント ID / シークレット / 認可エンドポイント URL / トークンエンドポイント URL**
165
+ が払い出されるので、そのまま `.env` に書く。
166
+ 3. 実行するユーザーに **「アプリの作成」権限** が必要。
167
+
168
+ OAuth を使う場合、必要なスコープは次の 3 つ。レコードには触れないので、レコード系のスコープは要求しない。
169
+
170
+ | スコープ | 用途 |
171
+ |---|---|
172
+ | `k:app_settings:write` | アプリ作成、フィールド追加、一覧・一般設定の変更、運用環境への反映 |
173
+ | `k:app_settings:read` | **反映状況の確認**(`GET /k/v1/preview/app/deploy.json`) |
174
+ | `k:file:write` | **アプリアイコンの画像アップロード**(`POST /k/v1/file.json`) |
175
+
176
+ `read` を忘れると、書き込みがすべて成功したあと最後の状況確認だけが
177
+ `403 CB_OA01 Cannot access protected resource` で落ちる。
178
+ アプリは動作テスト環境に作られたまま残るという分かりにくい壊れ方をするので、
179
+ fieldsmith は保存済みトークンのスコープを起動時に検査し、足りなければ 1 リクエストも投げずに停止する。
180
+
181
+ > アプリの新規作成 API(`POST /k/v1/preview/app.json`)は **API トークンでは実行できない**。
182
+ > そのため fieldsmith は OAuth 認証を使う。
183
+
184
+ ### 認可
185
+
186
+ ```bash
187
+ npm run fieldsmith -- login
188
+ ```
189
+
190
+ 表示された URL をブラウザで開いて許可し、転送先のアドレスバーの URL をそのまま貼り付ける。
191
+ アクセストークンは 1 時間で失効するが、リフレッシュトークン(無期限)を
192
+ `~/.config/fieldsmith/tokens.json`(パーミッション 0600)に保存するので、以降の再ログインは基本的に不要。
193
+
194
+ ## AppSpec — アプリの設計図
195
+
196
+ ```jsonc
197
+ {
198
+ "name": "案件管理", // 64 文字以内
199
+ "description": "営業案件を管理します", // 10,000 文字以内
200
+ "theme": "BLUE",
201
+ "fields": [ // 配列の順序がフォームの配置順になる
202
+ { "type": "SINGLE_LINE_TEXT", "label": "案件名", "required": true },
203
+ { "type": "NUMBER", "label": "金額", "digit": true, "unit": "円", "unitPosition": "AFTER" },
204
+ { "type": "DROP_DOWN", "label": "受注確度", "options": ["高", "中", "低"], "defaultValue": "中" },
205
+ { "type": "USER_SELECT", "label": "担当者" },
206
+ { "type": "DATE", "label": "受注予定日" }
207
+ ],
208
+ "views": [
209
+ { "name": "全件", "type": "LIST", "fields": ["案件名", "金額"], "sort": "受注予定日 asc" }
210
+ ],
211
+ "settings": { "titleFieldCode": "案件名", "enableComments": true }
212
+ }
213
+ ```
214
+
215
+ - **選択肢は文字列の配列**で書く。kintone が要求する `{ ラベル: { label, index } }` への変換は fieldsmith が行う。
216
+ - `icon` に絵文字か頭文字を書くと、**アイコン画像を生成してアップロードし**、アプリアイコンに設定する。
217
+ - `code`(フィールドコード)は省略可。省略すると `label` から導出する。
218
+ - `STATUS` / `ASSIGNEE` / `CATEGORY` などフィールド追加 API で追加できない型は、
219
+ **kintone に 1 リクエストも投げる前に**理由付きで弾く。
220
+ - 一覧や `titleFieldCode` から参照するフィールドコードの存在も、事前に検証する。
221
+
222
+ ## スペースへの配置
223
+
224
+ アプリを特定のスペースに作れる。CLI の `--space` が AppSpec の `space` より優先される。
225
+
226
+ ```bash
227
+ npm run fieldsmith -- deploy spec.json --space 12
228
+ ```
229
+
230
+ ```jsonc
231
+ {
232
+ "name": "案件管理",
233
+ "space": 12, // スペース ID
234
+ "thread": 34, // 省略可。kintone がスレッドの指定を求める場合のみ
235
+ "guestSpaceId": 7 // ゲストスペースの場合のみ
236
+ }
237
+ ```
238
+
239
+ 省略するとスペースに属さないアプリになる。
240
+
241
+ ### スペース指定時は REST API を直接叩いている
242
+
243
+ `@kintone/rest-api-client` の `addApp()` は、スペース指定時にデフォルトスレッドを
244
+ 調べるため `GET /k/v1/space.json` を呼ぶ。ところが **この API は OAuth 認証に対応していない**
245
+ (パスワード認証とセッション認証のみ)ため、OAuth で動く fieldsmith からは使えない。
246
+
247
+ そこでスペース指定時だけ `POST /k/v1/preview/app.json` を直接叩き、`space` を渡している
248
+ (`src/kintone/deploy.ts` の `createPreviewApp`)。トークンの更新は通常経路と同じ仕組みに乗る。
249
+
250
+ kintone がスレッドの指定を求めた場合は `--thread` を使う。スレッド ID はスペースを
251
+ 開いたときの URL から読める: `/k/#/space/{スペースID}/thread/{スレッドID}`
252
+
253
+ ## フォームのレイアウト
254
+
255
+ 似た系統のフィールドを横に並べる。既定で有効。
256
+
257
+ ```jsonc
258
+ {
259
+ "layout": "grouped", // 既定。省略してもこの動作
260
+ "layout": { "mode": "grouped", "maxPerRow": 2 }, // 1 行あたりの上限を変える (既定 3)
261
+ "layout": "stacked" // レイアウトに触らない (1 行 1 フィールド)
262
+ }
263
+ ```
264
+
265
+ ### 並べ方の規則
266
+
267
+ **並び順は変えない。** AppSpec に書いた順序のまま、隣り合った同じ系統のフィールドを
268
+ 最大 `maxPerRow` 個まで同じ行にまとめる。系統が変わったところで行を切る。
269
+
270
+ | 系統 | フィールド型 |
271
+ |---|---|
272
+ | テキスト | `SINGLE_LINE_TEXT` `LINK` |
273
+ | 数値 | `NUMBER` `CALC` |
274
+ | 選択 | `DROP_DOWN` `RADIO_BUTTON` `CHECK_BOX` `MULTI_SELECT` |
275
+ | 日時 | `DATE` `TIME` `DATETIME` |
276
+ | ユーザー | `USER_SELECT` `ORGANIZATION_SELECT` `GROUP_SELECT` |
277
+
278
+ `MULTI_LINE_TEXT` `RICH_TEXT` `FILE` など、幅や高さを取る型は常に単独行にする。
279
+
280
+ 18 フィールドの例:
281
+
282
+ ```
283
+ bookshelfId | title | author
284
+ readStatus | rating | ownership
285
+ lentTo | location | tags
286
+ publisher
287
+ publishedYear
288
+ isbn | coverUrl
289
+ cover
290
+ lookupStatus
291
+ spineText
292
+ addedOn
293
+ memo
294
+ ```
295
+
296
+ `deploy --dry-run` でこの並びを事前に確認できる。
297
+
298
+ ### 実物を起点に並べ替えている
299
+
300
+ レイアウト変更 API は **「フォーム上のすべてのフィールド」の指定を求める**。
301
+ 1 つでも欠けると失敗するので、AppSpec から推測して組み立てず、
302
+ フィールド追加後に `getFormLayout` で **kintone が実際に置いたレイアウトを取得してから**
303
+ 並べ替えている(`src/spec/layout.ts` の `regroupLayout`)。
304
+ `GROUP` と `SUBTABLE` の行は中身の構造が別なので、触らずそのまま残す。
305
+
306
+ ## アプリアイコン
307
+
308
+ `icon` に絵文字か文字を書くと、256×256 の PNG を生成してアプリアイコンに設定する。
309
+
310
+ ```jsonc
311
+ {
312
+ "name": "書籍管理",
313
+ "icon": "📚", // 絵文字、または "書" のような頭文字
314
+ "iconBackground": "#2563eb" // 省略するとアプリ名から自動で決まる
315
+ }
316
+ ```
317
+
318
+ 省略した場合はアイコンを設定しない(kintone の既定アイコンのまま)。
319
+ `plan` / `create` では、モデルが内容に合った絵文字を選ぶ。
320
+
321
+ ### 描画のしくみ
322
+
323
+ 文字と絵文字で経路が分かれる。
324
+
325
+ | `icon` の内容 | 描き方 |
326
+ |---|---|
327
+ | `書` `案` `AB` などの文字 | 同梱の **DotGothic16**(ドット絵風の日本語フォント)で描画 |
328
+ | `📚` `💼` などの絵文字 | **Noto Color Emoji から PNG を取り出して合成** |
329
+
330
+ 絵文字を `<text>` として描かないのは、Noto Color Emoji が CBDT 形式の
331
+ **ビットマップ**フォントで、SVG のラスタライザ(resvg)が描画できないため。
332
+ このフォントは絵文字の実体が PNG として埋め込まれているので、
333
+ ラスタライズせず取り出してそのまま合成している(`src/icon/emojiFont.ts`)。
334
+
335
+ - フォントは `assets/fonts/DotGothic16.ttf` に同梱(OFL-1.1、`OFL.txt` 同梱)。
336
+ システムフォントは使わないので、実行環境によって見た目が変わらない。
337
+ - 絵文字フォントは環境依存で探す。見つからない場合や、ZWJ で連結した複雑な絵文字は、
338
+ **文字として描画する**(アイコン生成そのものは失敗させない)。
339
+ - 同じ入力なら常に同じ画像になる。背景色もアプリ名から決まるので、作り直しても見た目が変わらない。
340
+
341
+ ## 失敗したときの挙動
342
+
343
+ デプロイは次の順に進む。
344
+
345
+ 1. 動作テスト環境にアプリを作成
346
+ 2. フィールドを追加(100 件ずつ分割)
347
+ 3. フォームの並びを整える(`layout` が `grouped` の場合のみ。既定で有効)
348
+ 4. アイコン画像を生成してアップロード(`icon` の指定がある場合のみ)
349
+ 5. 一般設定を変更(アイコンまたは他の設定がある場合のみ)
350
+ 6. 一覧を変更(指定がある場合のみ)
351
+ 7. 運用環境へ反映(非同期)
352
+ 8. 反映完了までポーリング(指数バックオフ、最大 5 分)
353
+
354
+ 各ステップが返す `revision` を次の呼び出しに引き回すので、同じアプリを別の誰かが同時に
355
+ 編集していた場合は kintone 側で検出される。
356
+
357
+ 2 以降で失敗した場合、アプリは**動作テスト環境にだけ存在し、運用環境には出ていない**。
358
+ 既定では調査できるようにアプリを残し、エラーにアプリ ID を含めて報告する。
359
+ 破棄したい場合は `--revert-on-failure` を付けて実行する。
360
+
361
+ ## 既存アプリを AppSpec にする
362
+
363
+ 手で作った既存アプリも AppSpec として取り出せる。**読み取りしかしないので、いつ実行しても安全。**
364
+
365
+ ```bash
366
+ fieldsmith pull 752 -o spec.json
367
+ ```
368
+
369
+ 取り出した AppSpec はそのまま `deploy` に渡せるので、既存アプリを雛形にして
370
+ 別のスペースや別のドメインに複製できる。
371
+
372
+ ### 表現できないものは黙って捨てない
373
+
374
+ AppSpec は kintone の設定をすべて表現できるわけではない。落としたものは必ず知らせる。
375
+
376
+ ```
377
+ 次の設定は AppSpec で表現できないため含まれていません:
378
+ - フィールド「明細」(SUBTABLE) は AppSpec で表現できないため除きました。
379
+ デプロイし直しても、このフィールドは作られません。
380
+ - アプリアイコンに画像が設定されていますが、AppSpec には含められません。
381
+ ```
382
+
383
+ 黙って捨てると「取得した spec をデプロイしたら別物ができた」となるので、
384
+ `--json` でも `warnings` として返す。
385
+
386
+ 現状で落ちるもの: テーブル・ルックアップ・関連レコード一覧などの未対応フィールド型、
387
+ カスタマイズ一覧、アプリアイコンの画像、細かなレイアウト。
388
+
389
+ ### レイアウトについて
390
+
391
+ `pull` は `"layout": "stacked"` を付けて返す。AppSpec は任意のレイアウトを
392
+ 表現できないので、**既存の並びに手を触れない**という指定にしてある。
393
+ 横並びにしたい場合は `"layout": "grouped"` に書き換える。
394
+
395
+ フィールドの順序は、フォームのレイアウト上の位置から復元する
396
+ (`getFormFields` はオブジェクトを返すので順序が保証されない)。
397
+
398
+ ## 差分を見る
399
+
400
+ 手元の AppSpec と既存アプリの違いを表示する。**読み取りのみ。**
401
+
402
+ ```bash
403
+ fieldsmith diff 761 target.json
404
+ ```
405
+
406
+ ```
407
+ ~ description: 営業案件の進捗を管理します。 → 営業案件の進捗を管理します(更新版)
408
+ + 受注日 (DATE) を追加
409
+ ~ 受注確度: options: ["高","中","低"] → ["S","A","B","C"]
410
+ ! 備考: 型を MULTI_LINE_TEXT → SINGLE_LINE_TEXT に変更 (kintone では不可)
411
+ - 顧客名 (SINGLE_LINE_TEXT) を削除候補へ
412
+ + 一覧「今月の案件」を追加
413
+ ```
414
+
415
+ | 記号 | 意味 |
416
+ |---|---|
417
+ | `+` | 追加する |
418
+ | `~` | 設定を変更する |
419
+ | `!` | **kintone では実行できない**(型変更)|
420
+ | `-` | 削除候補にする(**削除しない**)|
421
+
422
+ ### JSON 内のフィールドの順序
423
+
424
+ **順序が原因で追加や削除候補になることはない。** 対応付けはフィールドコードで行うので、
425
+ 並べ替えただけなら誤判定しない。
426
+
427
+ ただし**並べ替えそのものは差分として拾う**(`layout` が `grouped` のとき)。
428
+
429
+ ```
430
+ ~ フォームの並びを組み直す (並び順)
431
+ ```
432
+
433
+ `layout` が `stacked`(`pull` の既定)の場合は「既存の並びに手を触れない」指定なので、
434
+ 並べ替えても何も起きない。差分にも出さない。並びを変えたいときは `"layout": "grouped"` にする。
435
+
436
+ ### 同一性はフィールドコードで決まる
437
+
438
+ ラベルだけ変えたなら「同じフィールドの変更」、コードが変わったら「別フィールドの追加 + 元は削除候補」。
439
+ AppSpec を書き直すときは、**既存フィールドのコードを変えないこと**。
440
+
441
+ ### 型は変更できない
442
+
443
+ kintone は作成後のフィールド型を変えられない。`!` が出た場合、実現するには
444
+ 別のコードで新しいフィールドを作り、古い方を削除候補に送ることになる。
445
+ **データは移らない。**
446
+
447
+ ### 削除候補という考え方
448
+
449
+ 目標の AppSpec から消えたフィールドを、fieldsmith は**削除しない**。
450
+ 畳んだグループに移すだけで、データは残る。実際に消すかどうかは人が kintone の画面で決める。
451
+
452
+ ## 既存アプリを更新する
453
+
454
+ ```bash
455
+ fieldsmith pull 761 -o spec.json # 1. 現状を取り出す
456
+ # 2. spec.json を編集する(手でも、LLM でも)
457
+ fieldsmith diff 761 spec.json # 3. 何が起きるか確かめる
458
+ fieldsmith update 761 spec.json # 4. 動作テスト環境まで適用する
459
+ fieldsmith update 761 spec.json --deploy # 5. 問題なければ運用環境へ反映する
460
+ ```
461
+
462
+ ### 既定では運用環境へ反映しない
463
+
464
+ 更新は新規作成と違って壊しうる。`update` は**動作テスト環境で止める**ので、
465
+ kintone の画面で「変更を確認」してから、人が反映を決められる。
466
+
467
+ ```
468
+ 動作テスト環境まで反映しました。運用環境にはまだ反映していません。
469
+ https://example.cybozu.com/k/761/ で「変更を確認」してください。
470
+ 問題なければ --deploy を付けて再実行します。
471
+ ```
472
+
473
+ ### フィールドは消さない
474
+
475
+ 目標の AppSpec から消えたフィールドを、fieldsmith は**削除しない**。
476
+ `削除候補` という畳んだグループ(`_削除候補`)を作ってそこへ移すだけで、**データは残る**。
477
+ 実際に消すかどうかは人が kintone の画面で決める。
478
+
479
+ グループのフィールドコードは固定なので、更新を繰り返してもグループは増えない。
480
+ 削除候補に入れたフィールドを AppSpec に書き戻せば、グループから出て元に戻る。
481
+
482
+ ### 型を変えようとすると止まる
483
+
484
+ kintone は作成後のフィールド型を変更できない。`update` は**何も送らずに失敗する**。
485
+
486
+ ```
487
+ kintone は作成後のフィールド型を変更できないため、この AppSpec は適用できません。
488
+ 案件名: SINGLE_LINE_TEXT → MULTI_LINE_TEXT
489
+ 別のフィールドコードで新しいフィールドを作り、古い方は AppSpec から外してください
490
+ (外したフィールドは削除候補に移ります。データは残ります)。
491
+ ```
492
+
493
+ 一部だけ適用して spec と実物がずれるより、止めて直してもらうほうが安全という判断。
494
+
495
+ ### 書かれていない項目は「現状維持」
496
+
497
+ kintone の設定変更 API は、**省略した項目を変更しない**。fieldsmith もそれに合わせている。
498
+
499
+ ```jsonc
500
+ // 現状: required: true のフィールド
501
+ { "type": "SINGLE_LINE_TEXT", "label": "案件名" } // → required は true のまま
502
+ { "type": "SINGLE_LINE_TEXT", "label": "案件名", "required": false } // → false になる
503
+ ```
504
+
505
+ 差分にも出さない。出すと「実際には何も起きないのに差分が永遠に消えない」ことになる。
506
+ 値を変えたいときは明示的に書く。
507
+
508
+ ### 何度流しても同じ結果になる
509
+
510
+ `update` は運用環境ではなく**動作テスト環境**を読んで差分を取る。
511
+ 動作テスト環境は「運用環境 + まだ反映していない変更」なので、
512
+ 反映せずに 2 回流しても「もう追加済みのフィールドをまた追加する」ことにならない。
513
+
514
+ 既に削除候補グループへ移してあるフィールドも、もう一度は動かさない。
515
+
516
+ ### 更新の順序
517
+
518
+ 1. 現状を取得して差分を計算する
519
+ 2. 増えたフィールドを追加する(削除候補グループが要るならここで作る)
520
+ 3. 変わったフィールドを変更する(**変わったものだけ**送る。この API は部分指定でよい)
521
+ 4. アプリの設定を変更する
522
+ 5. 一覧を変更する
523
+ 6. レイアウトを組み直す(削除候補への退避もここ)
524
+ 7. `--deploy` のときだけ運用環境へ反映する
525
+
526
+ ## 指示だけで更新案を作る
527
+
528
+ 「こう変えて」と書けば、既存アプリの設計を書き換えた AppSpec が出る。**kintone は読むだけ。**
529
+
530
+ ```bash
531
+ fieldsmith revise 761 "受注確度を S/A/B/C の4段階にして、失注理由を足して" -o revised.json
532
+ ```
533
+
534
+ ```
535
+ ✓ 設計を生成しました (12 秒, 入力 4364 / 出力 1044 トークン)
536
+
537
+ この AppSpec を適用すると、こうなります:
538
+
539
+ + 失注理由 (MULTI_LINE_TEXT) を追加
540
+ ~ 受注確度: options: ["高","中","低"] → ["S","A","B","C"]
541
+
542
+ 確認したら: fieldsmith update 761 revised.json
543
+ ```
544
+
545
+ **何が起きるかは、生成結果の自己申告ではなく決定的な差分で示す。**
546
+ モデルには「変更後の完成形」を書かせ、そこから何をするかは `diff` が導く。
547
+
548
+ ### 更新でいちばん壊れやすいところ
549
+
550
+ フィールドコードが変わると、kintone は**別のフィールド**とみなす。
551
+ 追加 + 元は削除候補になり、**データは新しいフィールドに入らない**。
552
+ そのため `revise` はモデルに強く縛りをかけている。
553
+
554
+ - 既存フィールドの `code` は絶対に変えない(ラベルだけ変えるときも据え置く)
555
+ - 新しいフィールドだけ `code` を空にする(`label` から導出される)
556
+ - 既存フィールドの `type` は変えない(kintone では不可能なため)
557
+ - 頼まれていないフィールドと一覧は、渡されたとおりに書き写す
558
+
559
+ ### モデルが表現できないものは引き継ぐ
560
+
561
+ `layout` と `icon` は LLM のスキーマに無い(前者は構造化出力の上限、
562
+ 後者は kintone から絵文字として取り出せないため)。
563
+ `revise` は**元の設計から引き継ぐ**ので、頼んでいないレイアウトの組み直しや
564
+ アイコンの付け替えは起きない。
565
+
566
+ ## AI エージェントから使う
567
+
568
+ エージェントの仕事は **AppSpec を書くこと**で、デプロイはシェルコマンド 1 つ。
569
+ そのために必要なものは揃えてある。
570
+
571
+ ### 1. 書き方を教える
572
+
573
+ ```bash
574
+ fieldsmith schema # 簡潔な一覧(これを読ませる)
575
+ fieldsmith schema --json # 完全な JSON Schema
576
+ fieldsmith schema --example # そのまま deploy できる実例
577
+ ```
578
+
579
+ いずれも Zod の定義から導出しているので、実装とずれない。
580
+
581
+ ### 2. 認証なしで検証させる
582
+
583
+ ```bash
584
+ fieldsmith deploy spec.json --dry-run --json
585
+ ```
586
+
587
+ `--dry-run` は **kintone にも Claude にも接続しない**。
588
+ エージェントは「書く → 検証 → 直す」を、資格情報なしで何度でも回せる。
589
+
590
+ ### 3. 結果を機械可読で受け取る
591
+
592
+ `--json` を付けると、**stdout は JSON だけ**になり、進捗と人間向けの表示は stderr に回る。
593
+
594
+ ```json
595
+ {
596
+ "ok": true,
597
+ "command": "deploy",
598
+ "app": { "id": "752", "url": "https://example.cybozu.com/k/752/", "name": "案件管理" },
599
+ "revision": "7",
600
+ "fieldCount": 6
601
+ }
602
+ ```
603
+
604
+ 失敗も同じ形で返る。`hint` に次の一手が入る。
605
+
606
+ ```json
607
+ {
608
+ "ok": false,
609
+ "command": "deploy",
610
+ "error": {
611
+ "kind": "validation",
612
+ "exitCode": 2,
613
+ "hint": "AppSpec を直して再実行する",
614
+ "message": "AppSpec の検証に失敗しました",
615
+ "issues": [
616
+ { "path": "fields.0.type", "message": "STATUS: プロセス管理の設定で追加される…" }
617
+ ]
618
+ }
619
+ }
620
+ ```
621
+
622
+ ### 終了コード
623
+
624
+ 全部 1 で返すと「AppSpec を直す」のか「login する」のかが判別できないので、種類ごとに分けてある。
625
+
626
+ | コード | `kind` | 次にすること |
627
+ |---|---|---|
628
+ | 0 | — | 成功 |
629
+ | 1 | `unknown` | 想定外。メッセージを確認する |
630
+ | 2 | `validation` | AppSpec を直して再実行する |
631
+ | 3 | `auth` | `fieldsmith login` で認可をやり直す |
632
+ | 4 | `config` | `.env` の設定を見直す |
633
+ | 5 | `kintone` | 権限を確認するか、時間をおいて再試行する |
634
+ | 6 | `generation` | 要件の書き方を変えて再実行する |
635
+ | 7 | `input` | コマンドの引数を見直す |
636
+
637
+ ### 使い方の型
638
+
639
+ ```bash
640
+ fieldsmith schema > /tmp/appspec-reference.md # 1. 書き方を読む
641
+ # 2. エージェントが spec.json を書く
642
+ fieldsmith deploy spec.json --dry-run --json # 3. 検証(認証不要)。exit 2 なら issues を読んで直す
643
+ fieldsmith deploy spec.json --json # 4. デプロイ
644
+ ```
645
+
646
+ `login` だけは人間が一度やる必要がある(ブラウザで認可して URL を貼る)。
647
+ それ以外はすべて非対話で動く。`create` は確認が入るので、エージェントからは `-y` を付ける。
648
+
649
+ ### MCP ではなく CLI にしている理由
650
+
651
+ - エージェントの実作業は**ファイルを書くこと**。AppSpec は 100 行を超える JSON で、
652
+ ツール引数に埋めるより、ファイルとして残すほうがレビューでき、差分が見え、再利用できる
653
+ - 検証ループ(`--dry-run`)が**資格情報なしで回る**
654
+ - デプロイは進捗を出す長時間処理で、stderr へのストリーミングと相性がよい
655
+
656
+ シェルの無いホストから使いたくなったら、`deployAppSpec` / `generateAppSpec` を包む
657
+ 薄い MCP サーバーを足せばよい。コアがライブラリとして分離してあるので、後から乗せられる。
658
+
659
+ ## AppSpec を文章から作る
660
+
661
+ `plan` と `create` は、要件を書いた文章から AppSpec を生成する。
662
+ 生成結果は必ず AppSpec のスキーマで検証されるので、kintone に無効な設定が届くことはない。
663
+
664
+ `plan` / `create` を使う場合のみ必要。次のどちらかを設定する。
665
+
666
+ ```bash
667
+ # .env に書く(または環境変数として export する)
668
+ ANTHROPIC_API_KEY=sk-ant-...
669
+ ```
670
+
671
+ ```bash
672
+ # もしくは Anthropic CLI でログインする(キーをファイルに書かずに済む)
673
+ ant auth login
674
+ ```
675
+
676
+ どちらも無い場合、`plan` / `create` は次のエラーで停止する。
677
+
678
+ ```
679
+ Claude API の認証情報が見つかりません。次のいずれかを設定してください:
680
+ - .env または環境変数に ANTHROPIC_API_KEY を設定する
681
+ - Anthropic CLI (`ant auth login`) でログインする
682
+ ```
683
+
684
+ ### 要件をファイルから渡す
685
+
686
+ 要件が長くなると、シェルの引数として渡すのは現実的でなくなる(改行、引用符、全角括弧)。
687
+ `--prompt-file`(`-f`)でファイルから読める。Markdown でも構わない。
688
+
689
+ ```bash
690
+ npm run fieldsmith -- plan -f requirements.md -o spec.json
691
+ npm run fieldsmith -- create -f requirements.md
692
+ ```
693
+
694
+ `-` を渡すと標準入力から読む。
695
+
696
+ ```bash
697
+ cat requirements.md | npm run fieldsmith -- plan -f - -o spec.json
698
+ ```
699
+
700
+ 引数と `--prompt-file` の同時指定はエラーにする。どちらが使われるか曖昧なまま
701
+ 生成が走るほうが困るため。どちらも無い場合は使い方を表示する。
702
+
703
+ ## 実行中の見え方
704
+
705
+ 生成もデプロイも数十秒かかることがあるので、何をしているかを常に表示する。
706
+ 生成はストリーミングで行うため、待っている間も進み具合が動く。
707
+
708
+ ```
709
+ ⠹ 設計を書き出しています (612 文字) (14秒)
710
+ ```
711
+
712
+ 完了すると 1 行に確定する。
713
+
714
+ ```
715
+ ✓ 設計を生成しました (23 秒, 入力 1243 / 出力 892 トークン)
716
+ ```
717
+
718
+ ### `--verbose`
719
+
720
+ `-v` / `--verbose` を付けると、切り分けに要る情報まで出す。
721
+
722
+ - 生成中の**思考の要約**(いま何を考えているかが流れる)
723
+ - 使用モデル、接続先の kintone ドメイン
724
+ - 各ステップの `revision` の遷移、追加したフィールドコード
725
+ - 反映状況の確認が何回目か、何秒経過したか
726
+
727
+ ```bash
728
+ npm run fieldsmith -- --verbose create "問い合わせ管理アプリ"
729
+ ```
730
+
731
+ ```
732
+ ⠸ 考え中: …問い合わせの状態は「未対応/対応中/完了」の3つが妥当 (8秒)
733
+ ✓ 設計を生成しました (23 秒, 入力 1243 / 出力 892 トークン)
734
+
735
+ 「問い合わせ管理」をデプロイします (フィールド 6 件)
736
+ アプリ「問い合わせ管理」を動作テスト環境に作成します
737
+ アプリ ID 128 を作成しました
738
+ revision=1
739
+ フィールドを 6 件追加します
740
+ フィールドを追加しました
741
+ 件名, 内容, 対応状況, 担当者, 受付日, 備考 / revision 1 → 2
742
+ 運用環境へ反映します
743
+ POST /k/v1/preview/app/deploy.json (app=128, revision=4)
744
+ 反映の完了を待っています
745
+ 反映状況: PROCESSING
746
+ 1 回目の確認 (1 秒経過)
747
+ 反映状況: SUCCESS
748
+ 2 回目の確認 (3 秒経過)
749
+
750
+ ✓ デプロイが完了しました。
751
+ ```
752
+
753
+ ### 応答が来ないとき
754
+
755
+ `サーバーの応答を待っています (NN 秒経過 / 上限 180 秒)` のままカウントが進む場合、
756
+ リクエストは送信済みでサーバーからの最初の応答を待っている状態を意味する。
757
+ 180 秒で打ち切り、確認すべきことを添えて終了する(SDK の既定は 10 分だが、
758
+ CLI がそれだけ無反応だと事故と区別が付かないので短くしてある)。
759
+
760
+ 原因の切り分けには `--verbose` を使う。サーバーから届いた生のイベントが
761
+ 到着順に表示されるので、どこで止まっているかが分かる。
762
+
763
+ ```
764
+ リクエスト ID: req_011Ce...
765
+ イベント: message_start (2 秒)
766
+ イベント: content_block_start (2 秒)
767
+ イベント: content_block_delta (3 秒)
768
+ ```
769
+
770
+ `リクエスト ID` は問い合わせの際に使える。
771
+
772
+ 進捗は stderr に出すので、`plan` の JSON を `>` でファイルに落としても混ざらない。
773
+ パイプやリダイレクト時は 1 行書き換えをやめ、普通の行として出す(ANSI エスケープを混ぜない)。
774
+
775
+ ## 開発
776
+
777
+ ```bash
778
+ npm run typecheck
779
+ npm test # ユニットテスト(kintone にも Claude にも接続しない)
780
+ npm run e2e:manual # 実 kintone 環境に検証用アプリを 1 つ作る
781
+ ```
782
+
783
+ ユニットテストは kintone REST API を MSW でモックし、実際に飛ぶ HTTP を観測して
784
+ 呼び出し順序・`revision` の引き回し・分割・ポーリング・失敗時の挙動まで検証している。
785
+
786
+ ### 構成
787
+
788
+ | パス | 役割 |
789
+ |---|---|
790
+ | `src/spec/` | AppSpec のスキーマ(Zod)と kintone ペイロードへの変換 |
791
+ | `src/kintone/` | OAuth、トークン保存、クライアント、デプロイのオーケストレーション |
792
+ | `src/icon/` | アプリアイコンの生成(SVG → PNG、絵文字の抽出) |
793
+ | `src/llm/` | プロンプト → AppSpec の生成(Claude API) |
794
+ | `src/cli/` | コマンド定義 |
795
+
796
+ ### 構造化出力のスキーマに関する制約
797
+
798
+ `src/llm/generate.ts` の LLM 向けスキーマは、**すべてのプロパティを必須にし、
799
+ 「指定なし」を空文字・空配列で表す**。見た目は不自然だが、これには理由がある。
800
+
801
+ 構造化出力は、渡した JSON Schema を**制約付きデコード用の文法にコンパイル**して、
802
+ モデルがその形以外を出力できないようにする仕組み。文法が膨らむ書き方は
803
+ **Claude API 側が 400 で弾く**(SDK でも Zod でもなく、サーバーが返すエラー)。
804
+
805
+ 素直に書くと、2 通りの弾かれ方のどちらかに必ず当たる。
806
+
807
+ | 書き方 | 生成される JSON Schema | 返るエラー |
808
+ |---|---|---|
809
+ | `.nullable()` | `anyOf: [T, null]` = union | `Schemas contains too many parameters with union types … (limit: 16 parameters with unions)` |
810
+ | `.optional()` | `required` から外れる | `Schema is too complex.` |
811
+
812
+ 後者は、省略可能なキーが増えると「どのキーがどの順で現れてもよい」を表現する必要があり、
813
+ 組み合わせが膨らむため。すべて必須にすれば出現順が 1 通りに決まり、文法が最小になる。
814
+ 空の値は `toAppSpecInput()` で落とすので、コア側には現れない。
815
+
816
+ **上限は公式に文書化されていない。** 数字の 16 が出てくるのは union のエラーだけで、
817
+ これは「union を持つパラメータ」の数の話。実測はこうだった。
818
+
819
+ | 構成 | 結果 |
820
+ |---|---|
821
+ | 26 プロパティ / 省略可 10 / union 19 | union 上限で 400 |
822
+ | 26 プロパティ / 省略可 10 / union 0 | `Schema is too complex.` |
823
+ | 14〜16 プロパティ / 省略可 0 / union 0 | 通る |
824
+
825
+ 効いているのはプロパティ数そのものではなく union と省略可の数なので、
826
+ `tests/llm.test.ts` では **union 0 / 省略可 0** を厳密に見張り、
827
+ プロパティ数は観測から決めた経験則として 16 を上限にしている。
828
+
829
+ それでも弾かれた場合は、構造化出力を使わず JSON を直接書かせる方式に自動で切り替わる。
830
+
831
+ それでもスキーマが弾かれた場合は、**構造化出力を使わず JSON を直接書かせる方式に自動で切り替える**。
832
+ どちらの経路でも結果は必ずコアの `parseAppSpec()` で検証するので、扱いは変わらない。
833
+
834
+ LLM 層は `zod/v4`(`@anthropic-ai/sdk` の `zodOutputFormat` が要求)、
835
+ コアは zod v3 を使う。両者の値は直接やり取りせず、`toAppSpecInput()` で
836
+ ただのオブジェクトに落としてから受け渡す。
837
+
838
+ ## ライセンス
839
+
840
+ MIT License([LICENSE](LICENSE))
841
+
842
+ 同梱している日本語フォント **DotGothic16** は SIL Open Font License 1.1 です。
843
+ ライセンス全文は [assets/fonts/OFL.txt](assets/fonts/OFL.txt) に同梱しています。