@khotan/cli 0.11.0 → 0.12.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 (2) hide show
  1. package/dist/khotan.js +2687 -548
  2. package/package.json +1 -1
package/dist/khotan.js CHANGED
@@ -19,7 +19,7 @@ var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
19
19
  var __require = /* @__PURE__ */ createRequire(import.meta.url);
20
20
 
21
21
  // ../khotan-core/src/version.ts
22
- var CATALOG_SCHEMA_VERSION = 2, CATALOG_VERSION = "2026-07-02", KHOTAN_ADAPTER_NAME = "khotan", KHOTAN_ADAPTER_VERSION = "0.10.0", SUPPORTED_CATALOG_SCHEMA_VERSIONS;
22
+ var CATALOG_SCHEMA_VERSION = 2, CATALOG_VERSION = "2026-08-23", KHOTAN_ADAPTER_NAME = "khotan", KHOTAN_ADAPTER_VERSION = "0.10.0", SUPPORTED_CATALOG_SCHEMA_VERSIONS;
23
23
  var init_version = __esm(() => {
24
24
  SUPPORTED_CATALOG_SCHEMA_VERSIONS = [
25
25
  CATALOG_SCHEMA_VERSION
@@ -1129,7 +1129,7 @@ var init_catalog = __esm(() => {
1129
1129
  { name: "slug", location: "body", type: "string", description: "Stable identifier; derived from the name when omitted." },
1130
1130
  { name: "description", location: "body", type: "string", description: "Short summary shown in listings." }
1131
1131
  ],
1132
- http: { method: "POST", path: "/api/v1/projects", operationId: "createProject" },
1132
+ http: { method: "POST", path: "/api/v1/projects", operationId: "createProject", bodyMode: "json" },
1133
1133
  cli: { command: ["projects", "create"], summary: "Create a project" }
1134
1134
  },
1135
1135
  {
@@ -1138,18 +1138,34 @@ var init_catalog = __esm(() => {
1138
1138
  safety: "destructive",
1139
1139
  auth: true,
1140
1140
  title: "Delete a project",
1141
- description: "Soft-delete a project and free its slug. The linked Linear project is retained as the record of work done.",
1141
+ description: "Soft-delete a project and free its slug. The linked tracker project is retained as the record of work done.",
1142
1142
  input: [idField("projectId", "The project id or slug.")],
1143
1143
  http: { method: "DELETE", path: "/api/v1/projects/{projectId}", operationId: "deleteProject" },
1144
1144
  cli: { command: ["projects", "delete"], summary: "Delete a project" }
1145
1145
  },
1146
+ {
1147
+ id: "projects.update",
1148
+ domain: "projects",
1149
+ safety: "write",
1150
+ auth: true,
1151
+ title: "Update a project",
1152
+ description: "Update a project's name, description, or lifecycle. Archive with lifecycle archived.",
1153
+ input: [
1154
+ idField("projectId", "The project id or slug."),
1155
+ { name: "name", location: "body", type: "string", description: "Display name for the project." },
1156
+ { name: "description", location: "body", type: "string", description: "Short summary shown in listings." },
1157
+ { name: "lifecycle", location: "body", type: "string", description: "One of: active, archived." }
1158
+ ],
1159
+ http: { method: "PATCH", path: "/api/v1/projects/{projectId}", operationId: "updateProject", bodyMode: "json" },
1160
+ cli: { command: ["projects", "update"], summary: "Update a project" }
1161
+ },
1146
1162
  {
1147
1163
  id: "projects.spec.get",
1148
1164
  domain: "projects",
1149
1165
  safety: "read",
1150
1166
  auth: true,
1151
1167
  title: "Read a project spec",
1152
- description: "Read the spec tree at one altitude: high renders the goal, medium adds phases, low adds items and their mirrored Linear issues.",
1168
+ description: "Read the spec tree at one altitude: high renders the goal, medium adds phases, low adds items and their mirrored tracker issues.",
1153
1169
  input: [
1154
1170
  idField("projectId", "The project id or slug."),
1155
1171
  { name: "altitude", location: "query", type: "string", default: "high", description: "One of: high, medium, low." }
@@ -1208,7 +1224,7 @@ var init_catalog = __esm(() => {
1208
1224
  { name: "resourceKind", location: "body", type: "string", required: true, description: "One of: app, pipeline, database." },
1209
1225
  { name: "resourceId", location: "body", type: "string", required: true, description: "The resource id." }
1210
1226
  ],
1211
- http: { method: "POST", path: "/api/v1/projects/{projectId}/members", operationId: "attachProjectMember" },
1227
+ http: { method: "POST", path: "/api/v1/projects/{projectId}/members", operationId: "attachProjectMember", bodyMode: "json" },
1212
1228
  cli: { command: ["projects", "attach"], summary: "Attach a resource to a project" }
1213
1229
  },
1214
1230
  {
@@ -1223,21 +1239,99 @@ var init_catalog = __esm(() => {
1223
1239
  { name: "resourceKind", location: "body", type: "string", required: true, description: "One of: app, pipeline, database." },
1224
1240
  { name: "resourceId", location: "body", type: "string", required: true, description: "The resource id." }
1225
1241
  ],
1226
- http: { method: "DELETE", path: "/api/v1/projects/{projectId}/members", operationId: "detachProjectMember" },
1242
+ http: { method: "DELETE", path: "/api/v1/projects/{projectId}/members", operationId: "detachProjectMember", bodyMode: "json" },
1227
1243
  cli: { command: ["projects", "detach"], summary: "Detach a resource from a project" }
1244
+ },
1245
+ {
1246
+ id: "projects.nodes.create",
1247
+ domain: "projects",
1248
+ safety: "write",
1249
+ auth: true,
1250
+ title: "Create a spec node",
1251
+ description: "Create a child under an existing spec node — a delivery phase under Milestones, or an item under a phase.",
1252
+ input: [
1253
+ idField("projectId", "The project id or slug."),
1254
+ { name: "parentId", location: "body", type: "string", required: true, description: "The parent node id." },
1255
+ { name: "title", location: "body", type: "string", required: true, description: "Display title for the node." },
1256
+ { name: "body", location: "body", type: "string", description: "Initial body. Empty when omitted." },
1257
+ { name: "position", location: "body", type: "number", description: "Sibling position. Appended when omitted." }
1258
+ ],
1259
+ http: { method: "POST", path: "/api/v1/projects/{projectId}/spec/nodes", operationId: "createProjectSpecNode", bodyMode: "json" },
1260
+ cli: { command: ["projects", "node", "create"], summary: "Create a spec node" }
1261
+ },
1262
+ {
1263
+ id: "projects.nodes.update",
1264
+ domain: "projects",
1265
+ safety: "write",
1266
+ auth: true,
1267
+ title: "Update a spec node",
1268
+ description: "Update a node's title, sibling position, parent, or milestone plan dates.",
1269
+ input: [
1270
+ idField("projectId", "The project id or slug."),
1271
+ idField("nodeId", "The spec node id."),
1272
+ { name: "title", location: "body", type: "string", description: "Display title for the node." },
1273
+ { name: "position", location: "body", type: "number", description: "Sibling position." },
1274
+ { name: "parentId", location: "body", type: "string", description: "Move the node under this parent." },
1275
+ { name: "startDate", location: "body", type: "string", description: "Milestone plan start (YYYY-MM-DD). Only valid on a phase." },
1276
+ { name: "targetDate", location: "body", type: "string", description: "Milestone plan target (YYYY-MM-DD). Only valid on a phase." },
1277
+ { name: "dependsOnNodeId", location: "body", type: "string", description: "Milestone this phase waits on. Only valid on a phase." }
1278
+ ],
1279
+ http: { method: "PATCH", path: "/api/v1/projects/{projectId}/spec/nodes/{nodeId}", operationId: "updateProjectSpecNode", bodyMode: "json" },
1280
+ cli: { command: ["projects", "node", "update"], summary: "Update a spec node" }
1281
+ },
1282
+ {
1283
+ id: "projects.nodes.delete",
1284
+ domain: "projects",
1285
+ safety: "destructive",
1286
+ auth: true,
1287
+ title: "Delete a spec node",
1288
+ description: "Soft-delete a spec node and its descendants. The goal node cannot be deleted.",
1289
+ input: [
1290
+ idField("projectId", "The project id or slug."),
1291
+ idField("nodeId", "The spec node id.")
1292
+ ],
1293
+ http: { method: "DELETE", path: "/api/v1/projects/{projectId}/spec/nodes/{nodeId}", operationId: "deleteProjectSpecNode" },
1294
+ cli: { command: ["projects", "node", "delete"], summary: "Delete a spec node" }
1295
+ },
1296
+ {
1297
+ id: "projects.linear.options",
1298
+ domain: "projects",
1299
+ safety: "read",
1300
+ auth: true,
1301
+ title: "List Linear projects that can be bound",
1302
+ description: "List Linear projects this workspace can bind a Khotan project to.",
1303
+ input: [
1304
+ { name: "query", location: "query", type: "string", description: "Filter Linear projects by name." }
1305
+ ],
1306
+ http: { method: "GET", path: "/api/v1/projects/linear-options", operationId: "listLinearProjectOptions" },
1307
+ cli: { command: ["projects", "linear", "options"], summary: "List Linear projects that can be bound", defaultOutput: "table" }
1308
+ },
1309
+ {
1310
+ id: "projects.linear.bind",
1311
+ domain: "projects",
1312
+ safety: "write",
1313
+ auth: true,
1314
+ title: "Bind a project to Linear",
1315
+ description: "Bind this project to an existing Linear project. A new project mints its own on first push.",
1316
+ input: [
1317
+ idField("projectId", "The project id or slug."),
1318
+ { name: "linearProjectId", location: "body", type: "string", required: true, description: "The Linear project id to bind." }
1319
+ ],
1320
+ http: { method: "POST", path: "/api/v1/projects/{projectId}/linear", operationId: "bindLinearProject", bodyMode: "json" },
1321
+ cli: { command: ["projects", "linear", "bind"], summary: "Bind a project to Linear" }
1228
1322
  }
1229
1323
  ];
1230
1324
  capabilities = [
1231
1325
  ...identityCapabilities,
1232
1326
  ...appsCapabilities,
1233
1327
  ...pipelinesCapabilities,
1328
+ ...fileCapabilities,
1329
+ ...contextCapabilities,
1330
+ ...integrationsCapabilities,
1234
1331
  ...databaseCapabilities,
1235
1332
  ...redisCapabilities,
1236
- ...fileCapabilities,
1237
1333
  ...folderCapabilities,
1238
- ...contextCapabilities,
1239
1334
  ...projectCapabilities,
1240
- ...integrationsCapabilities,
1241
1335
  ...emailSubscriptionCapabilities
1242
1336
  ];
1243
1337
  khotanCatalog = {
@@ -1430,6 +1524,13 @@ function validateCatalogStructure(catalog = khotanCatalog) {
1430
1524
  });
1431
1525
  }
1432
1526
  }
1527
+ const hasBodyInput = capability.input.some((field) => field.location === "body");
1528
+ if (hasBodyInput && capability.http.bodyMode !== "json") {
1529
+ errors.push({
1530
+ capabilityId: capability.id,
1531
+ message: 'Has body inputs but does not declare bodyMode: "json".'
1532
+ });
1533
+ }
1433
1534
  }
1434
1535
  return { ok: errors.length === 0, errors };
1435
1536
  }
@@ -1497,6 +1598,11 @@ var init_spec = __esm(() => {
1497
1598
  title: "Khotan API",
1498
1599
  version: "1.0.0"
1499
1600
  },
1601
+ servers: [
1602
+ {
1603
+ url: "https://app.khotan.com"
1604
+ }
1605
+ ],
1500
1606
  paths: {
1501
1607
  "/api/v1/health": {
1502
1608
  get: {
@@ -4630,7 +4736,7 @@ var init_spec = __esm(() => {
4630
4736
  }
4631
4737
  },
4632
4738
  summary: "Create a Redis database",
4633
- description: "Provisions a global Upstash Redis database synchronously and responds 201 with the persisted record whether provisioning ends ready or failed; branch on the returned status. This operation is not idempotent: retrying a request that already succeeded provisions a second database. Omit primaryRegion to use the default region.",
4739
+ description: "Provisions a globally replicated Redis database synchronously and responds 201 with the persisted record whether provisioning ends ready or failed; branch on the returned status. This operation is not idempotent: retrying a request that already succeeded provisions a second database. Omit primaryRegion to use the default region.",
4634
4740
  tags: [
4635
4741
  "Redis Databases"
4636
4742
  ],
@@ -4920,7 +5026,7 @@ var init_spec = __esm(() => {
4920
5026
  }
4921
5027
  },
4922
5028
  summary: "Delete a Redis database",
4923
- description: "Tears down the Upstash database, then soft-deletes the record — the same semantics as deleting from the dashboard. Deletion is permanent. Returns 403 resource_protected when the database has deletion protection enabled; protection can only be removed from the dashboard.",
5029
+ description: "Tears down the database at the provider, then soft-deletes the record — the same semantics as deleting from the dashboard. Deletion is permanent. Returns 403 resource_protected when the database has deletion protection enabled; protection can only be removed from the dashboard.",
4924
5030
  tags: [
4925
5031
  "Redis Databases"
4926
5032
  ],
@@ -15155,6 +15261,102 @@ var init_spec = __esm(() => {
15155
15261
  ]
15156
15262
  }
15157
15263
  },
15264
+ "/api/v1/projects/linear-options": {
15265
+ get: {
15266
+ responses: {
15267
+ "200": {
15268
+ description: "Linear projects this workspace can bind to.",
15269
+ content: {
15270
+ "application/json": {
15271
+ schema: {
15272
+ type: "object",
15273
+ properties: {
15274
+ projects: {
15275
+ type: "array",
15276
+ items: {
15277
+ type: "object",
15278
+ properties: {
15279
+ id: {
15280
+ type: "string"
15281
+ },
15282
+ name: {
15283
+ type: "string"
15284
+ },
15285
+ url: {
15286
+ nullable: true,
15287
+ type: "string"
15288
+ },
15289
+ statusName: {
15290
+ nullable: true,
15291
+ type: "string"
15292
+ }
15293
+ },
15294
+ required: [
15295
+ "id",
15296
+ "name",
15297
+ "url",
15298
+ "statusName"
15299
+ ],
15300
+ additionalProperties: false
15301
+ }
15302
+ }
15303
+ },
15304
+ required: [
15305
+ "projects"
15306
+ ],
15307
+ additionalProperties: false
15308
+ }
15309
+ }
15310
+ }
15311
+ },
15312
+ "401": {
15313
+ description: "Authentication is missing or invalid.",
15314
+ content: {
15315
+ "application/json": {
15316
+ schema: {
15317
+ $ref: "#/components/schemas/Error"
15318
+ }
15319
+ }
15320
+ }
15321
+ },
15322
+ "429": {
15323
+ description: "Too many requests; retry after the indicated delay.",
15324
+ content: {
15325
+ "application/json": {
15326
+ schema: {
15327
+ $ref: "#/components/schemas/Error"
15328
+ }
15329
+ }
15330
+ }
15331
+ }
15332
+ },
15333
+ summary: "List Linear projects that can be bound",
15334
+ description: "Returns Linear projects visible to this workspace. Use this to pick a project to bind; a new Khotan project mints its own Linear project on first push.",
15335
+ tags: [
15336
+ "Projects"
15337
+ ],
15338
+ operationId: "listLinearProjectOptions",
15339
+ parameters: [
15340
+ {
15341
+ name: "query",
15342
+ in: "query",
15343
+ required: false,
15344
+ schema: {
15345
+ type: "string",
15346
+ maxLength: 200
15347
+ }
15348
+ }
15349
+ ],
15350
+ security: [
15351
+ {
15352
+ bearerAuth: []
15353
+ },
15354
+ {
15355
+ apiKeyHeader: []
15356
+ }
15357
+ ]
15358
+ }
15359
+ },
15158
15360
  "/api/v1/projects/{projectId}": {
15159
15361
  get: {
15160
15362
  responses: {
@@ -15383,7 +15585,7 @@ var init_spec = __esm(() => {
15383
15585
  }
15384
15586
  },
15385
15587
  summary: "Update a project",
15386
- description: "Updates a project's Khotan-owned attributes. The slug is immutable, and execution state is never writable here — Linear owns it.",
15588
+ description: "Updates a project's Khotan-owned attributes. The slug is immutable, and execution state is never writable here — the tracker owns it.",
15387
15589
  tags: [
15388
15590
  "Projects"
15389
15591
  ],
@@ -15484,7 +15686,7 @@ var init_spec = __esm(() => {
15484
15686
  }
15485
15687
  },
15486
15688
  summary: "Delete a project",
15487
- description: "Soft-deletes a project and frees its slug for reuse. The linked Linear project is retained.",
15689
+ description: "Soft-deletes a project and frees its slug for reuse. The linked tracker project is retained.",
15488
15690
  tags: [
15489
15691
  "Projects"
15490
15692
  ],
@@ -15573,6 +15775,10 @@ var init_spec = __esm(() => {
15573
15775
  title: {
15574
15776
  type: "string"
15575
15777
  },
15778
+ sectionKey: {
15779
+ nullable: true,
15780
+ type: "string"
15781
+ },
15576
15782
  documentId: {
15577
15783
  nullable: true,
15578
15784
  type: "string"
@@ -15589,6 +15795,18 @@ var init_spec = __esm(() => {
15589
15795
  nullable: true,
15590
15796
  type: "string"
15591
15797
  },
15798
+ startDate: {
15799
+ nullable: true,
15800
+ type: "string"
15801
+ },
15802
+ targetDate: {
15803
+ nullable: true,
15804
+ type: "string"
15805
+ },
15806
+ dependsOnNodeId: {
15807
+ nullable: true,
15808
+ type: "string"
15809
+ },
15592
15810
  children: {
15593
15811
  type: "array",
15594
15812
  items: {
@@ -15602,10 +15820,14 @@ var init_spec = __esm(() => {
15602
15820
  "depth",
15603
15821
  "position",
15604
15822
  "title",
15823
+ "sectionKey",
15605
15824
  "documentId",
15606
15825
  "body",
15607
15826
  "bodyRevision",
15608
15827
  "linearMilestoneId",
15828
+ "startDate",
15829
+ "targetDate",
15830
+ "dependsOnNodeId",
15609
15831
  "children"
15610
15832
  ],
15611
15833
  additionalProperties: false
@@ -15647,7 +15869,7 @@ var init_spec = __esm(() => {
15647
15869
  }
15648
15870
  },
15649
15871
  summary: "Read a project spec",
15650
- description: "Returns the project's spec tree limited to the requested altitude: high renders the goal, medium adds phases, low adds items joined with their mirrored Linear issues. Defaults to high.",
15872
+ description: "Returns the project's spec tree limited to the requested altitude: high renders the goal, medium adds phases, low adds items joined with their mirrored tracker issues. Defaults to high.",
15651
15873
  tags: [
15652
15874
  "Projects"
15653
15875
  ],
@@ -15673,7 +15895,7 @@ var init_spec = __esm(() => {
15673
15895
  "medium",
15674
15896
  "low"
15675
15897
  ],
15676
- description: "Depth cap over the one spec tree: high renders the goal, medium adds phases, low adds items and their mirrored Linear issues."
15898
+ description: "Depth cap over the one spec tree: high renders the goal, medium adds phases, low adds items and their mirrored tracker issues."
15677
15899
  }
15678
15900
  }
15679
15901
  ],
@@ -15923,59 +16145,69 @@ var init_spec = __esm(() => {
15923
16145
  ]
15924
16146
  }
15925
16147
  },
15926
- "/api/v1/projects/{projectId}/revisions": {
15927
- get: {
16148
+ "/api/v1/projects/{projectId}/spec/nodes": {
16149
+ post: {
15928
16150
  responses: {
15929
- "200": {
15930
- description: "The project's committed spec revisions.",
16151
+ "201": {
16152
+ description: "The created node.",
15931
16153
  content: {
15932
16154
  "application/json": {
15933
16155
  schema: {
15934
16156
  type: "object",
15935
16157
  properties: {
15936
- revisions: {
15937
- type: "array",
15938
- items: {
15939
- type: "object",
15940
- properties: {
15941
- id: {
15942
- type: "string"
15943
- },
15944
- revision: {
15945
- type: "number"
15946
- },
15947
- changeSummary: {
15948
- nullable: true,
15949
- type: "string"
15950
- },
15951
- actorType: {
15952
- type: "string"
15953
- },
15954
- actorId: {
15955
- type: "string"
15956
- },
15957
- committedAt: {
15958
- type: "string"
15959
- },
15960
- documentCount: {
15961
- type: "number"
15962
- }
15963
- },
15964
- required: [
15965
- "id",
15966
- "revision",
15967
- "changeSummary",
15968
- "actorType",
15969
- "actorId",
15970
- "committedAt",
15971
- "documentCount"
15972
- ],
15973
- additionalProperties: false
15974
- }
16158
+ id: {
16159
+ type: "string"
16160
+ },
16161
+ parentId: {
16162
+ nullable: true,
16163
+ type: "string"
16164
+ },
16165
+ depth: {
16166
+ type: "number"
16167
+ },
16168
+ position: {
16169
+ type: "number"
16170
+ },
16171
+ title: {
16172
+ type: "string"
16173
+ },
16174
+ sectionKey: {
16175
+ nullable: true,
16176
+ type: "string"
16177
+ },
16178
+ documentId: {
16179
+ nullable: true,
16180
+ type: "string"
16181
+ },
16182
+ linearMilestoneId: {
16183
+ nullable: true,
16184
+ type: "string"
16185
+ },
16186
+ startDate: {
16187
+ nullable: true,
16188
+ type: "string"
16189
+ },
16190
+ targetDate: {
16191
+ nullable: true,
16192
+ type: "string"
16193
+ },
16194
+ dependsOnNodeId: {
16195
+ nullable: true,
16196
+ type: "string"
15975
16197
  }
15976
16198
  },
15977
16199
  required: [
15978
- "revisions"
16200
+ "id",
16201
+ "parentId",
16202
+ "depth",
16203
+ "position",
16204
+ "title",
16205
+ "sectionKey",
16206
+ "documentId",
16207
+ "linearMilestoneId",
16208
+ "startDate",
16209
+ "targetDate",
16210
+ "dependsOnNodeId"
15979
16211
  ],
15980
16212
  additionalProperties: false
15981
16213
  }
@@ -15992,6 +16224,16 @@ var init_spec = __esm(() => {
15992
16224
  }
15993
16225
  }
15994
16226
  },
16227
+ "403": {
16228
+ description: "The caller is not permitted to perform this action.",
16229
+ content: {
16230
+ "application/json": {
16231
+ schema: {
16232
+ $ref: "#/components/schemas/Error"
16233
+ }
16234
+ }
16235
+ }
16236
+ },
15995
16237
  "404": {
15996
16238
  description: "The resource was not found.",
15997
16239
  content: {
@@ -16002,8 +16244,8 @@ var init_spec = __esm(() => {
16002
16244
  }
16003
16245
  }
16004
16246
  },
16005
- "429": {
16006
- description: "Too many requests; retry after the indicated delay.",
16247
+ "422": {
16248
+ description: "Response.",
16007
16249
  content: {
16008
16250
  "application/json": {
16009
16251
  schema: {
@@ -16011,14 +16253,24 @@ var init_spec = __esm(() => {
16011
16253
  }
16012
16254
  }
16013
16255
  }
16014
- }
16015
- },
16016
- summary: "List spec revisions",
16017
- description: "Returns the project's committed spec revisions newest-first, with actor, timestamp, change summary, and the number of documents each revision bundled.",
16256
+ },
16257
+ "429": {
16258
+ description: "Too many requests; retry after the indicated delay.",
16259
+ content: {
16260
+ "application/json": {
16261
+ schema: {
16262
+ $ref: "#/components/schemas/Error"
16263
+ }
16264
+ }
16265
+ }
16266
+ }
16267
+ },
16268
+ summary: "Create a spec node",
16269
+ description: "Creates a child under an existing spec node. Use this to add a delivery phase under the Milestones section, or an item under a phase. The thirteen section headings are created with the project and cannot be added here.",
16018
16270
  tags: [
16019
16271
  "Projects"
16020
16272
  ],
16021
- operationId: "listProjectSpecRevisions",
16273
+ operationId: "createProjectSpecNode",
16022
16274
  parameters: [
16023
16275
  {
16024
16276
  name: "projectId",
@@ -16028,18 +16280,42 @@ var init_spec = __esm(() => {
16028
16280
  type: "string",
16029
16281
  minLength: 1
16030
16282
  }
16031
- },
16032
- {
16033
- name: "limit",
16034
- in: "query",
16035
- required: false,
16036
- schema: {
16037
- type: "integer",
16038
- minimum: 1,
16039
- maximum: 200
16040
- }
16041
16283
  }
16042
16284
  ],
16285
+ requestBody: {
16286
+ required: true,
16287
+ content: {
16288
+ "application/json": {
16289
+ schema: {
16290
+ type: "object",
16291
+ properties: {
16292
+ parentId: {
16293
+ type: "string",
16294
+ minLength: 1
16295
+ },
16296
+ title: {
16297
+ type: "string",
16298
+ minLength: 1,
16299
+ maxLength: 200
16300
+ },
16301
+ body: {
16302
+ type: "string",
16303
+ maxLength: 1e6
16304
+ },
16305
+ position: {
16306
+ type: "integer",
16307
+ minimum: 0,
16308
+ maximum: 9007199254740991
16309
+ }
16310
+ },
16311
+ required: [
16312
+ "parentId",
16313
+ "title"
16314
+ ]
16315
+ }
16316
+ }
16317
+ }
16318
+ },
16043
16319
  security: [
16044
16320
  {
16045
16321
  bearerAuth: []
@@ -16050,11 +16326,11 @@ var init_spec = __esm(() => {
16050
16326
  ]
16051
16327
  }
16052
16328
  },
16053
- "/api/v1/projects/{projectId}/revisions/{revision}": {
16054
- get: {
16329
+ "/api/v1/projects/{projectId}/spec/nodes/{nodeId}": {
16330
+ patch: {
16055
16331
  responses: {
16056
16332
  "200": {
16057
- description: "The revision with its diff and provenance.",
16333
+ description: "The updated node.",
16058
16334
  content: {
16059
16335
  "application/json": {
16060
16336
  schema: {
@@ -16063,106 +16339,56 @@ var init_spec = __esm(() => {
16063
16339
  id: {
16064
16340
  type: "string"
16065
16341
  },
16066
- revision: {
16342
+ parentId: {
16343
+ nullable: true,
16344
+ type: "string"
16345
+ },
16346
+ depth: {
16067
16347
  type: "number"
16068
16348
  },
16069
- changeSummary: {
16070
- nullable: true,
16349
+ position: {
16350
+ type: "number"
16351
+ },
16352
+ title: {
16071
16353
  type: "string"
16072
16354
  },
16073
- actorType: {
16355
+ sectionKey: {
16356
+ nullable: true,
16074
16357
  type: "string"
16075
16358
  },
16076
- actorId: {
16359
+ documentId: {
16360
+ nullable: true,
16077
16361
  type: "string"
16078
16362
  },
16079
- committedAt: {
16363
+ linearMilestoneId: {
16364
+ nullable: true,
16080
16365
  type: "string"
16081
16366
  },
16082
- documentCount: {
16083
- type: "number"
16367
+ startDate: {
16368
+ nullable: true,
16369
+ type: "string"
16084
16370
  },
16085
- documents: {
16086
- type: "array",
16087
- items: {
16088
- type: "object",
16089
- properties: {
16090
- documentId: {
16091
- type: "string"
16092
- },
16093
- nodeId: {
16094
- nullable: true,
16095
- type: "string"
16096
- },
16097
- title: {
16098
- type: "string"
16099
- },
16100
- revision: {
16101
- type: "number"
16102
- },
16103
- content: {
16104
- type: "string"
16105
- },
16106
- previousContent: {
16107
- nullable: true,
16108
- type: "string"
16109
- }
16110
- },
16111
- required: [
16112
- "documentId",
16113
- "nodeId",
16114
- "title",
16115
- "revision",
16116
- "content",
16117
- "previousContent"
16118
- ],
16119
- additionalProperties: false
16120
- }
16371
+ targetDate: {
16372
+ nullable: true,
16373
+ type: "string"
16121
16374
  },
16122
- links: {
16123
- type: "array",
16124
- items: {
16125
- type: "object",
16126
- properties: {
16127
- direction: {
16128
- type: "string"
16129
- },
16130
- kind: {
16131
- type: "string"
16132
- },
16133
- ref: {
16134
- type: "string"
16135
- },
16136
- url: {
16137
- nullable: true,
16138
- type: "string"
16139
- },
16140
- label: {
16141
- nullable: true,
16142
- type: "string"
16143
- }
16144
- },
16145
- required: [
16146
- "direction",
16147
- "kind",
16148
- "ref",
16149
- "url",
16150
- "label"
16151
- ],
16152
- additionalProperties: false
16153
- }
16375
+ dependsOnNodeId: {
16376
+ nullable: true,
16377
+ type: "string"
16154
16378
  }
16155
16379
  },
16156
16380
  required: [
16157
16381
  "id",
16158
- "revision",
16159
- "changeSummary",
16160
- "actorType",
16161
- "actorId",
16162
- "committedAt",
16163
- "documentCount",
16164
- "documents",
16165
- "links"
16382
+ "parentId",
16383
+ "depth",
16384
+ "position",
16385
+ "title",
16386
+ "sectionKey",
16387
+ "documentId",
16388
+ "linearMilestoneId",
16389
+ "startDate",
16390
+ "targetDate",
16391
+ "dependsOnNodeId"
16166
16392
  ],
16167
16393
  additionalProperties: false
16168
16394
  }
@@ -16179,6 +16405,16 @@ var init_spec = __esm(() => {
16179
16405
  }
16180
16406
  }
16181
16407
  },
16408
+ "403": {
16409
+ description: "The caller is not permitted to perform this action.",
16410
+ content: {
16411
+ "application/json": {
16412
+ schema: {
16413
+ $ref: "#/components/schemas/Error"
16414
+ }
16415
+ }
16416
+ }
16417
+ },
16182
16418
  "404": {
16183
16419
  description: "The resource was not found.",
16184
16420
  content: {
@@ -16189,6 +16425,16 @@ var init_spec = __esm(() => {
16189
16425
  }
16190
16426
  }
16191
16427
  },
16428
+ "422": {
16429
+ description: "Response.",
16430
+ content: {
16431
+ "application/json": {
16432
+ schema: {
16433
+ $ref: "#/components/schemas/Error"
16434
+ }
16435
+ }
16436
+ }
16437
+ },
16192
16438
  "429": {
16193
16439
  description: "Too many requests; retry after the indicated delay.",
16194
16440
  content: {
@@ -16200,12 +16446,12 @@ var init_spec = __esm(() => {
16200
16446
  }
16201
16447
  }
16202
16448
  },
16203
- summary: "Get a spec revision",
16204
- description: "Returns one committed spec revision: each bundled document's content alongside the previous revision's content, plus the cause and effect provenance edges attached to it.",
16449
+ summary: "Update a spec node",
16450
+ description: "Updates a node's title, sibling position, parent, or milestone plan. A move is applied first so a node that is becoming a phase can then accept dates. Each field is optional; omitted fields are left alone.",
16205
16451
  tags: [
16206
16452
  "Projects"
16207
16453
  ],
16208
- operationId: "getProjectSpecRevision",
16454
+ operationId: "updateProjectSpecNode",
16209
16455
  parameters: [
16210
16456
  {
16211
16457
  name: "projectId",
@@ -16217,16 +16463,60 @@ var init_spec = __esm(() => {
16217
16463
  }
16218
16464
  },
16219
16465
  {
16220
- name: "revision",
16466
+ name: "nodeId",
16221
16467
  in: "path",
16222
16468
  required: true,
16223
16469
  schema: {
16224
- type: "integer",
16225
- minimum: 1,
16226
- maximum: 9007199254740991
16470
+ type: "string",
16471
+ minLength: 1
16227
16472
  }
16228
16473
  }
16229
16474
  ],
16475
+ requestBody: {
16476
+ required: true,
16477
+ content: {
16478
+ "application/json": {
16479
+ schema: {
16480
+ type: "object",
16481
+ properties: {
16482
+ title: {
16483
+ type: "string",
16484
+ minLength: 1,
16485
+ maxLength: 200
16486
+ },
16487
+ position: {
16488
+ type: "integer",
16489
+ minimum: 0,
16490
+ maximum: 9007199254740991
16491
+ },
16492
+ parentId: {
16493
+ description: "Move the node under this parent before applying the other fields.",
16494
+ type: "string",
16495
+ minLength: 1
16496
+ },
16497
+ startDate: {
16498
+ description: "Milestone plan start. Only valid on a phase.",
16499
+ nullable: true,
16500
+ type: "string",
16501
+ pattern: "^\\d{4}-\\d{2}-\\d{2}(?:T[\\d:.Z+-]+)?$"
16502
+ },
16503
+ targetDate: {
16504
+ description: "Milestone plan target. Only valid on a phase.",
16505
+ nullable: true,
16506
+ type: "string",
16507
+ pattern: "^\\d{4}-\\d{2}-\\d{2}(?:T[\\d:.Z+-]+)?$"
16508
+ },
16509
+ dependsOnNodeId: {
16510
+ description: "Milestone this phase waits on. Only valid on a phase.",
16511
+ nullable: true,
16512
+ type: "string",
16513
+ minLength: 1
16514
+ }
16515
+ }
16516
+ }
16517
+ }
16518
+ }
16519
+ },
16230
16520
  security: [
16231
16521
  {
16232
16522
  bearerAuth: []
@@ -16235,39 +16525,444 @@ var init_spec = __esm(() => {
16235
16525
  apiKeyHeader: []
16236
16526
  }
16237
16527
  ]
16238
- }
16239
- },
16240
- "/api/v1/projects/{projectId}/members": {
16241
- get: {
16528
+ },
16529
+ delete: {
16242
16530
  responses: {
16243
- "200": {
16244
- description: "The project's members.",
16531
+ "204": {
16532
+ description: "The node was deleted."
16533
+ },
16534
+ "401": {
16535
+ description: "Authentication is missing or invalid.",
16245
16536
  content: {
16246
16537
  "application/json": {
16247
16538
  schema: {
16248
- type: "object",
16249
- properties: {
16250
- members: {
16251
- type: "array",
16252
- items: {
16253
- type: "object",
16254
- properties: {
16255
- id: {
16256
- type: "string"
16257
- },
16258
- resourceKind: {
16259
- type: "string"
16260
- },
16261
- resourceId: {
16262
- type: "string"
16263
- },
16264
- attachedAt: {
16265
- type: "string"
16266
- }
16267
- },
16268
- required: [
16269
- "id",
16270
- "resourceKind",
16539
+ $ref: "#/components/schemas/Error"
16540
+ }
16541
+ }
16542
+ }
16543
+ },
16544
+ "403": {
16545
+ description: "The caller is not permitted to perform this action.",
16546
+ content: {
16547
+ "application/json": {
16548
+ schema: {
16549
+ $ref: "#/components/schemas/Error"
16550
+ }
16551
+ }
16552
+ }
16553
+ },
16554
+ "404": {
16555
+ description: "The resource was not found.",
16556
+ content: {
16557
+ "application/json": {
16558
+ schema: {
16559
+ $ref: "#/components/schemas/Error"
16560
+ }
16561
+ }
16562
+ }
16563
+ },
16564
+ "422": {
16565
+ description: "Response.",
16566
+ content: {
16567
+ "application/json": {
16568
+ schema: {
16569
+ $ref: "#/components/schemas/Error"
16570
+ }
16571
+ }
16572
+ }
16573
+ },
16574
+ "429": {
16575
+ description: "Too many requests; retry after the indicated delay.",
16576
+ content: {
16577
+ "application/json": {
16578
+ schema: {
16579
+ $ref: "#/components/schemas/Error"
16580
+ }
16581
+ }
16582
+ }
16583
+ }
16584
+ },
16585
+ summary: "Delete a spec node",
16586
+ description: "Soft-deletes the node and its descendants. The goal node cannot be deleted — delete the project instead.",
16587
+ tags: [
16588
+ "Projects"
16589
+ ],
16590
+ operationId: "deleteProjectSpecNode",
16591
+ parameters: [
16592
+ {
16593
+ name: "projectId",
16594
+ in: "path",
16595
+ required: true,
16596
+ schema: {
16597
+ type: "string",
16598
+ minLength: 1
16599
+ }
16600
+ },
16601
+ {
16602
+ name: "nodeId",
16603
+ in: "path",
16604
+ required: true,
16605
+ schema: {
16606
+ type: "string",
16607
+ minLength: 1
16608
+ }
16609
+ }
16610
+ ],
16611
+ security: [
16612
+ {
16613
+ bearerAuth: []
16614
+ },
16615
+ {
16616
+ apiKeyHeader: []
16617
+ }
16618
+ ]
16619
+ }
16620
+ },
16621
+ "/api/v1/projects/{projectId}/revisions": {
16622
+ get: {
16623
+ responses: {
16624
+ "200": {
16625
+ description: "The project's committed spec revisions.",
16626
+ content: {
16627
+ "application/json": {
16628
+ schema: {
16629
+ type: "object",
16630
+ properties: {
16631
+ revisions: {
16632
+ type: "array",
16633
+ items: {
16634
+ type: "object",
16635
+ properties: {
16636
+ id: {
16637
+ type: "string"
16638
+ },
16639
+ revision: {
16640
+ type: "number"
16641
+ },
16642
+ changeSummary: {
16643
+ nullable: true,
16644
+ type: "string"
16645
+ },
16646
+ actorType: {
16647
+ type: "string"
16648
+ },
16649
+ actorId: {
16650
+ type: "string"
16651
+ },
16652
+ committedAt: {
16653
+ type: "string"
16654
+ },
16655
+ documentCount: {
16656
+ type: "number"
16657
+ }
16658
+ },
16659
+ required: [
16660
+ "id",
16661
+ "revision",
16662
+ "changeSummary",
16663
+ "actorType",
16664
+ "actorId",
16665
+ "committedAt",
16666
+ "documentCount"
16667
+ ],
16668
+ additionalProperties: false
16669
+ }
16670
+ }
16671
+ },
16672
+ required: [
16673
+ "revisions"
16674
+ ],
16675
+ additionalProperties: false
16676
+ }
16677
+ }
16678
+ }
16679
+ },
16680
+ "401": {
16681
+ description: "Authentication is missing or invalid.",
16682
+ content: {
16683
+ "application/json": {
16684
+ schema: {
16685
+ $ref: "#/components/schemas/Error"
16686
+ }
16687
+ }
16688
+ }
16689
+ },
16690
+ "404": {
16691
+ description: "The resource was not found.",
16692
+ content: {
16693
+ "application/json": {
16694
+ schema: {
16695
+ $ref: "#/components/schemas/Error"
16696
+ }
16697
+ }
16698
+ }
16699
+ },
16700
+ "429": {
16701
+ description: "Too many requests; retry after the indicated delay.",
16702
+ content: {
16703
+ "application/json": {
16704
+ schema: {
16705
+ $ref: "#/components/schemas/Error"
16706
+ }
16707
+ }
16708
+ }
16709
+ }
16710
+ },
16711
+ summary: "List spec revisions",
16712
+ description: "Returns the project's committed spec revisions newest-first, with actor, timestamp, change summary, and the number of documents each revision bundled.",
16713
+ tags: [
16714
+ "Projects"
16715
+ ],
16716
+ operationId: "listProjectSpecRevisions",
16717
+ parameters: [
16718
+ {
16719
+ name: "projectId",
16720
+ in: "path",
16721
+ required: true,
16722
+ schema: {
16723
+ type: "string",
16724
+ minLength: 1
16725
+ }
16726
+ },
16727
+ {
16728
+ name: "limit",
16729
+ in: "query",
16730
+ required: false,
16731
+ schema: {
16732
+ type: "integer",
16733
+ minimum: 1,
16734
+ maximum: 200
16735
+ }
16736
+ }
16737
+ ],
16738
+ security: [
16739
+ {
16740
+ bearerAuth: []
16741
+ },
16742
+ {
16743
+ apiKeyHeader: []
16744
+ }
16745
+ ]
16746
+ }
16747
+ },
16748
+ "/api/v1/projects/{projectId}/revisions/{revision}": {
16749
+ get: {
16750
+ responses: {
16751
+ "200": {
16752
+ description: "The revision with its diff and provenance.",
16753
+ content: {
16754
+ "application/json": {
16755
+ schema: {
16756
+ type: "object",
16757
+ properties: {
16758
+ id: {
16759
+ type: "string"
16760
+ },
16761
+ revision: {
16762
+ type: "number"
16763
+ },
16764
+ changeSummary: {
16765
+ nullable: true,
16766
+ type: "string"
16767
+ },
16768
+ actorType: {
16769
+ type: "string"
16770
+ },
16771
+ actorId: {
16772
+ type: "string"
16773
+ },
16774
+ committedAt: {
16775
+ type: "string"
16776
+ },
16777
+ documentCount: {
16778
+ type: "number"
16779
+ },
16780
+ documents: {
16781
+ type: "array",
16782
+ items: {
16783
+ type: "object",
16784
+ properties: {
16785
+ documentId: {
16786
+ type: "string"
16787
+ },
16788
+ nodeId: {
16789
+ nullable: true,
16790
+ type: "string"
16791
+ },
16792
+ title: {
16793
+ type: "string"
16794
+ },
16795
+ revision: {
16796
+ type: "number"
16797
+ },
16798
+ content: {
16799
+ type: "string"
16800
+ },
16801
+ previousContent: {
16802
+ nullable: true,
16803
+ type: "string"
16804
+ }
16805
+ },
16806
+ required: [
16807
+ "documentId",
16808
+ "nodeId",
16809
+ "title",
16810
+ "revision",
16811
+ "content",
16812
+ "previousContent"
16813
+ ],
16814
+ additionalProperties: false
16815
+ }
16816
+ },
16817
+ links: {
16818
+ type: "array",
16819
+ items: {
16820
+ type: "object",
16821
+ properties: {
16822
+ direction: {
16823
+ type: "string"
16824
+ },
16825
+ kind: {
16826
+ type: "string"
16827
+ },
16828
+ ref: {
16829
+ type: "string"
16830
+ },
16831
+ url: {
16832
+ nullable: true,
16833
+ type: "string"
16834
+ },
16835
+ label: {
16836
+ nullable: true,
16837
+ type: "string"
16838
+ }
16839
+ },
16840
+ required: [
16841
+ "direction",
16842
+ "kind",
16843
+ "ref",
16844
+ "url",
16845
+ "label"
16846
+ ],
16847
+ additionalProperties: false
16848
+ }
16849
+ }
16850
+ },
16851
+ required: [
16852
+ "id",
16853
+ "revision",
16854
+ "changeSummary",
16855
+ "actorType",
16856
+ "actorId",
16857
+ "committedAt",
16858
+ "documentCount",
16859
+ "documents",
16860
+ "links"
16861
+ ],
16862
+ additionalProperties: false
16863
+ }
16864
+ }
16865
+ }
16866
+ },
16867
+ "401": {
16868
+ description: "Authentication is missing or invalid.",
16869
+ content: {
16870
+ "application/json": {
16871
+ schema: {
16872
+ $ref: "#/components/schemas/Error"
16873
+ }
16874
+ }
16875
+ }
16876
+ },
16877
+ "404": {
16878
+ description: "The resource was not found.",
16879
+ content: {
16880
+ "application/json": {
16881
+ schema: {
16882
+ $ref: "#/components/schemas/Error"
16883
+ }
16884
+ }
16885
+ }
16886
+ },
16887
+ "429": {
16888
+ description: "Too many requests; retry after the indicated delay.",
16889
+ content: {
16890
+ "application/json": {
16891
+ schema: {
16892
+ $ref: "#/components/schemas/Error"
16893
+ }
16894
+ }
16895
+ }
16896
+ }
16897
+ },
16898
+ summary: "Get a spec revision",
16899
+ description: "Returns one committed spec revision: each bundled document's content alongside the previous revision's content, plus the cause and effect provenance edges attached to it.",
16900
+ tags: [
16901
+ "Projects"
16902
+ ],
16903
+ operationId: "getProjectSpecRevision",
16904
+ parameters: [
16905
+ {
16906
+ name: "projectId",
16907
+ in: "path",
16908
+ required: true,
16909
+ schema: {
16910
+ type: "string",
16911
+ minLength: 1
16912
+ }
16913
+ },
16914
+ {
16915
+ name: "revision",
16916
+ in: "path",
16917
+ required: true,
16918
+ schema: {
16919
+ type: "integer",
16920
+ minimum: 1,
16921
+ maximum: 9007199254740991
16922
+ }
16923
+ }
16924
+ ],
16925
+ security: [
16926
+ {
16927
+ bearerAuth: []
16928
+ },
16929
+ {
16930
+ apiKeyHeader: []
16931
+ }
16932
+ ]
16933
+ }
16934
+ },
16935
+ "/api/v1/projects/{projectId}/members": {
16936
+ get: {
16937
+ responses: {
16938
+ "200": {
16939
+ description: "The project's members.",
16940
+ content: {
16941
+ "application/json": {
16942
+ schema: {
16943
+ type: "object",
16944
+ properties: {
16945
+ members: {
16946
+ type: "array",
16947
+ items: {
16948
+ type: "object",
16949
+ properties: {
16950
+ id: {
16951
+ type: "string"
16952
+ },
16953
+ resourceKind: {
16954
+ type: "string"
16955
+ },
16956
+ resourceId: {
16957
+ type: "string"
16958
+ },
16959
+ attachedAt: {
16960
+ type: "string"
16961
+ }
16962
+ },
16963
+ required: [
16964
+ "id",
16965
+ "resourceKind",
16271
16966
  "resourceId",
16272
16967
  "attachedAt"
16273
16968
  ],
@@ -16276,15 +16971,920 @@ var init_spec = __esm(() => {
16276
16971
  }
16277
16972
  },
16278
16973
  required: [
16279
- "members"
16974
+ "members"
16975
+ ],
16976
+ additionalProperties: false
16977
+ }
16978
+ }
16979
+ }
16980
+ },
16981
+ "401": {
16982
+ description: "Authentication is missing or invalid.",
16983
+ content: {
16984
+ "application/json": {
16985
+ schema: {
16986
+ $ref: "#/components/schemas/Error"
16987
+ }
16988
+ }
16989
+ }
16990
+ },
16991
+ "404": {
16992
+ description: "The resource was not found.",
16993
+ content: {
16994
+ "application/json": {
16995
+ schema: {
16996
+ $ref: "#/components/schemas/Error"
16997
+ }
16998
+ }
16999
+ }
17000
+ },
17001
+ "429": {
17002
+ description: "Too many requests; retry after the indicated delay.",
17003
+ content: {
17004
+ "application/json": {
17005
+ schema: {
17006
+ $ref: "#/components/schemas/Error"
17007
+ }
17008
+ }
17009
+ }
17010
+ }
17011
+ },
17012
+ summary: "List project members",
17013
+ description: "Returns the embedded apps, pipelines, and databases attached to this project.",
17014
+ tags: [
17015
+ "Projects"
17016
+ ],
17017
+ operationId: "listProjectMembers",
17018
+ parameters: [
17019
+ {
17020
+ name: "projectId",
17021
+ in: "path",
17022
+ required: true,
17023
+ schema: {
17024
+ type: "string",
17025
+ minLength: 1
17026
+ }
17027
+ }
17028
+ ],
17029
+ security: [
17030
+ {
17031
+ bearerAuth: []
17032
+ },
17033
+ {
17034
+ apiKeyHeader: []
17035
+ }
17036
+ ]
17037
+ },
17038
+ post: {
17039
+ responses: {
17040
+ "201": {
17041
+ description: "The created membership.",
17042
+ content: {
17043
+ "application/json": {
17044
+ schema: {
17045
+ type: "object",
17046
+ properties: {
17047
+ id: {
17048
+ type: "string"
17049
+ },
17050
+ resourceKind: {
17051
+ type: "string"
17052
+ },
17053
+ resourceId: {
17054
+ type: "string"
17055
+ },
17056
+ attachedAt: {
17057
+ type: "string"
17058
+ }
17059
+ },
17060
+ required: [
17061
+ "id",
17062
+ "resourceKind",
17063
+ "resourceId",
17064
+ "attachedAt"
17065
+ ],
17066
+ additionalProperties: false
17067
+ }
17068
+ }
17069
+ }
17070
+ },
17071
+ "401": {
17072
+ description: "Authentication is missing or invalid.",
17073
+ content: {
17074
+ "application/json": {
17075
+ schema: {
17076
+ $ref: "#/components/schemas/Error"
17077
+ }
17078
+ }
17079
+ }
17080
+ },
17081
+ "403": {
17082
+ description: "The caller is not permitted to perform this action.",
17083
+ content: {
17084
+ "application/json": {
17085
+ schema: {
17086
+ $ref: "#/components/schemas/Error"
17087
+ }
17088
+ }
17089
+ }
17090
+ },
17091
+ "404": {
17092
+ description: "The resource was not found.",
17093
+ content: {
17094
+ "application/json": {
17095
+ schema: {
17096
+ $ref: "#/components/schemas/Error"
17097
+ }
17098
+ }
17099
+ }
17100
+ },
17101
+ "409": {
17102
+ description: "Response.",
17103
+ content: {
17104
+ "application/json": {
17105
+ schema: {
17106
+ $ref: "#/components/schemas/Error"
17107
+ }
17108
+ }
17109
+ }
17110
+ },
17111
+ "429": {
17112
+ description: "Too many requests; retry after the indicated delay.",
17113
+ content: {
17114
+ "application/json": {
17115
+ schema: {
17116
+ $ref: "#/components/schemas/Error"
17117
+ }
17118
+ }
17119
+ }
17120
+ }
17121
+ },
17122
+ summary: "Attach a resource to a project",
17123
+ description: "Attaches an embedded app, pipeline, or database. A resource already attached elsewhere returns 409 until it is detached.",
17124
+ tags: [
17125
+ "Projects"
17126
+ ],
17127
+ operationId: "attachProjectMember",
17128
+ parameters: [
17129
+ {
17130
+ name: "projectId",
17131
+ in: "path",
17132
+ required: true,
17133
+ schema: {
17134
+ type: "string",
17135
+ minLength: 1
17136
+ }
17137
+ }
17138
+ ],
17139
+ requestBody: {
17140
+ required: true,
17141
+ content: {
17142
+ "application/json": {
17143
+ schema: {
17144
+ type: "object",
17145
+ properties: {
17146
+ resourceKind: {
17147
+ type: "string",
17148
+ enum: [
17149
+ "app",
17150
+ "pipeline",
17151
+ "database"
17152
+ ]
17153
+ },
17154
+ resourceId: {
17155
+ type: "string",
17156
+ minLength: 1
17157
+ }
17158
+ },
17159
+ required: [
17160
+ "resourceKind",
17161
+ "resourceId"
17162
+ ]
17163
+ }
17164
+ }
17165
+ }
17166
+ },
17167
+ security: [
17168
+ {
17169
+ bearerAuth: []
17170
+ },
17171
+ {
17172
+ apiKeyHeader: []
17173
+ }
17174
+ ]
17175
+ },
17176
+ delete: {
17177
+ responses: {
17178
+ "204": {
17179
+ description: "The membership was removed."
17180
+ },
17181
+ "401": {
17182
+ description: "Authentication is missing or invalid.",
17183
+ content: {
17184
+ "application/json": {
17185
+ schema: {
17186
+ $ref: "#/components/schemas/Error"
17187
+ }
17188
+ }
17189
+ }
17190
+ },
17191
+ "403": {
17192
+ description: "The caller is not permitted to perform this action.",
17193
+ content: {
17194
+ "application/json": {
17195
+ schema: {
17196
+ $ref: "#/components/schemas/Error"
17197
+ }
17198
+ }
17199
+ }
17200
+ },
17201
+ "404": {
17202
+ description: "The resource was not found.",
17203
+ content: {
17204
+ "application/json": {
17205
+ schema: {
17206
+ $ref: "#/components/schemas/Error"
17207
+ }
17208
+ }
17209
+ }
17210
+ },
17211
+ "429": {
17212
+ description: "Too many requests; retry after the indicated delay.",
17213
+ content: {
17214
+ "application/json": {
17215
+ schema: {
17216
+ $ref: "#/components/schemas/Error"
17217
+ }
17218
+ }
17219
+ }
17220
+ }
17221
+ },
17222
+ summary: "Detach a resource from a project",
17223
+ description: "Removes the membership. The underlying app, pipeline, or database is untouched.",
17224
+ tags: [
17225
+ "Projects"
17226
+ ],
17227
+ operationId: "detachProjectMember",
17228
+ parameters: [
17229
+ {
17230
+ name: "projectId",
17231
+ in: "path",
17232
+ required: true,
17233
+ schema: {
17234
+ type: "string",
17235
+ minLength: 1
17236
+ }
17237
+ }
17238
+ ],
17239
+ requestBody: {
17240
+ required: true,
17241
+ content: {
17242
+ "application/json": {
17243
+ schema: {
17244
+ type: "object",
17245
+ properties: {
17246
+ resourceKind: {
17247
+ type: "string",
17248
+ enum: [
17249
+ "app",
17250
+ "pipeline",
17251
+ "database"
17252
+ ]
17253
+ },
17254
+ resourceId: {
17255
+ type: "string",
17256
+ minLength: 1
17257
+ }
17258
+ },
17259
+ required: [
17260
+ "resourceKind",
17261
+ "resourceId"
17262
+ ]
17263
+ }
17264
+ }
17265
+ }
17266
+ },
17267
+ security: [
17268
+ {
17269
+ bearerAuth: []
17270
+ },
17271
+ {
17272
+ apiKeyHeader: []
17273
+ }
17274
+ ]
17275
+ }
17276
+ },
17277
+ "/api/v1/projects/{projectId}/changes": {
17278
+ post: {
17279
+ responses: {
17280
+ "200": {
17281
+ description: "The issue tracking this change.",
17282
+ content: {
17283
+ "application/json": {
17284
+ schema: {
17285
+ type: "object",
17286
+ properties: {
17287
+ projectId: {
17288
+ type: "string"
17289
+ },
17290
+ changeId: {
17291
+ type: "string"
17292
+ },
17293
+ issueId: {
17294
+ type: "string"
17295
+ },
17296
+ issueUrl: {
17297
+ nullable: true,
17298
+ type: "string"
17299
+ },
17300
+ identifier: {
17301
+ nullable: true,
17302
+ description: "Linear's human key, e.g. ADE-12.",
17303
+ type: "string"
17304
+ },
17305
+ created: {
17306
+ type: "boolean",
17307
+ description: "False when an existing issue was updated."
17308
+ },
17309
+ documentsWritten: {
17310
+ type: "number"
17311
+ }
17312
+ },
17313
+ required: [
17314
+ "projectId",
17315
+ "changeId",
17316
+ "issueId",
17317
+ "issueUrl",
17318
+ "identifier",
17319
+ "created",
17320
+ "documentsWritten"
17321
+ ],
17322
+ additionalProperties: false
17323
+ }
17324
+ }
17325
+ }
17326
+ },
17327
+ "401": {
17328
+ description: "Authentication is missing or invalid.",
17329
+ content: {
17330
+ "application/json": {
17331
+ schema: {
17332
+ $ref: "#/components/schemas/Error"
17333
+ }
17334
+ }
17335
+ }
17336
+ },
17337
+ "403": {
17338
+ description: "The caller is not permitted to perform this action.",
17339
+ content: {
17340
+ "application/json": {
17341
+ schema: {
17342
+ $ref: "#/components/schemas/Error"
17343
+ }
17344
+ }
17345
+ }
17346
+ },
17347
+ "404": {
17348
+ description: "The resource was not found.",
17349
+ content: {
17350
+ "application/json": {
17351
+ schema: {
17352
+ $ref: "#/components/schemas/Error"
17353
+ }
17354
+ }
17355
+ }
17356
+ },
17357
+ "422": {
17358
+ description: "Response.",
17359
+ content: {
17360
+ "application/json": {
17361
+ schema: {
17362
+ $ref: "#/components/schemas/Error"
17363
+ }
17364
+ }
17365
+ }
17366
+ },
17367
+ "429": {
17368
+ description: "Too many requests; retry after the indicated delay.",
17369
+ content: {
17370
+ "application/json": {
17371
+ schema: {
17372
+ $ref: "#/components/schemas/Error"
17373
+ }
17374
+ }
17375
+ }
17376
+ }
17377
+ },
17378
+ summary: "File a change as an issue",
17379
+ description: "Creates or updates the issue tracking one OpenSpec change directory under this project. Khotan owns the issue's title and description; its state, assignee, priority, and dates stay with the tracker. Idempotent by change id.",
17380
+ tags: [
17381
+ "Projects"
17382
+ ],
17383
+ operationId: "syncProjectChange",
17384
+ parameters: [
17385
+ {
17386
+ name: "projectId",
17387
+ in: "path",
17388
+ required: true,
17389
+ schema: {
17390
+ type: "string",
17391
+ minLength: 1
17392
+ }
17393
+ }
17394
+ ],
17395
+ requestBody: {
17396
+ required: true,
17397
+ content: {
17398
+ "application/json": {
17399
+ schema: {
17400
+ type: "object",
17401
+ properties: {
17402
+ changeId: {
17403
+ type: "string",
17404
+ minLength: 1,
17405
+ maxLength: 200,
17406
+ description: "The change directory name. Stable identity for the issue, so renaming the directory files a second one."
17407
+ },
17408
+ title: {
17409
+ type: "string",
17410
+ minLength: 1,
17411
+ maxLength: 200
17412
+ },
17413
+ body: {
17414
+ type: "string",
17415
+ maxLength: 1e6
17416
+ },
17417
+ capabilities: {
17418
+ default: [],
17419
+ description: "Capabilities this change amends, applied as labels in one managed group. Labels outside that group are untouched.",
17420
+ maxItems: 20,
17421
+ type: "array",
17422
+ items: {
17423
+ type: "string",
17424
+ minLength: 1,
17425
+ maxLength: 80
17426
+ }
17427
+ },
17428
+ documents: {
17429
+ default: [],
17430
+ description: "Long parts of the change — design and requirements — attached as documents rather than inlined in the description.",
17431
+ maxItems: 20,
17432
+ type: "array",
17433
+ items: {
17434
+ type: "object",
17435
+ properties: {
17436
+ key: {
17437
+ type: "string",
17438
+ minLength: 1,
17439
+ maxLength: 80,
17440
+ description: "Stable key for this document, so a re-push updates it rather than adding another."
17441
+ },
17442
+ title: {
17443
+ type: "string",
17444
+ minLength: 1,
17445
+ maxLength: 200
17446
+ },
17447
+ content: {
17448
+ type: "string",
17449
+ maxLength: 1e6
17450
+ }
17451
+ },
17452
+ required: [
17453
+ "key",
17454
+ "title",
17455
+ "content"
17456
+ ]
17457
+ }
17458
+ },
17459
+ source: {
17460
+ description: "Where the change came from. A url is attached to the issue as a link.",
17461
+ type: "object",
17462
+ properties: {
17463
+ ref: {
17464
+ type: "string",
17465
+ minLength: 1,
17466
+ maxLength: 200
17467
+ },
17468
+ url: {
17469
+ nullable: true,
17470
+ type: "string",
17471
+ format: "uri"
17472
+ },
17473
+ label: {
17474
+ nullable: true,
17475
+ type: "string",
17476
+ maxLength: 200
17477
+ }
17478
+ },
17479
+ required: [
17480
+ "ref"
17481
+ ]
17482
+ }
17483
+ },
17484
+ required: [
17485
+ "changeId",
17486
+ "title",
17487
+ "body"
17488
+ ]
17489
+ }
17490
+ }
17491
+ }
17492
+ },
17493
+ security: [
17494
+ {
17495
+ bearerAuth: []
17496
+ },
17497
+ {
17498
+ apiKeyHeader: []
17499
+ }
17500
+ ]
17501
+ }
17502
+ },
17503
+ "/api/v1/projects/{projectId}/linear": {
17504
+ post: {
17505
+ responses: {
17506
+ "200": {
17507
+ description: "The Linear project now bound, or null if cleared.",
17508
+ content: {
17509
+ "application/json": {
17510
+ schema: {
17511
+ type: "object",
17512
+ properties: {
17513
+ bound: {
17514
+ nullable: true,
17515
+ type: "string"
17516
+ }
17517
+ },
17518
+ required: [
17519
+ "bound"
17520
+ ],
17521
+ additionalProperties: false
17522
+ }
17523
+ }
17524
+ }
17525
+ },
17526
+ "401": {
17527
+ description: "Authentication is missing or invalid.",
17528
+ content: {
17529
+ "application/json": {
17530
+ schema: {
17531
+ $ref: "#/components/schemas/Error"
17532
+ }
17533
+ }
17534
+ }
17535
+ },
17536
+ "403": {
17537
+ description: "The caller is not permitted to perform this action.",
17538
+ content: {
17539
+ "application/json": {
17540
+ schema: {
17541
+ $ref: "#/components/schemas/Error"
17542
+ }
17543
+ }
17544
+ }
17545
+ },
17546
+ "404": {
17547
+ description: "The resource was not found.",
17548
+ content: {
17549
+ "application/json": {
17550
+ schema: {
17551
+ $ref: "#/components/schemas/Error"
17552
+ }
17553
+ }
17554
+ }
17555
+ },
17556
+ "422": {
17557
+ description: "Response.",
17558
+ content: {
17559
+ "application/json": {
17560
+ schema: {
17561
+ $ref: "#/components/schemas/Error"
17562
+ }
17563
+ }
17564
+ }
17565
+ },
17566
+ "429": {
17567
+ description: "Too many requests; retry after the indicated delay.",
17568
+ content: {
17569
+ "application/json": {
17570
+ schema: {
17571
+ $ref: "#/components/schemas/Error"
17572
+ }
17573
+ }
17574
+ }
17575
+ }
17576
+ },
17577
+ summary: "Bind a project to Linear",
17578
+ description: "Binds this project to an existing Linear project, or clears the binding when linearProjectId is null. A new project mints its Linear project on first push, so this is only needed when the binding must point at one that already exists.",
17579
+ tags: [
17580
+ "Projects"
17581
+ ],
17582
+ operationId: "bindLinearProject",
17583
+ parameters: [
17584
+ {
17585
+ name: "projectId",
17586
+ in: "path",
17587
+ required: true,
17588
+ schema: {
17589
+ type: "string",
17590
+ minLength: 1
17591
+ }
17592
+ }
17593
+ ],
17594
+ requestBody: {
17595
+ required: true,
17596
+ content: {
17597
+ "application/json": {
17598
+ schema: {
17599
+ type: "object",
17600
+ properties: {
17601
+ linearProjectId: {
17602
+ nullable: true,
17603
+ description: "The Linear project to bind. Null clears the binding.",
17604
+ type: "string",
17605
+ minLength: 1
17606
+ }
17607
+ },
17608
+ required: [
17609
+ "linearProjectId"
17610
+ ]
17611
+ }
17612
+ }
17613
+ }
17614
+ },
17615
+ security: [
17616
+ {
17617
+ bearerAuth: []
17618
+ },
17619
+ {
17620
+ apiKeyHeader: []
17621
+ }
17622
+ ]
17623
+ }
17624
+ },
17625
+ "/api/v1/sqlite-databases": {
17626
+ get: {
17627
+ responses: {
17628
+ "200": {
17629
+ description: "The organization's SQLite databases.",
17630
+ content: {
17631
+ "application/json": {
17632
+ schema: {
17633
+ type: "object",
17634
+ properties: {
17635
+ databases: {
17636
+ type: "array",
17637
+ items: {
17638
+ type: "object",
17639
+ properties: {
17640
+ id: {
17641
+ type: "string"
17642
+ },
17643
+ displayName: {
17644
+ type: "string"
17645
+ },
17646
+ status: {
17647
+ type: "string",
17648
+ enum: [
17649
+ "creating",
17650
+ "ready",
17651
+ "failed"
17652
+ ]
17653
+ },
17654
+ provider: {
17655
+ type: "string"
17656
+ },
17657
+ primaryRegion: {
17658
+ nullable: true,
17659
+ type: "string"
17660
+ },
17661
+ regions: {
17662
+ nullable: true,
17663
+ type: "array",
17664
+ items: {
17665
+ type: "string"
17666
+ }
17667
+ },
17668
+ providerDatabaseName: {
17669
+ nullable: true,
17670
+ type: "string"
17671
+ },
17672
+ endpointHost: {
17673
+ nullable: true,
17674
+ type: "string"
17675
+ },
17676
+ protected: {
17677
+ type: "boolean"
17678
+ },
17679
+ createdAt: {
17680
+ type: "string"
17681
+ },
17682
+ updatedAt: {
17683
+ type: "string"
17684
+ },
17685
+ readyAt: {
17686
+ nullable: true,
17687
+ type: "string"
17688
+ },
17689
+ failedAt: {
17690
+ nullable: true,
17691
+ type: "string"
17692
+ },
17693
+ failureState: {
17694
+ nullable: true,
17695
+ type: "object",
17696
+ properties: {
17697
+ code: {
17698
+ type: "string",
17699
+ enum: [
17700
+ "provider_unavailable",
17701
+ "provider_rejected",
17702
+ "internal_error"
17703
+ ]
17704
+ },
17705
+ message: {
17706
+ type: "string"
17707
+ }
17708
+ },
17709
+ required: [
17710
+ "code",
17711
+ "message"
17712
+ ],
17713
+ additionalProperties: false
17714
+ }
17715
+ },
17716
+ required: [
17717
+ "id",
17718
+ "displayName",
17719
+ "status",
17720
+ "provider",
17721
+ "primaryRegion",
17722
+ "regions",
17723
+ "providerDatabaseName",
17724
+ "endpointHost",
17725
+ "protected",
17726
+ "createdAt",
17727
+ "updatedAt",
17728
+ "readyAt",
17729
+ "failedAt",
17730
+ "failureState"
17731
+ ],
17732
+ additionalProperties: false
17733
+ }
17734
+ }
17735
+ },
17736
+ required: [
17737
+ "databases"
17738
+ ],
17739
+ additionalProperties: false
17740
+ }
17741
+ }
17742
+ }
17743
+ },
17744
+ "401": {
17745
+ description: "Authentication is missing or invalid.",
17746
+ content: {
17747
+ "application/json": {
17748
+ schema: {
17749
+ $ref: "#/components/schemas/Error"
17750
+ }
17751
+ }
17752
+ }
17753
+ },
17754
+ "429": {
17755
+ description: "Too many requests; retry after the indicated delay.",
17756
+ content: {
17757
+ "application/json": {
17758
+ schema: {
17759
+ $ref: "#/components/schemas/Error"
17760
+ }
17761
+ }
17762
+ }
17763
+ }
17764
+ },
17765
+ summary: "List SQLite databases",
17766
+ description: "Returns the organization's non-deleted SQLite databases. Responses never include connection credentials.",
17767
+ tags: [
17768
+ "SQLite Databases"
17769
+ ],
17770
+ operationId: "listSqliteDatabases",
17771
+ security: [
17772
+ {
17773
+ bearerAuth: []
17774
+ },
17775
+ {
17776
+ apiKeyHeader: []
17777
+ }
17778
+ ]
17779
+ },
17780
+ post: {
17781
+ responses: {
17782
+ "201": {
17783
+ description: "The persisted SQLite database record, ready or failed.",
17784
+ content: {
17785
+ "application/json": {
17786
+ schema: {
17787
+ type: "object",
17788
+ properties: {
17789
+ id: {
17790
+ type: "string"
17791
+ },
17792
+ displayName: {
17793
+ type: "string"
17794
+ },
17795
+ status: {
17796
+ type: "string",
17797
+ enum: [
17798
+ "creating",
17799
+ "ready",
17800
+ "failed"
17801
+ ]
17802
+ },
17803
+ provider: {
17804
+ type: "string"
17805
+ },
17806
+ primaryRegion: {
17807
+ nullable: true,
17808
+ type: "string"
17809
+ },
17810
+ regions: {
17811
+ nullable: true,
17812
+ type: "array",
17813
+ items: {
17814
+ type: "string"
17815
+ }
17816
+ },
17817
+ providerDatabaseName: {
17818
+ nullable: true,
17819
+ type: "string"
17820
+ },
17821
+ endpointHost: {
17822
+ nullable: true,
17823
+ type: "string"
17824
+ },
17825
+ protected: {
17826
+ type: "boolean"
17827
+ },
17828
+ createdAt: {
17829
+ type: "string"
17830
+ },
17831
+ updatedAt: {
17832
+ type: "string"
17833
+ },
17834
+ readyAt: {
17835
+ nullable: true,
17836
+ type: "string"
17837
+ },
17838
+ failedAt: {
17839
+ nullable: true,
17840
+ type: "string"
17841
+ },
17842
+ failureState: {
17843
+ nullable: true,
17844
+ type: "object",
17845
+ properties: {
17846
+ code: {
17847
+ type: "string",
17848
+ enum: [
17849
+ "provider_unavailable",
17850
+ "provider_rejected",
17851
+ "internal_error"
17852
+ ]
17853
+ },
17854
+ message: {
17855
+ type: "string"
17856
+ }
17857
+ },
17858
+ required: [
17859
+ "code",
17860
+ "message"
17861
+ ],
17862
+ additionalProperties: false
17863
+ }
17864
+ },
17865
+ required: [
17866
+ "id",
17867
+ "displayName",
17868
+ "status",
17869
+ "provider",
17870
+ "primaryRegion",
17871
+ "regions",
17872
+ "providerDatabaseName",
17873
+ "endpointHost",
17874
+ "protected",
17875
+ "createdAt",
17876
+ "updatedAt",
17877
+ "readyAt",
17878
+ "failedAt",
17879
+ "failureState"
16280
17880
  ],
16281
17881
  additionalProperties: false
16282
17882
  }
16283
17883
  }
16284
17884
  }
16285
17885
  },
16286
- "401": {
16287
- description: "Authentication is missing or invalid.",
17886
+ "400": {
17887
+ description: "The request was malformed or failed validation.",
16288
17888
  content: {
16289
17889
  "application/json": {
16290
17890
  schema: {
@@ -16293,8 +17893,8 @@ var init_spec = __esm(() => {
16293
17893
  }
16294
17894
  }
16295
17895
  },
16296
- "404": {
16297
- description: "The resource was not found.",
17896
+ "401": {
17897
+ description: "Authentication is missing or invalid.",
16298
17898
  content: {
16299
17899
  "application/json": {
16300
17900
  schema: {
@@ -16314,23 +17914,37 @@ var init_spec = __esm(() => {
16314
17914
  }
16315
17915
  }
16316
17916
  },
16317
- summary: "List project members",
16318
- description: "Returns the embedded apps, pipelines, and databases attached to this project.",
17917
+ summary: "Create a SQLite database",
17918
+ description: "Provisions a SQLite database synchronously and responds 201 with the persisted record whether provisioning ends ready or failed; branch on the returned status. This operation is not idempotent: retrying a request that already succeeded provisions a second database.",
16319
17919
  tags: [
16320
- "Projects"
17920
+ "SQLite Databases"
16321
17921
  ],
16322
- operationId: "listProjectMembers",
16323
- parameters: [
16324
- {
16325
- name: "projectId",
16326
- in: "path",
16327
- required: true,
16328
- schema: {
16329
- type: "string",
16330
- minLength: 1
17922
+ operationId: "createSqliteDatabase",
17923
+ requestBody: {
17924
+ required: true,
17925
+ content: {
17926
+ "application/json": {
17927
+ schema: {
17928
+ type: "object",
17929
+ properties: {
17930
+ name: {
17931
+ type: "string",
17932
+ minLength: 1,
17933
+ maxLength: 63
17934
+ },
17935
+ idempotencyKey: {
17936
+ type: "string",
17937
+ minLength: 1,
17938
+ maxLength: 200
17939
+ }
17940
+ },
17941
+ required: [
17942
+ "name"
17943
+ ]
17944
+ }
16331
17945
  }
16332
17946
  }
16333
- ],
17947
+ },
16334
17948
  security: [
16335
17949
  {
16336
17950
  bearerAuth: []
@@ -16339,11 +17953,13 @@ var init_spec = __esm(() => {
16339
17953
  apiKeyHeader: []
16340
17954
  }
16341
17955
  ]
16342
- },
16343
- post: {
17956
+ }
17957
+ },
17958
+ "/api/v1/sqlite-databases/{databaseId}": {
17959
+ get: {
16344
17960
  responses: {
16345
- "201": {
16346
- description: "The created membership.",
17961
+ "200": {
17962
+ description: "The SQLite database record.",
16347
17963
  content: {
16348
17964
  "application/json": {
16349
17965
  schema: {
@@ -16352,21 +17968,94 @@ var init_spec = __esm(() => {
16352
17968
  id: {
16353
17969
  type: "string"
16354
17970
  },
16355
- resourceKind: {
17971
+ displayName: {
16356
17972
  type: "string"
16357
17973
  },
16358
- resourceId: {
17974
+ status: {
17975
+ type: "string",
17976
+ enum: [
17977
+ "creating",
17978
+ "ready",
17979
+ "failed"
17980
+ ]
17981
+ },
17982
+ provider: {
16359
17983
  type: "string"
16360
17984
  },
16361
- attachedAt: {
17985
+ primaryRegion: {
17986
+ nullable: true,
17987
+ type: "string"
17988
+ },
17989
+ regions: {
17990
+ nullable: true,
17991
+ type: "array",
17992
+ items: {
17993
+ type: "string"
17994
+ }
17995
+ },
17996
+ providerDatabaseName: {
17997
+ nullable: true,
17998
+ type: "string"
17999
+ },
18000
+ endpointHost: {
18001
+ nullable: true,
18002
+ type: "string"
18003
+ },
18004
+ protected: {
18005
+ type: "boolean"
18006
+ },
18007
+ createdAt: {
18008
+ type: "string"
18009
+ },
18010
+ updatedAt: {
18011
+ type: "string"
18012
+ },
18013
+ readyAt: {
18014
+ nullable: true,
18015
+ type: "string"
18016
+ },
18017
+ failedAt: {
18018
+ nullable: true,
16362
18019
  type: "string"
18020
+ },
18021
+ failureState: {
18022
+ nullable: true,
18023
+ type: "object",
18024
+ properties: {
18025
+ code: {
18026
+ type: "string",
18027
+ enum: [
18028
+ "provider_unavailable",
18029
+ "provider_rejected",
18030
+ "internal_error"
18031
+ ]
18032
+ },
18033
+ message: {
18034
+ type: "string"
18035
+ }
18036
+ },
18037
+ required: [
18038
+ "code",
18039
+ "message"
18040
+ ],
18041
+ additionalProperties: false
16363
18042
  }
16364
18043
  },
16365
18044
  required: [
16366
18045
  "id",
16367
- "resourceKind",
16368
- "resourceId",
16369
- "attachedAt"
18046
+ "displayName",
18047
+ "status",
18048
+ "provider",
18049
+ "primaryRegion",
18050
+ "regions",
18051
+ "providerDatabaseName",
18052
+ "endpointHost",
18053
+ "protected",
18054
+ "createdAt",
18055
+ "updatedAt",
18056
+ "readyAt",
18057
+ "failedAt",
18058
+ "failureState"
16370
18059
  ],
16371
18060
  additionalProperties: false
16372
18061
  }
@@ -16383,16 +18072,6 @@ var init_spec = __esm(() => {
16383
18072
  }
16384
18073
  }
16385
18074
  },
16386
- "403": {
16387
- description: "The caller is not permitted to perform this action.",
16388
- content: {
16389
- "application/json": {
16390
- schema: {
16391
- $ref: "#/components/schemas/Error"
16392
- }
16393
- }
16394
- }
16395
- },
16396
18075
  "404": {
16397
18076
  description: "The resource was not found.",
16398
18077
  content: {
@@ -16403,16 +18082,6 @@ var init_spec = __esm(() => {
16403
18082
  }
16404
18083
  }
16405
18084
  },
16406
- "409": {
16407
- description: "Response.",
16408
- content: {
16409
- "application/json": {
16410
- schema: {
16411
- $ref: "#/components/schemas/Error"
16412
- }
16413
- }
16414
- }
16415
- },
16416
18085
  "429": {
16417
18086
  description: "Too many requests; retry after the indicated delay.",
16418
18087
  content: {
@@ -16424,15 +18093,15 @@ var init_spec = __esm(() => {
16424
18093
  }
16425
18094
  }
16426
18095
  },
16427
- summary: "Attach a resource to a project",
16428
- description: "Attaches an embedded app, pipeline, or database. A resource already attached elsewhere returns 409 until it is detached.",
18096
+ summary: "Get a SQLite database",
18097
+ description: "Returns one SQLite database's record, including status and failure state when present. Responses never include connection credentials.",
16429
18098
  tags: [
16430
- "Projects"
18099
+ "SQLite Databases"
16431
18100
  ],
16432
- operationId: "attachProjectMember",
18101
+ operationId: "getSqliteDatabase",
16433
18102
  parameters: [
16434
18103
  {
16435
- name: "projectId",
18104
+ name: "databaseId",
16436
18105
  in: "path",
16437
18106
  required: true,
16438
18107
  schema: {
@@ -16441,34 +18110,6 @@ var init_spec = __esm(() => {
16441
18110
  }
16442
18111
  }
16443
18112
  ],
16444
- requestBody: {
16445
- required: true,
16446
- content: {
16447
- "application/json": {
16448
- schema: {
16449
- type: "object",
16450
- properties: {
16451
- resourceKind: {
16452
- type: "string",
16453
- enum: [
16454
- "app",
16455
- "pipeline",
16456
- "database"
16457
- ]
16458
- },
16459
- resourceId: {
16460
- type: "string",
16461
- minLength: 1
16462
- }
16463
- },
16464
- required: [
16465
- "resourceKind",
16466
- "resourceId"
16467
- ]
16468
- }
16469
- }
16470
- }
16471
- },
16472
18113
  security: [
16473
18114
  {
16474
18115
  bearerAuth: []
@@ -16480,8 +18121,27 @@ var init_spec = __esm(() => {
16480
18121
  },
16481
18122
  delete: {
16482
18123
  responses: {
16483
- "204": {
16484
- description: "The membership was removed."
18124
+ "200": {
18125
+ description: "The SQLite database was deleted.",
18126
+ content: {
18127
+ "application/json": {
18128
+ schema: {
18129
+ type: "object",
18130
+ properties: {
18131
+ deleted: {
18132
+ type: "boolean",
18133
+ enum: [
18134
+ true
18135
+ ]
18136
+ }
18137
+ },
18138
+ required: [
18139
+ "deleted"
18140
+ ],
18141
+ additionalProperties: false
18142
+ }
18143
+ }
18144
+ }
16485
18145
  },
16486
18146
  "401": {
16487
18147
  description: "Authentication is missing or invalid.",
@@ -16522,17 +18182,27 @@ var init_spec = __esm(() => {
16522
18182
  }
16523
18183
  }
16524
18184
  }
18185
+ },
18186
+ "502": {
18187
+ description: "Response.",
18188
+ content: {
18189
+ "application/json": {
18190
+ schema: {
18191
+ $ref: "#/components/schemas/Error"
18192
+ }
18193
+ }
18194
+ }
16525
18195
  }
16526
18196
  },
16527
- summary: "Detach a resource from a project",
16528
- description: "Removes the membership. The underlying app, pipeline, or database is untouched.",
18197
+ summary: "Delete a SQLite database",
18198
+ description: "Tears down the database at the provider, then soft-deletes the record. Deletion is permanent.",
16529
18199
  tags: [
16530
- "Projects"
18200
+ "SQLite Databases"
16531
18201
  ],
16532
- operationId: "detachProjectMember",
18202
+ operationId: "deleteSqliteDatabase",
16533
18203
  parameters: [
16534
18204
  {
16535
- name: "projectId",
18205
+ name: "databaseId",
16536
18206
  in: "path",
16537
18207
  required: true,
16538
18208
  schema: {
@@ -16541,34 +18211,6 @@ var init_spec = __esm(() => {
16541
18211
  }
16542
18212
  }
16543
18213
  ],
16544
- requestBody: {
16545
- required: true,
16546
- content: {
16547
- "application/json": {
16548
- schema: {
16549
- type: "object",
16550
- properties: {
16551
- resourceKind: {
16552
- type: "string",
16553
- enum: [
16554
- "app",
16555
- "pipeline",
16556
- "database"
16557
- ]
16558
- },
16559
- resourceId: {
16560
- type: "string",
16561
- minLength: 1
16562
- }
16563
- },
16564
- required: [
16565
- "resourceKind",
16566
- "resourceId"
16567
- ]
16568
- }
16569
- }
16570
- }
16571
- },
16572
18214
  security: [
16573
18215
  {
16574
18216
  bearerAuth: []
@@ -16663,8 +18305,19 @@ function apiTags() {
16663
18305
  function operationsForTag(tag) {
16664
18306
  return listApiOperations().filter(({ operation }) => operation.tags?.includes(tag));
16665
18307
  }
18308
+ function preferOrder(items, preferred) {
18309
+ const rank = new Map(preferred.map((item, index) => [item, index]));
18310
+ return [...items].sort((a, b) => {
18311
+ const left = rank.get(a) ?? preferred.length;
18312
+ const right = rank.get(b) ?? preferred.length;
18313
+ if (left !== right) {
18314
+ return left - right;
18315
+ }
18316
+ return items.indexOf(a) - items.indexOf(b);
18317
+ });
18318
+ }
16666
18319
  function referenceTags() {
16667
- return apiTags().filter((tag) => !OVERVIEW_TAGS.has(tag));
18320
+ return preferOrder(apiTags().filter((tag) => !OVERVIEW_TAGS.has(tag)), REFERENCE_TAG_ORDER);
16668
18321
  }
16669
18322
  function typeLabel(schema) {
16670
18323
  if (!schema) {
@@ -16783,7 +18436,7 @@ function authSection() {
16783
18436
  const how = scheme.type === "http" && scheme.scheme === "bearer" ? "`Authorization: Bearer <key>`" : scheme.in === "header" && scheme.name ? `\`${scheme.name}: <key>\`` : `\`${name}\``;
16784
18437
  lines.push(`- ${how} — ${scheme.description ?? name}`);
16785
18438
  }
16786
- lines.push("", "Never commit a key. See [auth](/cli/auth/) for how the CLI stores and selects keys.");
18439
+ lines.push("", "Never commit a key. See [authentication](/api/auth/) for how keys are scoped, stored, and managed, or [CLI auth](/cli/auth/) for how the CLI selects between them.");
16787
18440
  return lines;
16788
18441
  }
16789
18442
  function errorSection() {
@@ -16796,9 +18449,9 @@ function errorSection() {
16796
18449
  '{ "error": { "code": "not_found", "message": "App not found.", "details": {} } }',
16797
18450
  "```",
16798
18451
  "",
16799
- "`401` means the key is missing or invalid, `403` means the key's organization cannot reach the resource, `404` means it does not exist, `400` means the request failed validation, and `429` means a rate limit was exceeded. Rate limits are applied per key; back off and retry on `429`.",
18452
+ "`401` means the key is missing or invalid, `403` means the key's organization cannot reach the resource, `404` means it does not exist, `400` means the request failed validation, and `429` means a rate limit was exceeded. Rate limits are applied per key; back off and retry on `429`, which carries a `Retry-After` header.",
16800
18453
  "",
16801
- "See [errors](/cli/errors/) for how the CLI surfaces these."
18454
+ "See [conventions](/api/conventions/) for retries, idempotency, and the rest of the shared contract, or [CLI errors](/cli/errors/) for how the CLI surfaces these."
16802
18455
  ];
16803
18456
  }
16804
18457
  function coreOperationsSection() {
@@ -16826,14 +18479,16 @@ function renderApiOverviewMarkdown() {
16826
18479
  "",
16827
18480
  "## Base URL",
16828
18481
  "",
16829
- "The API is served from your workspace origin:",
18482
+ `Every request goes to \`${API_ORIGIN_EXAMPLE}\`:`,
16830
18483
  "",
16831
18484
  "```bash",
16832
18485
  `curl ${API_ORIGIN_EXAMPLE}/api/v1/apps \\`,
16833
18486
  ' -H "Authorization: Bearer $KHOTAN_API_KEY"',
16834
18487
  "```",
16835
18488
  "",
16836
- `The machine-readable document is at [\`${OPENAPI_SPEC_PATH}\`](${API_ORIGIN_EXAMPLE}${OPENAPI_SPEC_PATH}) on the same origin. Generate a client from that rather than scraping these pages; they summarize each operation and omit nested response schemas.`,
18489
+ "There is no per-workspace API host. A key carries its own organization and no operation reads the request host, so the base URL never varies by account or workspace.",
18490
+ "",
18491
+ `The machine-readable document is at [\`${OPENAPI_SPEC_PATH}\`](${API_ORIGIN_EXAMPLE}${OPENAPI_SPEC_PATH}) on the same origin, and is also published on this site for reading without an account. Generate a client from the document rather than scraping these pages; they summarize each operation and omit nested response schemas.`,
16837
18492
  "",
16838
18493
  ...authSection(),
16839
18494
  "",
@@ -16851,7 +18506,7 @@ function renderApiOverviewMarkdown() {
16851
18506
  return lines.join(`
16852
18507
  `);
16853
18508
  }
16854
- var apiSpec, OPENAPI_SPEC_PATH = "/api/v1/openapi.json", API_ORIGIN_EXAMPLE = "https://<workspace>.khotan.com", HTTP_METHODS2, OVERVIEW_TAGS;
18509
+ var apiSpec, OPENAPI_SPEC_PATH = "/api/v1/openapi.json", API_ORIGIN_EXAMPLE = "https://app.khotan.com", HTTP_METHODS2, OVERVIEW_TAGS, REFERENCE_TAG_ORDER;
16855
18510
  var init_reference2 = __esm(() => {
16856
18511
  init_validate();
16857
18512
  init_version();
@@ -16863,6 +18518,18 @@ var init_reference2 = __esm(() => {
16863
18518
  "Identity",
16864
18519
  "API keys"
16865
18520
  ]);
18521
+ REFERENCE_TAG_ORDER = [
18522
+ "Apps",
18523
+ "Pipelines",
18524
+ "Files",
18525
+ "Context",
18526
+ "Integrations",
18527
+ "Databases",
18528
+ "Redis Databases",
18529
+ "SQLite Databases",
18530
+ "Folders",
18531
+ "Projects"
18532
+ ];
16866
18533
  });
16867
18534
 
16868
18535
  // ../khotan-core/src/client/api-client.ts
@@ -16949,6 +18616,40 @@ function createApiClient(context) {
16949
18616
  buildRequest(capabilityOrId, input = {}) {
16950
18617
  return buildHttpRequest(capabilityOrId, input, context);
16951
18618
  },
18619
+ async request(method, path, body) {
18620
+ if (!context.apiKey) {
18621
+ throw new KhotanClientError("not_authenticated", `${method} ${path} requires authentication but no API key is configured.`);
18622
+ }
18623
+ const headers = {
18624
+ accept: "application/json",
18625
+ "x-api-key": context.apiKey
18626
+ };
18627
+ if (body !== undefined) {
18628
+ headers["content-type"] = "application/json";
18629
+ }
18630
+ let response;
18631
+ try {
18632
+ response = await doFetch(`${normalizeApiUrl(context.apiUrl)}${path}`, {
18633
+ method,
18634
+ headers,
18635
+ body: body === undefined ? undefined : JSON.stringify(body)
18636
+ });
18637
+ } catch (error) {
18638
+ throw new KhotanClientError("network_error", `Could not reach the Khotan API at ${context.apiUrl}: ${error instanceof Error ? error.message : String(error)}`);
18639
+ }
18640
+ const rawBody = await response.text();
18641
+ if (!response.ok) {
18642
+ throw toApiError(response.status, rawBody);
18643
+ }
18644
+ if (!rawBody) {
18645
+ return null;
18646
+ }
18647
+ try {
18648
+ return JSON.parse(rawBody);
18649
+ } catch {
18650
+ throw new KhotanClientError("invalid_response", `The Khotan API returned a non-JSON success response for ${method} ${path}.`);
18651
+ }
18652
+ },
16952
18653
  async execute(capabilityOrId, input = {}) {
16953
18654
  const capability = resolveCapability(capabilityOrId);
16954
18655
  const request = buildHttpRequest(capability, input, context);
@@ -17135,77 +18836,442 @@ var init_repo_env = __esm(() => {
17135
18836
  OVERLAY_KEYS = [ENV_API_URL, ENV_API_KEY, ENV_PROFILE, ENV_ORG_ID];
17136
18837
  });
17137
18838
 
17138
- // ../khotan-core/src/index.ts
17139
- var exports_src = {};
17140
- __export(exports_src, {
17141
- validateCatalogStructure: () => validateCatalogStructure,
17142
- validateCatalogAgainstOpenApi: () => validateCatalogAgainstOpenApi,
17143
- tagSlug: () => tagSlug,
17144
- setProfile: () => setProfile,
17145
- selectProfile: () => selectProfile,
17146
- saveProfileStore: () => saveProfileStore,
17147
- resolveProfile: () => resolveProfile,
17148
- requiresConfirmation: () => requiresConfirmation,
17149
- renderTagReferenceMarkdown: () => renderTagReferenceMarkdown,
17150
- renderOperation: () => renderOperation,
17151
- renderDomainCommandsMarkdown: () => renderDomainCommandsMarkdown,
17152
- renderApiOverviewMarkdown: () => renderApiOverviewMarkdown,
17153
- renderApiOperation: () => renderApiOperation,
17154
- referenceTags: () => referenceTags,
17155
- parseRepoEnvFile: () => parseRepoEnvFile,
17156
- overlayRepoEnv: () => overlayRepoEnv,
17157
- operationsForTag: () => operationsForTag,
17158
- operationsForDomain: () => operationsForDomain,
17159
- openApiStamp: () => openApiStamp,
17160
- loadProfileStore: () => loadProfileStore,
17161
- listDomains: () => listDomains,
17162
- listCapabilities: () => listCapabilities,
17163
- listApiOperations: () => listApiOperations,
17164
- khotanCatalog: () => khotanCatalog,
17165
- isSecret: () => isSecret,
17166
- isReadOnly: () => isReadOnly,
17167
- isDestructive: () => isDestructive,
17168
- indexOpenApiOperations: () => indexOpenApiOperations,
17169
- getProfileStorePath: () => getProfileStorePath,
17170
- getCapability: () => getCapability,
17171
- findRepoEnvFile: () => findRepoEnvFile,
17172
- fileTransferSection: () => fileTransferSection,
17173
- fieldFlag: () => fieldFlag,
17174
- domainTitle: () => domainTitle,
17175
- domainLabel: () => domainLabel,
17176
- createApiClient: () => createApiClient,
17177
- commandUsage: () => commandUsage,
17178
- commandDomains: () => commandDomains,
17179
- catalogStamp: () => catalogStamp,
17180
- capabilitiesByOperationId: () => capabilitiesByOperationId,
17181
- camelToKebab: () => camelToKebab,
17182
- buildHttpRequest: () => buildHttpRequest,
17183
- assertSupportedCatalogSchemaVersion: () => assertSupportedCatalogSchemaVersion,
17184
- apiVersion: () => apiVersion,
17185
- apiTags: () => apiTags,
17186
- apiSpec: () => apiSpec,
17187
- SUPPORTED_CATALOG_SCHEMA_VERSIONS: () => SUPPORTED_CATALOG_SCHEMA_VERSIONS,
17188
- SAFETY_LEVELS: () => SAFETY_LEVELS,
17189
- REPO_ENV_FILENAME: () => REPO_ENV_FILENAME,
17190
- OVERVIEW_TAGS: () => OVERVIEW_TAGS,
17191
- OVERVIEW_DOMAINS: () => OVERVIEW_DOMAINS,
17192
- OPENAPI_SPEC_PATH: () => OPENAPI_SPEC_PATH,
17193
- KhotanUnknownCapabilityError: () => KhotanUnknownCapabilityError,
17194
- KhotanError: () => KhotanError,
17195
- KhotanConfirmationRequiredError: () => KhotanConfirmationRequiredError,
17196
- KhotanClientError: () => KhotanClientError,
17197
- KhotanCatalogVersionError: () => KhotanCatalogVersionError,
17198
- KhotanApiError: () => KhotanApiError,
17199
- KHOTAN_ADAPTER_VERSION: () => KHOTAN_ADAPTER_VERSION,
17200
- KHOTAN_ADAPTER_NAME: () => KHOTAN_ADAPTER_NAME,
17201
- ENV_PROFILE: () => ENV_PROFILE,
17202
- ENV_ORG_ID: () => ENV_ORG_ID,
17203
- ENV_API_URL: () => ENV_API_URL,
17204
- ENV_API_KEY: () => ENV_API_KEY,
17205
- CATALOG_VERSION: () => CATALOG_VERSION,
17206
- CATALOG_SCHEMA_VERSION: () => CATALOG_SCHEMA_VERSION,
18839
+ // ../khotan-core/src/openspec/change.ts
18840
+ function splitMarkdownSections(markdown) {
18841
+ const sections = new Map;
18842
+ let heading;
18843
+ let lines = [];
18844
+ const flush = () => {
18845
+ if (heading !== undefined) {
18846
+ sections.set(heading.toLowerCase(), lines.join(`
18847
+ `).trim());
18848
+ }
18849
+ };
18850
+ for (const line of markdown.split(`
18851
+ `)) {
18852
+ const match = SECTION_HEADING.exec(line);
18853
+ if (match && !line.startsWith("###")) {
18854
+ flush();
18855
+ heading = match[1];
18856
+ lines = [];
18857
+ continue;
18858
+ }
18859
+ lines.push(line);
18860
+ }
18861
+ flush();
18862
+ return sections;
18863
+ }
18864
+ function parseTasks(markdown) {
18865
+ const phases = [];
18866
+ let current;
18867
+ for (const line of markdown.split(`
18868
+ `)) {
18869
+ if (line.startsWith("## ")) {
18870
+ const match = PHASE_HEADING.exec(line);
18871
+ const title = match?.[2]?.trim();
18872
+ if (title) {
18873
+ current = { number: match?.[1], title, items: [] };
18874
+ phases.push(current);
18875
+ }
18876
+ continue;
18877
+ }
18878
+ const task = TASK_LINE.exec(line);
18879
+ if (task && current) {
18880
+ const title = task[3]?.trim();
18881
+ if (title) {
18882
+ current.items.push({
18883
+ number: task[2],
18884
+ title,
18885
+ done: task[1]?.toLowerCase() === "x"
18886
+ });
18887
+ }
18888
+ }
18889
+ }
18890
+ return phases;
18891
+ }
18892
+ function parseSpecDelta(delta) {
18893
+ const requirements = [];
18894
+ let verb = "ADDED";
18895
+ let current;
18896
+ let awaitingStatement = false;
18897
+ let pending = [];
18898
+ const flushStatement = () => {
18899
+ if (current && pending.length > 0) {
18900
+ current.statement = pending.join(" ");
18901
+ awaitingStatement = false;
18902
+ }
18903
+ pending = [];
18904
+ };
18905
+ for (const line of delta.content.split(`
18906
+ `)) {
18907
+ const deltaHeading = DELTA_HEADING.exec(line);
18908
+ if (deltaHeading?.[1]) {
18909
+ flushStatement();
18910
+ verb = deltaHeading[1].toUpperCase();
18911
+ current = undefined;
18912
+ awaitingStatement = false;
18913
+ continue;
18914
+ }
18915
+ const requirement = REQUIREMENT_HEADING.exec(line);
18916
+ if (requirement?.[1]) {
18917
+ flushStatement();
18918
+ current = {
18919
+ capability: delta.capability,
18920
+ verb,
18921
+ title: requirement[1],
18922
+ scenarios: []
18923
+ };
18924
+ requirements.push(current);
18925
+ awaitingStatement = true;
18926
+ continue;
18927
+ }
18928
+ const scenario = SCENARIO_HEADING.exec(line);
18929
+ if (scenario?.[1] && current) {
18930
+ flushStatement();
18931
+ current.scenarios.push(scenario[1]);
18932
+ awaitingStatement = false;
18933
+ continue;
18934
+ }
18935
+ if (awaitingStatement && current) {
18936
+ if (line.trim()) {
18937
+ pending.push(line.trim());
18938
+ } else {
18939
+ flushStatement();
18940
+ }
18941
+ }
18942
+ }
18943
+ flushStatement();
18944
+ return requirements;
18945
+ }
18946
+ function renderChangeIssue(changeId, sources) {
18947
+ const proposal = sources.proposal?.trim();
18948
+ const design = sources.design?.trim();
18949
+ const documents = [];
18950
+ const parts = [];
18951
+ if (proposal) {
18952
+ parts.push(proposal);
18953
+ }
18954
+ const phases = parseTasks(sources.tasks ?? "");
18955
+ if (phases.length > 0) {
18956
+ parts.push(renderTasks(phases));
18957
+ }
18958
+ const requirements = (sources.specs ?? []).flatMap(parseSpecDelta);
18959
+ if (requirements.length > 0) {
18960
+ documents.push({
18961
+ key: "requirements",
18962
+ title: "Requirements",
18963
+ content: renderRequirements(requirements)
18964
+ });
18965
+ }
18966
+ if (design) {
18967
+ documents.push({ key: "design", title: "Design", content: design });
18968
+ }
18969
+ const footer = [`OpenSpec change: \`openspec/changes/${changeId}\``];
18970
+ if (documents.length > 0) {
18971
+ footer.push(`Also on this issue: ${documents.map((document) => document.title).join(" and ")}.`);
18972
+ }
18973
+ parts.push(`---
18974
+
18975
+ ${footer.join(`
18976
+
18977
+ `)}`);
18978
+ return {
18979
+ title: humanizeChangeId(changeId),
18980
+ body: parts.join(`
18981
+
18982
+ `),
18983
+ documents
18984
+ };
18985
+ }
18986
+ function renderTasks(phases) {
18987
+ const lines = [
18988
+ "# Tasks",
18989
+ "Tracked in `tasks.md`. Tick them there — this list is rewritten on every push."
18990
+ ];
18991
+ for (const phase of phases) {
18992
+ lines.push(`## ${phase.number ? `${phase.number}. ` : ""}${phase.title}`, phase.items.map((item) => {
18993
+ const box = item.done ? "- [x]" : "- [ ]";
18994
+ const number = item.number ? `${item.number} ` : "";
18995
+ return `${box} ${number}${item.title}`;
18996
+ }).join(`
18997
+ `));
18998
+ }
18999
+ return lines.filter(Boolean).join(`
19000
+
19001
+ `);
19002
+ }
19003
+ function renderRequirements(requirements) {
19004
+ const byCapability = new Map;
19005
+ for (const requirement of requirements) {
19006
+ const list = byCapability.get(requirement.capability) ?? [];
19007
+ list.push(requirement);
19008
+ byCapability.set(requirement.capability, list);
19009
+ }
19010
+ const lines = [];
19011
+ for (const [capability, group] of byCapability) {
19012
+ lines.push(`## ${capability}`);
19013
+ for (const requirement of group) {
19014
+ lines.push(`**${requirement.verb} — ${requirement.title}**`);
19015
+ if (requirement.statement) {
19016
+ lines.push(requirement.statement);
19017
+ }
19018
+ if (requirement.scenarios.length > 0) {
19019
+ lines.push(requirement.scenarios.map((title) => `- ${title}`).join(`
19020
+ `));
19021
+ }
19022
+ }
19023
+ }
19024
+ return lines.join(`
19025
+
19026
+ `);
19027
+ }
19028
+ function humanizeChangeId(id) {
19029
+ const words = id.replace(/[-_]+/g, " ").trim();
19030
+ return words.charAt(0).toUpperCase() + words.slice(1);
19031
+ }
19032
+ var SECTION_HEADING, PHASE_HEADING, TASK_LINE, DELTA_HEADING, REQUIREMENT_HEADING, SCENARIO_HEADING;
19033
+ var init_change = __esm(() => {
19034
+ SECTION_HEADING = /^##\s+(.+?)\s*$/;
19035
+ PHASE_HEADING = /^##\s+(?:(\d+(?:\.\d+)*)\.\s*)?(.+?)\s*$/;
19036
+ TASK_LINE = /^\s*-\s+\[([ xX])\]\s+(?:(\d+(?:\.\d+)+)\s+)?(.*)$/;
19037
+ DELTA_HEADING = /^##\s+(ADDED|MODIFIED|REMOVED|RENAMED)\s+Requirements\s*$/i;
19038
+ REQUIREMENT_HEADING = /^###\s+Requirement:\s*(.+?)\s*$/;
19039
+ SCENARIO_HEADING = /^####\s+Scenario:\s*(.+?)\s*$/;
19040
+ });
19041
+
19042
+ // ../khotan-core/src/openspec/config.ts
19043
+ import { existsSync as existsSync2, readFileSync as readFileSync3 } from "node:fs";
19044
+ import { join as join3 } from "node:path";
19045
+ function parseKhotanConfig(text) {
19046
+ const lines = text.split(`
19047
+ `);
19048
+ const start = lines.findIndex((line) => KHOTAN_BLOCK.test(line));
19049
+ if (start === -1) {
19050
+ return {};
19051
+ }
19052
+ for (const line of lines.slice(start + 1)) {
19053
+ if (line.trim().startsWith("#") || line.trim() === "") {
19054
+ continue;
19055
+ }
19056
+ if (TOP_LEVEL_KEY.test(line)) {
19057
+ break;
19058
+ }
19059
+ const project = PROJECT_ENTRY.exec(line)?.[1];
19060
+ if (project) {
19061
+ return { project: unquote(project) };
19062
+ }
19063
+ }
19064
+ return {};
19065
+ }
19066
+ function readKhotanConfig(openspecRoot) {
19067
+ const path = join3(openspecRoot, "config.yaml");
19068
+ if (!existsSync2(path)) {
19069
+ return {};
19070
+ }
19071
+ return parseKhotanConfig(readFileSync3(path, "utf8"));
19072
+ }
19073
+ function unquote(value) {
19074
+ const trimmed = value.trim();
19075
+ if (trimmed.startsWith('"') && trimmed.endsWith('"')) {
19076
+ return trimmed.slice(1, -1);
19077
+ }
19078
+ if (trimmed.startsWith("'") && trimmed.endsWith("'")) {
19079
+ return trimmed.slice(1, -1);
19080
+ }
19081
+ return trimmed.split("#")[0]?.trim() ?? "";
19082
+ }
19083
+ var KHOTAN_CONFIG_KEY = "khotan", KHOTAN_BLOCK, TOP_LEVEL_KEY, PROJECT_ENTRY;
19084
+ var init_config = __esm(() => {
19085
+ KHOTAN_BLOCK = /^khotan:\s*$/;
19086
+ TOP_LEVEL_KEY = /^\S/;
19087
+ PROJECT_ENTRY = /^\s+project:\s*(.+?)\s*$/;
19088
+ });
19089
+
19090
+ // ../khotan-core/src/openspec/source-url.ts
19091
+ function changeSourceUrl(input) {
19092
+ const parsed = parseRemote(input.remote);
19093
+ if (!parsed) {
19094
+ return;
19095
+ }
19096
+ const { host, path } = parsed;
19097
+ const ref = input.ref ?? "HEAD";
19098
+ const dir = `openspec/changes/${input.changeId}`;
19099
+ if (host === "github.com") {
19100
+ return `https://github.com/${path}/tree/${ref}/${dir}`;
19101
+ }
19102
+ if (host === "gitlab.com") {
19103
+ return `https://gitlab.com/${path}/-/tree/${ref}/${dir}`;
19104
+ }
19105
+ return;
19106
+ }
19107
+ function parseRemote(remote) {
19108
+ const trimmed = remote.trim();
19109
+ for (const pattern of [SSH_REMOTE, HTTPS_REMOTE]) {
19110
+ const match = pattern.exec(trimmed);
19111
+ const host = match?.[1];
19112
+ const path = match?.[2];
19113
+ if (host && path) {
19114
+ return { host: host.toLowerCase(), path: path.replace(/\/+$/, "") };
19115
+ }
19116
+ }
19117
+ return null;
19118
+ }
19119
+ var SSH_REMOTE, HTTPS_REMOTE;
19120
+ var init_source_url = __esm(() => {
19121
+ SSH_REMOTE = /^(?:ssh:\/\/)?git@([^:/]+)[:/](.+?)(?:\.git)?$/;
19122
+ HTTPS_REMOTE = /^https?:\/\/(?:[^@]+@)?([^/]+)\/(.+?)(?:\.git)?$/;
19123
+ });
19124
+
19125
+ // ../khotan-core/src/openspec/read.ts
19126
+ import { existsSync as existsSync3, readdirSync, readFileSync as readFileSync4, statSync } from "node:fs";
19127
+ import { dirname as dirname3, join as join4 } from "node:path";
19128
+ function findOpenSpecRoot(startDir) {
19129
+ let dir = startDir;
19130
+ for (;; ) {
19131
+ const candidate = join4(dir, OPENSPEC_DIRNAME);
19132
+ if (isDirectory(candidate)) {
19133
+ return candidate;
19134
+ }
19135
+ if (existsSync3(join4(dir, ".git"))) {
19136
+ return;
19137
+ }
19138
+ const parent = dirname3(dir);
19139
+ if (parent === dir) {
19140
+ return;
19141
+ }
19142
+ dir = parent;
19143
+ }
19144
+ }
19145
+ function listChangeIds(openspecRoot) {
19146
+ const changesDir = join4(openspecRoot, "changes");
19147
+ if (!isDirectory(changesDir)) {
19148
+ return [];
19149
+ }
19150
+ return readdirSync(changesDir).filter((entry) => entry !== "archive" && !entry.startsWith(".")).filter((entry) => isDirectory(join4(changesDir, entry))).sort();
19151
+ }
19152
+ function readChangeDirectory(openspecRoot, id) {
19153
+ const path = join4(openspecRoot, "changes", id);
19154
+ if (!isDirectory(path)) {
19155
+ throw new Error(`No change directory at ${path}`);
19156
+ }
19157
+ return {
19158
+ id,
19159
+ path,
19160
+ sources: {
19161
+ proposal: readIfPresent(join4(path, "proposal.md")),
19162
+ design: readIfPresent(join4(path, "design.md")),
19163
+ tasks: readIfPresent(join4(path, "tasks.md")),
19164
+ specs: readSpecDeltas(path)
19165
+ }
19166
+ };
19167
+ }
19168
+ function readSpecDeltas(changePath) {
19169
+ const specsDir = join4(changePath, "specs");
19170
+ if (!isDirectory(specsDir)) {
19171
+ return [];
19172
+ }
19173
+ return readdirSync(specsDir).filter((entry) => !entry.startsWith(".")).sort().flatMap((capability) => {
19174
+ const content = readIfPresent(join4(specsDir, capability, "spec.md"));
19175
+ return content ? [{ capability, content }] : [];
19176
+ });
19177
+ }
19178
+ function readIfPresent(path) {
19179
+ return existsSync3(path) ? readFileSync4(path, "utf8") : undefined;
19180
+ }
19181
+ function isDirectory(path) {
19182
+ try {
19183
+ return statSync(path).isDirectory();
19184
+ } catch {
19185
+ return false;
19186
+ }
19187
+ }
19188
+ var OPENSPEC_DIRNAME = "openspec";
19189
+ var init_read = () => {};
19190
+
19191
+ // ../khotan-core/src/index.ts
19192
+ var exports_src = {};
19193
+ __export(exports_src, {
19194
+ API_ORIGIN_EXAMPLE: () => API_ORIGIN_EXAMPLE,
17207
19195
  AUTH_LOGIN_CAPABILITY_ID: () => AUTH_LOGIN_CAPABILITY_ID,
17208
- API_ORIGIN_EXAMPLE: () => API_ORIGIN_EXAMPLE
19196
+ CATALOG_SCHEMA_VERSION: () => CATALOG_SCHEMA_VERSION,
19197
+ CATALOG_VERSION: () => CATALOG_VERSION,
19198
+ ENV_API_KEY: () => ENV_API_KEY,
19199
+ ENV_API_URL: () => ENV_API_URL,
19200
+ ENV_ORG_ID: () => ENV_ORG_ID,
19201
+ ENV_PROFILE: () => ENV_PROFILE,
19202
+ KHOTAN_ADAPTER_NAME: () => KHOTAN_ADAPTER_NAME,
19203
+ KHOTAN_ADAPTER_VERSION: () => KHOTAN_ADAPTER_VERSION,
19204
+ KHOTAN_CONFIG_KEY: () => KHOTAN_CONFIG_KEY,
19205
+ KhotanApiError: () => KhotanApiError,
19206
+ KhotanCatalogVersionError: () => KhotanCatalogVersionError,
19207
+ KhotanClientError: () => KhotanClientError,
19208
+ KhotanConfirmationRequiredError: () => KhotanConfirmationRequiredError,
19209
+ KhotanError: () => KhotanError,
19210
+ KhotanUnknownCapabilityError: () => KhotanUnknownCapabilityError,
19211
+ OPENAPI_SPEC_PATH: () => OPENAPI_SPEC_PATH,
19212
+ OPENSPEC_DIRNAME: () => OPENSPEC_DIRNAME,
19213
+ OVERVIEW_DOMAINS: () => OVERVIEW_DOMAINS,
19214
+ OVERVIEW_TAGS: () => OVERVIEW_TAGS,
19215
+ REPO_ENV_FILENAME: () => REPO_ENV_FILENAME,
19216
+ SAFETY_LEVELS: () => SAFETY_LEVELS,
19217
+ SUPPORTED_CATALOG_SCHEMA_VERSIONS: () => SUPPORTED_CATALOG_SCHEMA_VERSIONS,
19218
+ apiSpec: () => apiSpec,
19219
+ apiTags: () => apiTags,
19220
+ apiVersion: () => apiVersion,
19221
+ assertSupportedCatalogSchemaVersion: () => assertSupportedCatalogSchemaVersion,
19222
+ buildHttpRequest: () => buildHttpRequest,
19223
+ camelToKebab: () => camelToKebab,
19224
+ capabilitiesByOperationId: () => capabilitiesByOperationId,
19225
+ catalogStamp: () => catalogStamp,
19226
+ changeSourceUrl: () => changeSourceUrl,
19227
+ commandDomains: () => commandDomains,
19228
+ commandUsage: () => commandUsage,
19229
+ createApiClient: () => createApiClient,
19230
+ domainLabel: () => domainLabel,
19231
+ domainTitle: () => domainTitle,
19232
+ fieldFlag: () => fieldFlag,
19233
+ fileTransferSection: () => fileTransferSection,
19234
+ findOpenSpecRoot: () => findOpenSpecRoot,
19235
+ findRepoEnvFile: () => findRepoEnvFile,
19236
+ getCapability: () => getCapability,
19237
+ getProfileStorePath: () => getProfileStorePath,
19238
+ humanizeChangeId: () => humanizeChangeId,
19239
+ indexOpenApiOperations: () => indexOpenApiOperations,
19240
+ isDestructive: () => isDestructive,
19241
+ isReadOnly: () => isReadOnly,
19242
+ isSecret: () => isSecret,
19243
+ khotanCatalog: () => khotanCatalog,
19244
+ listApiOperations: () => listApiOperations,
19245
+ listCapabilities: () => listCapabilities,
19246
+ listChangeIds: () => listChangeIds,
19247
+ listDomains: () => listDomains,
19248
+ loadProfileStore: () => loadProfileStore,
19249
+ openApiStamp: () => openApiStamp,
19250
+ operationsForDomain: () => operationsForDomain,
19251
+ operationsForTag: () => operationsForTag,
19252
+ overlayRepoEnv: () => overlayRepoEnv,
19253
+ parseKhotanConfig: () => parseKhotanConfig,
19254
+ parseRepoEnvFile: () => parseRepoEnvFile,
19255
+ parseSpecDelta: () => parseSpecDelta,
19256
+ parseTasks: () => parseTasks,
19257
+ readChangeDirectory: () => readChangeDirectory,
19258
+ readKhotanConfig: () => readKhotanConfig,
19259
+ referenceTags: () => referenceTags,
19260
+ renderApiOperation: () => renderApiOperation,
19261
+ renderApiOverviewMarkdown: () => renderApiOverviewMarkdown,
19262
+ renderChangeIssue: () => renderChangeIssue,
19263
+ renderDomainCommandsMarkdown: () => renderDomainCommandsMarkdown,
19264
+ renderOperation: () => renderOperation,
19265
+ renderTagReferenceMarkdown: () => renderTagReferenceMarkdown,
19266
+ requiresConfirmation: () => requiresConfirmation,
19267
+ resolveProfile: () => resolveProfile,
19268
+ saveProfileStore: () => saveProfileStore,
19269
+ selectProfile: () => selectProfile,
19270
+ setProfile: () => setProfile,
19271
+ splitMarkdownSections: () => splitMarkdownSections,
19272
+ tagSlug: () => tagSlug,
19273
+ validateCatalogAgainstOpenApi: () => validateCatalogAgainstOpenApi,
19274
+ validateCatalogStructure: () => validateCatalogStructure
17209
19275
  });
17210
19276
  var init_src = __esm(() => {
17211
19277
  init_version();
@@ -17218,6 +19284,10 @@ var init_src = __esm(() => {
17218
19284
  init_api_client();
17219
19285
  init_profiles();
17220
19286
  init_repo_env();
19287
+ init_change();
19288
+ init_config();
19289
+ init_source_url();
19290
+ init_read();
17221
19291
  });
17222
19292
 
17223
19293
  // src/cli/run.ts
@@ -17262,7 +19332,7 @@ function parseArgs(argv) {
17262
19332
  continue;
17263
19333
  }
17264
19334
  const next = argv[i + 1];
17265
- if (next !== undefined && !next.startsWith("-")) {
19335
+ if (next !== undefined && (next === "-" || !next.startsWith("-"))) {
17266
19336
  addFlag(body, next);
17267
19337
  i++;
17268
19338
  } else {
@@ -17530,6 +19600,88 @@ async function whoami(ctx) {
17530
19600
  return 0;
17531
19601
  }
17532
19602
 
19603
+ // src/cli/commands/change-push.ts
19604
+ init_src();
19605
+ import { execFileSync } from "node:child_process";
19606
+ async function changePush(deps, options) {
19607
+ const { session, io, json } = deps;
19608
+ const root = findOpenSpecRoot(options.cwd ?? process.cwd());
19609
+ if (!root) {
19610
+ throw new KhotanClientError("not_found", "No openspec/ directory found at or above this directory.");
19611
+ }
19612
+ const project = options.project ?? readKhotanConfig(root).project;
19613
+ if (!project) {
19614
+ throw new KhotanClientError("missing_argument", "No contract project named. Add a `khotan:` block with `project: <slug>` to openspec/config.yaml, or pass --project.");
19615
+ }
19616
+ const changeId = options.changeId ?? soleChangeId(root);
19617
+ const change = readChangeDirectory(root, changeId);
19618
+ const issue = renderChangeIssue(changeId, change.sources);
19619
+ const capabilities2 = (change.sources.specs ?? []).map((delta) => delta.capability);
19620
+ const sourceUrl = resolveSourceUrl(change.path, changeId, options.ref);
19621
+ if (!issue.body.trim()) {
19622
+ throw new KhotanClientError("invalid_argument", `Change "${changeId}" has no proposal or design content to file.`);
19623
+ }
19624
+ if (options.dryRun) {
19625
+ renderResult(io, {
19626
+ project,
19627
+ changeId,
19628
+ openspecRoot: root,
19629
+ title: issue.title,
19630
+ bodyLength: issue.body.length,
19631
+ documents: issue.documents.map((document) => ({
19632
+ title: document.title,
19633
+ length: document.content.length
19634
+ })),
19635
+ capabilities: capabilities2,
19636
+ sourceUrl: sourceUrl ?? null
19637
+ }, { json });
19638
+ return 0;
19639
+ }
19640
+ if (!session) {
19641
+ throw new KhotanClientError("not_authenticated", "Filing a change needs an API key. Run `khotan login`, or pass --dry-run.");
19642
+ }
19643
+ const documentNote = issue.documents.length > 0 ? ` with ${issue.documents.map((document) => document.title.toLowerCase()).join(" and ")}` : "";
19644
+ errLine(io, `Filing ${changeId} under ${project}${documentNote}…`);
19645
+ const result = await session.client.request("POST", `/api/v1/projects/${encodeURIComponent(project)}/changes`, {
19646
+ changeId,
19647
+ title: issue.title,
19648
+ body: issue.body,
19649
+ capabilities: capabilities2,
19650
+ documents: issue.documents,
19651
+ ...options.ref || sourceUrl ? {
19652
+ source: {
19653
+ ref: options.ref ?? changeId,
19654
+ ...sourceUrl ? { url: sourceUrl } : {},
19655
+ label: `openspec/changes/${changeId}`
19656
+ }
19657
+ } : {}
19658
+ });
19659
+ renderResult(io, result, { json });
19660
+ return 0;
19661
+ }
19662
+ function resolveSourceUrl(changePath, changeId, ref) {
19663
+ try {
19664
+ const remote = execFileSync("git", ["remote", "get-url", "origin"], {
19665
+ cwd: changePath,
19666
+ encoding: "utf8",
19667
+ stdio: ["ignore", "pipe", "ignore"]
19668
+ });
19669
+ return changeSourceUrl({ remote, ref, changeId });
19670
+ } catch {
19671
+ return;
19672
+ }
19673
+ }
19674
+ function soleChangeId(openspecRoot) {
19675
+ const ids = listChangeIds(openspecRoot);
19676
+ if (ids.length === 1 && ids[0]) {
19677
+ return ids[0];
19678
+ }
19679
+ if (ids.length === 0) {
19680
+ throw new KhotanClientError("not_found", "No active changes under openspec/changes. Write one first.");
19681
+ }
19682
+ throw new KhotanClientError("missing_argument", `This repo has ${ids.length} active changes. Name one: ${ids.join(", ")}.`);
19683
+ }
19684
+
17533
19685
  // src/cli/commands/files-transfer.ts
17534
19686
  init_src();
17535
19687
  import { basename } from "node:path";
@@ -17602,9 +19754,65 @@ async function downloadFile(deps, options) {
17602
19754
  return 0;
17603
19755
  }
17604
19756
 
19757
+ // src/cli/commands/spec-commit.ts
19758
+ init_src();
19759
+ import { readFileSync as readFileSync5 } from "node:fs";
19760
+ async function specCommit(deps, options) {
19761
+ const { session, io, json } = deps;
19762
+ const raw = readCommitDocument(options.file);
19763
+ const document = parseCommitDocument(raw);
19764
+ if (options.expectedRevision !== undefined) {
19765
+ document.expectedRevision = options.expectedRevision;
19766
+ }
19767
+ if (options.changeSummary !== undefined) {
19768
+ document.changeSummary = options.changeSummary;
19769
+ }
19770
+ if (typeof document.expectedRevision !== "number") {
19771
+ throw new KhotanClientError("missing_argument", "A commit needs expectedRevision. Put it in the JSON document, or pass --expected-revision.");
19772
+ }
19773
+ if (!Array.isArray(document.edits) || document.edits.length === 0) {
19774
+ throw new KhotanClientError("invalid_argument", "A commit needs a non-empty edits array.");
19775
+ }
19776
+ errLine(io, `Committing ${document.edits.length} edit${document.edits.length === 1 ? "" : "s"} on ${options.projectId} at revision ${document.expectedRevision}…`);
19777
+ try {
19778
+ const result = await session.client.request("POST", `/api/v1/projects/${encodeURIComponent(options.projectId)}/spec`, document);
19779
+ renderResult(io, result, { json });
19780
+ return 0;
19781
+ } catch (error) {
19782
+ if (error instanceof KhotanApiError && error.code === "revision_conflict") {
19783
+ const expected = error.details?.expectedRevision;
19784
+ const actual = error.details?.actualRevision;
19785
+ throw new KhotanClientError("revision_conflict", `The spec changed since revision ${expected ?? document.expectedRevision}; current is ${actual ?? "?"}. Re-read with khotan projects spec, then commit again.`);
19786
+ }
19787
+ throw error;
19788
+ }
19789
+ }
19790
+ function readCommitDocument(file) {
19791
+ if (file === "-") {
19792
+ return readFileSync5(0, "utf8");
19793
+ }
19794
+ try {
19795
+ return readFileSync5(file, "utf8");
19796
+ } catch (error) {
19797
+ throw new KhotanClientError("not_found", `Could not read ${file}: ${error instanceof Error ? error.message : String(error)}`);
19798
+ }
19799
+ }
19800
+ function parseCommitDocument(raw) {
19801
+ let parsed;
19802
+ try {
19803
+ parsed = JSON.parse(raw);
19804
+ } catch {
19805
+ throw new KhotanClientError("invalid_argument", "The commit document is not valid JSON.");
19806
+ }
19807
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
19808
+ throw new KhotanClientError("invalid_argument", "The commit document must be a JSON object with an edits array.");
19809
+ }
19810
+ return parsed;
19811
+ }
19812
+
17605
19813
  // src/cli/commands/init.ts
17606
- import { existsSync as existsSync2, mkdirSync as mkdirSync2, readdirSync, readFileSync as readFileSync3, writeFileSync as writeFileSync2 } from "node:fs";
17607
- import { dirname as dirname3, join as join3, relative } from "node:path";
19814
+ import { existsSync as existsSync4, mkdirSync as mkdirSync2, readdirSync as readdirSync2, readFileSync as readFileSync6, writeFileSync as writeFileSync2 } from "node:fs";
19815
+ import { dirname as dirname4, join as join5, relative } from "node:path";
17608
19816
 
17609
19817
  // src/cli/skills.ts
17610
19818
  init_src();
@@ -17777,25 +19985,25 @@ function toRelative(cwd, absolutePath) {
17777
19985
  return relative(cwd, absolutePath) || absolutePath;
17778
19986
  }
17779
19987
  function detectMonoRoot(cwd) {
17780
- if (hasWorkspacesManifest(join3(cwd, "package.json")))
19988
+ if (hasWorkspacesManifest(join5(cwd, "package.json")))
17781
19989
  return true;
17782
- if (existsSync2(join3(cwd, "turbo.json")))
19990
+ if (existsSync4(join5(cwd, "turbo.json")))
17783
19991
  return true;
17784
- if (existsSync2(join3(cwd, "WORKSPACE.md")))
19992
+ if (existsSync4(join5(cwd, "WORKSPACE.md")))
17785
19993
  return true;
17786
- if (existsSync2(join3(cwd, "package.json")))
19994
+ if (existsSync4(join5(cwd, "package.json")))
17787
19995
  return false;
17788
19996
  try {
17789
- return readdirSync(cwd, { withFileTypes: true }).some((entry) => entry.isDirectory() && !entry.name.startsWith(".") && entry.name !== "node_modules" && existsSync2(join3(cwd, entry.name, "package.json")));
19997
+ return readdirSync2(cwd, { withFileTypes: true }).some((entry) => entry.isDirectory() && !entry.name.startsWith(".") && entry.name !== "node_modules" && existsSync4(join5(cwd, entry.name, "package.json")));
17790
19998
  } catch {
17791
19999
  return false;
17792
20000
  }
17793
20001
  }
17794
20002
  function hasWorkspacesManifest(packageJsonPath) {
17795
- if (!existsSync2(packageJsonPath))
20003
+ if (!existsSync4(packageJsonPath))
17796
20004
  return false;
17797
20005
  try {
17798
- const parsed = JSON.parse(readFileSync3(packageJsonPath, "utf8"));
20006
+ const parsed = JSON.parse(readFileSync6(packageJsonPath, "utf8"));
17799
20007
  if (typeof parsed !== "object" || parsed === null)
17800
20008
  return false;
17801
20009
  const workspaces = parsed.workspaces;
@@ -17806,19 +20014,19 @@ function hasWorkspacesManifest(packageJsonPath) {
17806
20014
  }
17807
20015
  function writeTextAsset(ctx, absolutePath, content) {
17808
20016
  const path = toRelative(ctx.cwd, absolutePath);
17809
- const existed = existsSync2(absolutePath);
20017
+ const existed = existsSync4(absolutePath);
17810
20018
  if (existed && !ctx.force) {
17811
20019
  ctx.results.push({ path, action: "skipped" });
17812
20020
  return;
17813
20021
  }
17814
- mkdirSync2(dirname3(absolutePath), { recursive: true });
20022
+ mkdirSync2(dirname4(absolutePath), { recursive: true });
17815
20023
  writeFileSync2(absolutePath, content);
17816
20024
  ctx.results.push({ path, action: existed ? "forced" : "created" });
17817
20025
  }
17818
20026
  function writeManagedAsset(ctx, absolutePath, content) {
17819
20027
  const path = toRelative(ctx.cwd, absolutePath);
17820
- const existed = existsSync2(absolutePath);
17821
- mkdirSync2(dirname3(absolutePath), { recursive: true });
20028
+ const existed = existsSync4(absolutePath);
20029
+ mkdirSync2(dirname4(absolutePath), { recursive: true });
17822
20030
  writeFileSync2(absolutePath, content);
17823
20031
  ctx.results.push({ path, action: existed ? "updated" : "created" });
17824
20032
  }
@@ -17826,13 +20034,13 @@ function runInit(options) {
17826
20034
  const { io, cwd, client, force, json } = options;
17827
20035
  const ctx = { cwd, force, results: [] };
17828
20036
  if (client === "cursor") {
17829
- writeTextAsset(ctx, join3(cwd, ".cursor", "rules", "khotan.mdc"), CURSOR_RULES);
20037
+ writeTextAsset(ctx, join5(cwd, ".cursor", "rules", "khotan.mdc"), CURSOR_RULES);
17830
20038
  } else {
17831
- writeTextAsset(ctx, join3(cwd, "khotan-agents.md"), GENERIC_GUIDE);
20039
+ writeTextAsset(ctx, join5(cwd, "khotan-agents.md"), GENERIC_GUIDE);
17832
20040
  }
17833
20041
  for (const skill of generateSkills()) {
17834
20042
  for (const base of SKILL_BASES) {
17835
- writeManagedAsset(ctx, join3(cwd, base, "skills", skill.name, "SKILL.md"), skill.content);
20043
+ writeManagedAsset(ctx, join5(cwd, base, "skills", skill.name, "SKILL.md"), skill.content);
17836
20044
  }
17837
20045
  }
17838
20046
  const monoRoot = detectMonoRoot(cwd);
@@ -17859,115 +20067,9 @@ function runInit(options) {
17859
20067
  return 0;
17860
20068
  }
17861
20069
 
17862
- // src/cli/commands/spec-drift.ts
17863
- import { execFileSync } from "node:child_process";
17864
- import { readFileSync as readFileSync4 } from "node:fs";
17865
- import { join as join4 } from "node:path";
17866
- var SPEC_LOCK_PATH = ".khotan/spec.lock.json";
17867
- async function runSpecDrift(options) {
17868
- const { session, io, json, dir } = options;
17869
- const defaultBranch = options.defaultBranch ?? resolveDefaultBranch(dir);
17870
- if (!defaultBranch) {
17871
- errLine(io, "Could not determine the default branch. Pass --branch <name>, or run inside a git checkout.");
17872
- return 1;
17873
- }
17874
- const pinned = readPinnedRevisions(dir, defaultBranch);
17875
- if (pinned === null) {
17876
- errLine(io, `No ${SPEC_LOCK_PATH} found at the merge base with ${defaultBranch}. Nothing has been materialized into this repository yet.`);
17877
- return json ? 0 : 1;
17878
- }
17879
- const response = await session.client.execute("projects.list");
17880
- const rows = (response.projects ?? []).map((project) => {
17881
- const slug = typeof project.slug === "string" ? project.slug : null;
17882
- if (!slug)
17883
- return null;
17884
- const latestRevision = typeof project.latestSpecRevision === "number" ? project.latestSpecRevision : 0;
17885
- const pinnedRevision = pinned.get(slug) ?? null;
17886
- return {
17887
- project: slug,
17888
- pinnedRevision,
17889
- latestRevision,
17890
- behindBy: Math.max(0, latestRevision - (pinnedRevision ?? 0))
17891
- };
17892
- }).filter((row) => row !== null);
17893
- if (json) {
17894
- outLine(io, JSON.stringify({ defaultBranch, projects: rows }, null, 2));
17895
- return 0;
17896
- }
17897
- if (rows.length === 0) {
17898
- outLine(io, "No projects in this workspace.");
17899
- return 0;
17900
- }
17901
- const drifted = rows.filter((row) => row.behindBy > 0);
17902
- for (const row of rows) {
17903
- const pinnedLabel = row.pinnedRevision === null ? "not materialized" : `r${row.pinnedRevision}`;
17904
- const suffix = row.behindBy === 0 ? "up to date" : `${row.behindBy} revision${row.behindBy === 1 ? "" : "s"} behind (latest r${row.latestRevision})`;
17905
- outLine(io, `${row.project}: ${pinnedLabel} — ${suffix}`);
17906
- }
17907
- if (drifted.length > 0) {
17908
- outLine(io, `
17909
- ${drifted.length} project${drifted.length === 1 ? " has" : "s have"} moved on since this branch was cut.`);
17910
- }
17911
- return 0;
17912
- }
17913
- function readPinnedRevisions(dir, defaultBranch) {
17914
- const mergeBase = git(dir, ["merge-base", "HEAD", defaultBranch]) ?? git(dir, ["rev-parse", defaultBranch]);
17915
- const content = mergeBase ? git(dir, ["show", `${mergeBase}:${SPEC_LOCK_PATH}`]) : readWorkingTreeLock(dir);
17916
- if (!content) {
17917
- return null;
17918
- }
17919
- let parsed;
17920
- try {
17921
- parsed = JSON.parse(content);
17922
- } catch {
17923
- return null;
17924
- }
17925
- const pinned = new Map;
17926
- for (const [slug, entry] of Object.entries(parsed.projects ?? {})) {
17927
- if (entry && typeof entry.revision === "number") {
17928
- pinned.set(slug, entry.revision);
17929
- }
17930
- }
17931
- return pinned;
17932
- }
17933
- function readWorkingTreeLock(dir) {
17934
- try {
17935
- return readFileSync4(join4(dir, SPEC_LOCK_PATH), "utf8");
17936
- } catch {
17937
- return null;
17938
- }
17939
- }
17940
- function resolveDefaultBranch(dir) {
17941
- const remoteHead = git(dir, ["symbolic-ref", "refs/remotes/origin/HEAD"]);
17942
- if (remoteHead) {
17943
- const name = remoteHead.split("/").pop();
17944
- if (name)
17945
- return `origin/${name}`;
17946
- }
17947
- for (const candidate of ["origin/main", "origin/master", "main", "master"]) {
17948
- if (git(dir, ["rev-parse", "--verify", candidate])) {
17949
- return candidate;
17950
- }
17951
- }
17952
- return null;
17953
- }
17954
- function git(dir, args) {
17955
- try {
17956
- const output = execFileSync("git", args, {
17957
- cwd: dir,
17958
- encoding: "utf8",
17959
- stdio: ["ignore", "pipe", "ignore"]
17960
- });
17961
- const trimmed = output.trim();
17962
- return trimmed.length > 0 ? trimmed : null;
17963
- } catch {
17964
- return null;
17965
- }
17966
- }
17967
-
17968
20070
  // src/cli/commands/status.ts
17969
20071
  import { writeFileSync as writeFileSync3 } from "node:fs";
17970
- import { join as join5, relative as relative2 } from "node:path";
20072
+ import { join as join6, relative as relative2 } from "node:path";
17971
20073
  function str(value) {
17972
20074
  return typeof value === "string" && value.length > 0 ? value : null;
17973
20075
  }
@@ -18247,7 +20349,7 @@ async function runStatus(options) {
18247
20349
  const { session, io, json, writeDir } = options;
18248
20350
  const topology = await loadTopology(session);
18249
20351
  if (writeDir) {
18250
- const target = join5(writeDir, "WORKSPACE.md");
20352
+ const target = join6(writeDir, "WORKSPACE.md");
18251
20353
  writeFileSync3(target, buildWorkspaceManifest(topology));
18252
20354
  const shown = relative2(process.cwd(), target) || target;
18253
20355
  if (json) {
@@ -18329,6 +20431,10 @@ function printTopLevelHelp(io) {
18329
20431
  outLine(io, " files upload <path> Upload a local file (presigned flow)");
18330
20432
  outLine(io, " files download <fileId> Download a file to --output (presigned flow)");
18331
20433
  outLine(io);
20434
+ outLine(io, "OpenSpec:");
20435
+ outLine(io, " change push [changeId] File a change directory as an issue");
20436
+ outLine(io, " projects spec commit Commit spec edits from a JSON document");
20437
+ outLine(io);
18332
20438
  outLine(io, "Workspace:");
18333
20439
  outLine(io, " status Show the workspace topology (apps, pipelines, databases)");
18334
20440
  outLine(io, " status --write Refresh a committable WORKSPACE.md from live data");
@@ -18477,25 +20583,25 @@ init_src();
18477
20583
  // src/cli/assert-org.ts
18478
20584
  init_src();
18479
20585
  import { createHash } from "node:crypto";
18480
- import { existsSync as existsSync3, mkdirSync as mkdirSync3, readFileSync as readFileSync5, writeFileSync as writeFileSync4 } from "node:fs";
20586
+ import { existsSync as existsSync5, mkdirSync as mkdirSync3, readFileSync as readFileSync7, writeFileSync as writeFileSync4 } from "node:fs";
18481
20587
  import { homedir as homedir2 } from "node:os";
18482
- import { dirname as dirname4, join as join6 } from "node:path";
20588
+ import { dirname as dirname5, join as join7 } from "node:path";
18483
20589
  var CACHE_TTL_MS = 5 * 60 * 1000;
18484
20590
  function cacheDir(env) {
18485
20591
  const xdg = env.XDG_CACHE_HOME;
18486
- const base = xdg && xdg.trim() ? xdg : join6(homedir2(), ".cache");
18487
- return join6(base, "khotan");
20592
+ const base = xdg && xdg.trim() ? xdg : join7(homedir2(), ".cache");
20593
+ return join7(base, "khotan");
18488
20594
  }
18489
20595
  function cacheFilePath(env, apiUrl, orgId) {
18490
20596
  const hash = createHash("sha256").update(`${apiUrl}
18491
20597
  ${orgId}`).digest("hex").slice(0, 32);
18492
- return join6(cacheDir(env), `org-assert-${hash}.json`);
20598
+ return join7(cacheDir(env), `org-assert-${hash}.json`);
18493
20599
  }
18494
20600
  function readAssertionCache(path, expectedOrgId) {
18495
- if (!existsSync3(path))
20601
+ if (!existsSync5(path))
18496
20602
  return false;
18497
20603
  try {
18498
- const parsed = JSON.parse(readFileSync5(path, "utf8"));
20604
+ const parsed = JSON.parse(readFileSync7(path, "utf8"));
18499
20605
  if (parsed.organizationId !== expectedOrgId)
18500
20606
  return false;
18501
20607
  if (Date.now() - parsed.verifiedAt > CACHE_TTL_MS)
@@ -18507,7 +20613,7 @@ function readAssertionCache(path, expectedOrgId) {
18507
20613
  }
18508
20614
  function writeAssertionCache(path, organizationId) {
18509
20615
  try {
18510
- mkdirSync3(dirname4(path), { recursive: true });
20616
+ mkdirSync3(dirname5(path), { recursive: true });
18511
20617
  const payload = { organizationId, verifiedAt: Date.now() };
18512
20618
  writeFileSync4(path, `${JSON.stringify(payload)}
18513
20619
  `);
@@ -18686,29 +20792,6 @@ async function run(argv, options = {}) {
18686
20792
  const writeDir = write ? flagValue(args, "dir") ?? (writeValue && writeValue !== "true" ? writeValue : undefined) ?? process.cwd() : undefined;
18687
20793
  return await runStatus({ session, io, json, writeDir });
18688
20794
  }
18689
- if (first === "spec" && positionals[1] === "drift") {
18690
- if (helpFlag) {
18691
- errLine(io, "Usage: khotan spec drift [--dir <path>] [--branch <name>] [--json]");
18692
- return 0;
18693
- }
18694
- const session = await resolveSessionAsserted({
18695
- env,
18696
- storeOptions: options.storeOptions,
18697
- profileName,
18698
- apiUrlOverride,
18699
- apiKeyOverride,
18700
- fetch: options.fetch,
18701
- assertOrgOverride
18702
- });
18703
- const json = jsonFlag || session.resolved.defaultOutput === "json";
18704
- return await runSpecDrift({
18705
- session,
18706
- io,
18707
- json,
18708
- dir: flagValue(args, "dir") ?? process.cwd(),
18709
- defaultBranch: flagValue(args, "branch")
18710
- });
18711
- }
18712
20795
  if (first === "login") {
18713
20796
  return await authLogin(authCtx);
18714
20797
  }
@@ -18734,6 +20817,62 @@ async function run(argv, options = {}) {
18734
20817
  errLine(io, `Unknown auth command "${sub ?? ""}". Try: set-key, login, use, list.`);
18735
20818
  return 1;
18736
20819
  }
20820
+ if (first === "projects" && positionals[1] === "spec" && positionals[2] === "commit") {
20821
+ if (helpFlag) {
20822
+ errLine(io, "Usage: khotan projects spec commit <projectId> --file <path> [--expected-revision <n>] [--change-summary <text>] [--json]");
20823
+ return 0;
20824
+ }
20825
+ const projectId = positionals[3];
20826
+ const file = flagValue(args, "file");
20827
+ if (!projectId || !file) {
20828
+ throw new KhotanClientError("missing_argument", "Usage: khotan projects spec commit <projectId> --file <path>.");
20829
+ }
20830
+ const session = await resolveSessionAsserted({
20831
+ env,
20832
+ storeOptions: options.storeOptions,
20833
+ profileName,
20834
+ apiUrlOverride,
20835
+ apiKeyOverride,
20836
+ fetch: options.fetch,
20837
+ assertOrgOverride
20838
+ });
20839
+ const json = jsonFlag || session.resolved.defaultOutput === "json";
20840
+ const expectedRevisionRaw = flagValue(args, "expected-revision");
20841
+ const expectedRevision = expectedRevisionRaw ? Number(expectedRevisionRaw) : undefined;
20842
+ if (expectedRevisionRaw && Number.isNaN(expectedRevision)) {
20843
+ throw new KhotanClientError("invalid_argument", "--expected-revision expects a number.");
20844
+ }
20845
+ return await specCommit({ session, io, json }, {
20846
+ projectId,
20847
+ file,
20848
+ expectedRevision,
20849
+ changeSummary: flagValue(args, "change-summary")
20850
+ });
20851
+ }
20852
+ if (first === "change" && positionals[1] === "push") {
20853
+ if (helpFlag) {
20854
+ errLine(io, "Usage: khotan change push [changeId] [--project <slug>] [--ref <sha>] [--dir <path>] [--dry-run] [--json]");
20855
+ return 0;
20856
+ }
20857
+ const dryRun = flagBool(args, "dry-run");
20858
+ const session = dryRun ? undefined : await resolveSessionAsserted({
20859
+ env,
20860
+ storeOptions: options.storeOptions,
20861
+ profileName,
20862
+ apiUrlOverride,
20863
+ apiKeyOverride,
20864
+ fetch: options.fetch,
20865
+ assertOrgOverride
20866
+ });
20867
+ const json = jsonFlag || session?.resolved.defaultOutput === "json";
20868
+ return await changePush({ session, io, json }, {
20869
+ changeId: positionals[2],
20870
+ project: flagValue(args, "project"),
20871
+ cwd: flagValue(args, "dir"),
20872
+ ref: flagValue(args, "ref"),
20873
+ dryRun
20874
+ });
20875
+ }
18737
20876
  if (first === "files" && (positionals[1] === "upload" || positionals[1] === "download")) {
18738
20877
  if (helpFlag) {
18739
20878
  errLine(io, positionals[1] === "upload" ? "Usage: khotan files upload <localPath> [--name <n>] [--folder-id <id>] [--folder-path <p>] [--content-type <t>]" : "Usage: khotan files download <fileId> --output <path>");