@kosuke-ai/cli 6.7.0 → 6.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,135 @@
1
+ import { ResponseTooLarge } from '../runtime/client.js';
2
+ /**
3
+ * The integration tools routes, read the way a command prints them. Everything
4
+ * the platform sends is text from a connected third party or a user's label, so
5
+ * it is flattened to bounded one-liners here and never interpreted.
6
+ */
7
+ export const TOOLS_PATH = '/api/agent-integrations/tools';
8
+ const UNAVAILABLE = 'Integration tools are temporarily unavailable.';
9
+ const TOO_LARGE = 'The platform answer was too large to read. Narrow the request.';
10
+ const TIMED_OUT = 'The platform did not answer in time.';
11
+ /** A write may have landed before the connection failed; a retry could repeat it. */
12
+ const UNKNOWN_OUTCOME = 'The integration tool call did not complete and its outcome is unknown. Do not retry it; read the current state first.';
13
+ export const isRow = (value) => typeof value === 'object' && value !== null && !Array.isArray(value);
14
+ export const rowsOf = (value) => (Array.isArray(value) ? value.filter(isRow) : []);
15
+ /** One bounded line: control characters and runs of space become one space. */
16
+ export function oneLine(value, max) {
17
+ if (typeof value !== 'string')
18
+ return '';
19
+ const flat = value.replace(/[\p{Cc}\p{Cf}\s]+/gu, ' ').trim();
20
+ return flat.length > max ? `${flat.slice(0, max - 1).trimEnd()}…` : flat;
21
+ }
22
+ const count = (value) => typeof value === 'number' && Number.isFinite(value) && value >= 0 ? Math.trunc(value) : 0;
23
+ export function connectionOf(row) {
24
+ const id = oneLine(row.id, 16);
25
+ const alias = oneLine(row.alias, 80);
26
+ if (!alias)
27
+ return null;
28
+ return {
29
+ id,
30
+ alias,
31
+ scope: row.scope === 'personal' ? 'personal' : 'workspace',
32
+ label: oneLine(row.label, 60),
33
+ accountIdentity: oneLine(row.accountIdentity, 80) || null,
34
+ };
35
+ }
36
+ /** `data` of a `view=overview` answer, or null for any other shape (an older platform's). */
37
+ function parseOverview(data) {
38
+ if (!isRow(data) || data.view !== 'overview' || !Array.isArray(data.toolkits))
39
+ return null;
40
+ const toolkits = rowsOf(data.toolkits).map((row) => {
41
+ const tools = isRow(row.tools) ? row.tools : {};
42
+ const toolkit = oneLine(row.toolkit, 64);
43
+ return {
44
+ toolkit,
45
+ name: oneLine(row.name, 60) || toolkit,
46
+ connections: rowsOf(row.connections).flatMap((connection) => connectionOf(connection) ?? []),
47
+ read: count(tools.read),
48
+ write: count(tools.write),
49
+ };
50
+ });
51
+ const tools = Array.isArray(data.tools)
52
+ ? rowsOf(data.tools).map((row) => ({
53
+ name: oneLine(row.name, 80),
54
+ toolkit: oneLine(row.toolkit, 64),
55
+ access: row.access === 'read' ? 'read' : 'write',
56
+ description: oneLine(row.description, 160),
57
+ aliases: (Array.isArray(row.aliases) ? row.aliases : [])
58
+ .map((alias) => oneLine(alias, 80))
59
+ .filter(Boolean),
60
+ }))
61
+ : null;
62
+ return {
63
+ toolkits,
64
+ connectionTotal: count(data.connectionTotal),
65
+ tools,
66
+ toolTotal: tools ? count(data.toolTotal) : null,
67
+ note: oneLine(data.note, 400) || null,
68
+ };
69
+ }
70
+ /** The platform's own sentence for a refusal, bounded; its status when it said nothing. */
71
+ function sentenceOf(error, status) {
72
+ const said = isRow(error) ? oneLine(error.error, 300) : '';
73
+ return said || `HTTP ${status}`;
74
+ }
75
+ /**
76
+ * Why a request failed, in words the reader can act on. The 4xx sentences are
77
+ * the platform's own (a Connector Token, a workspace the key is not a member of,
78
+ * Slack triage); only 401 and 429 are worded here.
79
+ */
80
+ export function failureOf(status, error, method) {
81
+ // A gateway answering for the app does not prove the app never ran the write.
82
+ if (method === 'POST' && (status === 502 || status === 504)) {
83
+ return { ok: false, status, message: UNKNOWN_OUTCOME };
84
+ }
85
+ if (status === 401) {
86
+ return {
87
+ ok: false,
88
+ status,
89
+ message: 'The API key was refused (HTTP 401). Run `kosuke auth status`.',
90
+ };
91
+ }
92
+ if (status === 429) {
93
+ return { ok: false, status, message: 'Too many requests. Try again shortly.' };
94
+ }
95
+ if (status >= 400 && status < 500) {
96
+ return { ok: false, status, message: sentenceOf(error, status) };
97
+ }
98
+ return { ok: false, status, message: UNAVAILABLE };
99
+ }
100
+ /** A request that never got an answer. */
101
+ export function transportFailure(error, method) {
102
+ const name = error instanceof Error ? error.name : '';
103
+ if (method === 'GET' && error instanceof ResponseTooLarge) {
104
+ return { ok: false, status: null, message: TOO_LARGE };
105
+ }
106
+ const timedOut = name === 'TimeoutError' || name === 'AbortError';
107
+ if (method === 'POST')
108
+ return { ok: false, status: null, message: UNKNOWN_OUTCOME };
109
+ return { ok: false, status: null, message: timedOut ? TIMED_OUT : UNAVAILABLE };
110
+ }
111
+ /** The overview view: the connections per toolkit, and the matching tools once a filter is set. */
112
+ export async function readOverview(client, query, signal) {
113
+ const params = { view: 'overview', ...query };
114
+ try {
115
+ const { data, error, response } = await client.GET(TOOLS_PATH, {
116
+ params: { query: params },
117
+ signal,
118
+ });
119
+ if (!response.ok)
120
+ return failureOf(response.status, error, 'GET');
121
+ const raw = isRow(data) ? data.data : undefined;
122
+ const overview = parseOverview(raw);
123
+ if (!overview) {
124
+ return {
125
+ ok: false,
126
+ status: response.status,
127
+ message: 'This platform does not offer the integrations overview yet.',
128
+ };
129
+ }
130
+ return { ok: true, overview, raw };
131
+ }
132
+ catch (error) {
133
+ return transportFailure(error, 'GET');
134
+ }
135
+ }
@@ -0,0 +1,52 @@
1
+ /**
2
+ * `kosuke integrations list` as text: a block per toolkit, with its connections
3
+ * and tool counts, then the matching tools when a filter is set. A cut is never
4
+ * silent: the server's note is printed whenever it sent one.
5
+ */
6
+ const plural = (count, noun) => `${count} ${noun}${count === 1 ? '' : 's'}`;
7
+ function connectionLine(connection) {
8
+ const identity = connection.accountIdentity ?? 'account unknown';
9
+ const id = connection.id ? `, id ${connection.id}` : '';
10
+ return `${connection.alias}: ${connection.scope}, "${connection.label}", ${identity}${id}`;
11
+ }
12
+ function toolLine(tool, width) {
13
+ const access = (tool.access === 'read' ? '[read-only]' : '[writes]').padEnd(11);
14
+ const accounts = tool.aliases.length > 0 ? ` [account: ${tool.aliases.join(', ')}]` : '';
15
+ return `${tool.name.padEnd(width)} ${access} ${tool.description}${accounts}`.trimEnd();
16
+ }
17
+ export function renderOverview(overview, filtered) {
18
+ const { toolkits, tools, note } = overview;
19
+ if (toolkits.length === 0 && overview.connectionTotal === 0) {
20
+ return filtered ? 'No connected tool matches.\n' : 'No integrations are connected.\n';
21
+ }
22
+ if (filtered && tools?.length === 0)
23
+ return 'No connected tool matches.\n';
24
+ const width = Math.max(0, ...(tools ?? []).map((tool) => tool.name.length));
25
+ const blocks = toolkits.map((toolkit) => {
26
+ const lines = [
27
+ `${toolkit.name} (${toolkit.toolkit}): ${toolkit.read} read, ${toolkit.write} write tools`,
28
+ ...toolkit.connections.map((connection) => ` ${connectionLine(connection)}`),
29
+ ];
30
+ const own = (tools ?? []).filter((tool) => tool.toolkit === toolkit.toolkit);
31
+ if (own.length > 0) {
32
+ lines.push(' tools:', ...own.map((tool) => ` ${toolLine(tool, width)}`));
33
+ }
34
+ return lines.join('\n');
35
+ });
36
+ const out = [blocks.join('\n\n')];
37
+ if (tools) {
38
+ out.push(`${tools.length} of ${plural(overview.toolTotal ?? tools.length, 'tool')} shown.`);
39
+ }
40
+ else {
41
+ out.push(`${plural(overview.connectionTotal, 'connection')}. ` +
42
+ "`kosuke integrations list --toolkit <slug>` shows a toolkit's tools; " +
43
+ "`kosuke integrations describe <tool>` prints one tool's input schema.");
44
+ }
45
+ if (note)
46
+ out.push(note);
47
+ return `${out.join('\n\n')}\n`;
48
+ }
49
+ /** One connection per line, for a refusal that asks the caller to pick one. */
50
+ export function choiceLines(choices) {
51
+ return choices.map((choice) => ` ${connectionLine(choice)}`).join('\n');
52
+ }
package/dist/program.js CHANGED
@@ -1,6 +1,7 @@
1
1
  import { Command } from 'commander';
2
2
  import { attachAgentCommands } from './commands/agent.js';
3
3
  import { attachAuthCommands } from './commands/auth.js';
4
+ import { attachIntegrationsCommands } from './commands/integrations.js';
4
5
  import { attachWatchCommand } from './commands/watch.js';
5
6
  import { operations } from './generated/commands/index.js';
6
7
  import { attachOperation } from './runtime/attach.js';
@@ -8,8 +9,8 @@ import { isCronToken, isLeadToken, isOrchestratorToken, isSandboxAgentToken, } f
8
9
  import { createRuntime, ExitSignal } from './runtime/runtime.js';
9
10
  import { version } from './runtime/version.js';
10
11
  /**
11
- * The command tree: `auth`, `agent` and `sessions watch` by hand, everything else
12
- * from the generated descriptors. Global flags are read before any subcommand runs, so the
12
+ * The command tree: `auth`, `agent`, `integrations list|describe|call` and `sessions watch` by
13
+ * hand, everything else from the generated descriptors. Global flags are read before any subcommand runs, so the
13
14
  * runtime the subcommands share is built once, from them.
14
15
  */
15
16
  export function createProgram(options = {}) {
@@ -55,6 +56,11 @@ export function createProgram(options = {}) {
55
56
  // member's own key and their Lead may mark Activity seen, so the others
56
57
  // read it with `--peek`.
57
58
  attachAgentCommands(program, lazy, options.agent);
59
+ // Beside the generated `integrations tools` and `execute`, which stay raw passthroughs. The
60
+ // orchestrator token reaches neither: Slack triage has no integration tools.
61
+ if (narrowedTo !== 'orchestratorToken') {
62
+ attachIntegrationsCommands(program, lazy, options.integrations);
63
+ }
58
64
  // The job stream admits a member's API key only; it closes 4401 on every narrower token.
59
65
  if (narrowedTo === null)
60
66
  attachWatchCommand(program, lazy, options.watch);
@@ -99,6 +99,57 @@ function withTransportRetry(fetchImpl) {
99
99
  }
100
100
  };
101
101
  }
102
+ /** The most of one integration answer the CLI reads; its listings are bounded well below it. */
103
+ const MAX_INTEGRATION_RESPONSE_BYTES = 512 * 1024;
104
+ const INTEGRATIONS_PREFIX = '/api/agent-integrations/';
105
+ /** An integration answer past the cap; a call that got one may still have run. */
106
+ export class ResponseTooLarge extends Error {
107
+ constructor() {
108
+ super('Integration response was too large');
109
+ this.name = 'ResponseTooLarge';
110
+ }
111
+ }
112
+ /**
113
+ * Caps what an integration route can make the CLI buffer, error bodies included. The body is
114
+ * counted as it streams, so nothing past the cap is held or parsed. Other routes are untouched.
115
+ */
116
+ function withIntegrationCap(fetchImpl) {
117
+ return async (...args) => {
118
+ const response = await fetchImpl(...args);
119
+ const [input] = args;
120
+ try {
121
+ const href = input instanceof Request ? input.url : String(input);
122
+ if (!new URL(href, 'http://localhost').pathname.startsWith(INTEGRATIONS_PREFIX)) {
123
+ return response;
124
+ }
125
+ }
126
+ catch {
127
+ return response;
128
+ }
129
+ if (!response.body)
130
+ return response;
131
+ const declared = Number(response.headers.get('content-length'));
132
+ if (Number.isFinite(declared) && declared > MAX_INTEGRATION_RESPONSE_BYTES) {
133
+ await response.body.cancel().catch(() => undefined);
134
+ throw new ResponseTooLarge();
135
+ }
136
+ let total = 0;
137
+ const counted = response.body.pipeThrough(new TransformStream({
138
+ transform(chunk, controller) {
139
+ total += chunk.byteLength;
140
+ if (total > MAX_INTEGRATION_RESPONSE_BYTES)
141
+ controller.error(new ResponseTooLarge());
142
+ else
143
+ controller.enqueue(chunk);
144
+ },
145
+ }));
146
+ return new Response(counted, {
147
+ status: response.status,
148
+ statusText: response.statusText,
149
+ headers: response.headers,
150
+ });
151
+ };
152
+ }
102
153
  export function createApiClient({ apiUrl, apiKey, fetch }) {
103
154
  return createClient({
104
155
  baseUrl: apiUrl,
@@ -106,6 +157,6 @@ export function createApiClient({ apiUrl, apiKey, fetch }) {
106
157
  ...(apiKey ? { Authorization: `Bearer ${apiKey}` } : {}),
107
158
  'User-Agent': `@kosuke-ai/cli/${version}`,
108
159
  },
109
- fetch: withTransportRetry(fetch ?? globalThis.fetch),
160
+ fetch: withIntegrationCap(withTransportRetry(fetch ?? globalThis.fetch)),
110
161
  });
111
162
  }
@@ -37,6 +37,15 @@ const LEAD_TOKEN_PREFIX = 'kosuke_lead_';
37
37
  export function isLeadToken(token) {
38
38
  return token?.startsWith(LEAD_TOKEN_PREFIX) ?? false;
39
39
  }
40
+ /**
41
+ * A credential that decides its own workspace: a sandbox, cron or Lead token.
42
+ * The orchestrator token is left out because it reaches no integration tools.
43
+ */
44
+ export function isTurnToken(token) {
45
+ return (isSandboxAgentToken(token ?? undefined) ||
46
+ isCronToken(token ?? undefined) ||
47
+ isLeadToken(token ?? undefined));
48
+ }
40
49
  export function resolveCredentials(flags, env = process.env) {
41
50
  const stored = readCredentials(env);
42
51
  const apiKey = flags.apiKey
package/openapi.json CHANGED
@@ -141,6 +141,224 @@
141
141
  }
142
142
  }
143
143
  },
144
+ "/api/agent-integrations/execute": {
145
+ "post": {
146
+ "operationId": "integrationsExecute",
147
+ "summary": "Run one integration tool. An agent runs it for its own workspace, or on the member’s personal connection when that member started the turn. With an API key, name a workspace you belong to: the call runs only on your own personal connections, and one on a workspace connection is refused. The answer names the connection it ran on, or lists the connections to choose from when the call needs one. A tool failure answers 200 with isError true. A Connector Token or a browser session is refused.",
148
+ "tags": ["integrations"],
149
+ "x-kosuke-command": ["integrations", "execute"],
150
+ "x-kosuke-sandbox-token": true,
151
+ "x-kosuke-cron-token": true,
152
+ "x-kosuke-lead-token": true,
153
+ "parameters": [
154
+ {
155
+ "name": "workspace",
156
+ "in": "query",
157
+ "required": false,
158
+ "description": "The workspace the call is made in. Required with an API key, which is refused with 403 when you are not a member of it. An agent credential reaches its own workspace and refuses another.",
159
+ "schema": {
160
+ "description": "The workspace the call is made in. Required with an API key, which is refused with 403 when you are not a member of it. An agent credential reaches its own workspace and refuses another.",
161
+ "type": "string",
162
+ "minLength": 1,
163
+ "maxLength": 100
164
+ }
165
+ }
166
+ ],
167
+ "requestBody": {
168
+ "required": true,
169
+ "content": {
170
+ "application/json": {
171
+ "schema": {
172
+ "type": "object",
173
+ "properties": {
174
+ "name": {
175
+ "type": "string",
176
+ "minLength": 1,
177
+ "maxLength": 80,
178
+ "description": "The tool to run, as the tools list names it."
179
+ },
180
+ "arguments": {
181
+ "type": "object",
182
+ "propertyNames": {
183
+ "type": "string"
184
+ },
185
+ "additionalProperties": {},
186
+ "description": "The tool's arguments, as its input schema describes them."
187
+ },
188
+ "account": {
189
+ "description": "The connection to run on: its short id, its alias such as notion-personal, or scope:label. Needed when several can run the tool.",
190
+ "type": "string",
191
+ "minLength": 1,
192
+ "maxLength": 100
193
+ }
194
+ },
195
+ "required": ["name", "arguments"],
196
+ "additionalProperties": false
197
+ }
198
+ }
199
+ }
200
+ },
201
+ "responses": {
202
+ "2XX": {
203
+ "description": "Success envelope",
204
+ "content": {
205
+ "application/json": {
206
+ "schema": {
207
+ "$ref": "#/components/schemas/ApiSuccess"
208
+ }
209
+ }
210
+ }
211
+ },
212
+ "4XX": {
213
+ "description": "Error envelope",
214
+ "content": {
215
+ "application/json": {
216
+ "schema": {
217
+ "$ref": "#/components/schemas/ApiError"
218
+ }
219
+ }
220
+ }
221
+ },
222
+ "5XX": {
223
+ "description": "Error envelope",
224
+ "content": {
225
+ "application/json": {
226
+ "schema": {
227
+ "$ref": "#/components/schemas/ApiError"
228
+ }
229
+ }
230
+ }
231
+ }
232
+ }
233
+ }
234
+ },
235
+ "/api/agent-integrations/tools": {
236
+ "get": {
237
+ "operationId": "integrationsTools",
238
+ "summary": "List the integration tools you can run now, with the connections that run them. An agent reads its own workspace. With an API key, name a workspace you belong to: it shows that workspace’s connections and your own personal ones. A Connector Token or a browser session is refused.",
239
+ "tags": ["integrations"],
240
+ "x-kosuke-command": ["integrations", "tools"],
241
+ "x-kosuke-sandbox-token": true,
242
+ "x-kosuke-cron-token": true,
243
+ "x-kosuke-lead-token": true,
244
+ "parameters": [
245
+ {
246
+ "name": "workspace",
247
+ "in": "query",
248
+ "required": false,
249
+ "description": "The workspace to list for. Required with an API key, which is refused with 403 when you are not a member of it. An agent credential reaches its own workspace and refuses another.",
250
+ "schema": {
251
+ "description": "The workspace to list for. Required with an API key, which is refused with 403 when you are not a member of it. An agent credential reaches its own workspace and refuses another.",
252
+ "type": "string",
253
+ "minLength": 1,
254
+ "maxLength": 100
255
+ }
256
+ },
257
+ {
258
+ "name": "view",
259
+ "in": "query",
260
+ "required": false,
261
+ "description": "overview: the connections per toolkit with tool counts, and the matching tools once toolkit or search is set. summary: tool names and short descriptions, cut at 300. Without it, full definitions cut at a size budget, which is what older clients read.",
262
+ "schema": {
263
+ "description": "overview: the connections per toolkit with tool counts, and the matching tools once toolkit or search is set. summary: tool names and short descriptions, cut at 300. Without it, full definitions cut at a size budget, which is what older clients read.",
264
+ "type": "string",
265
+ "enum": ["summary", "overview"]
266
+ }
267
+ },
268
+ {
269
+ "name": "toolkit",
270
+ "in": "query",
271
+ "required": false,
272
+ "description": "Only this toolkit, by slug, such as gmail.",
273
+ "schema": {
274
+ "description": "Only this toolkit, by slug, such as gmail.",
275
+ "type": "string",
276
+ "pattern": "^[a-z0-9_]{1,64}$"
277
+ }
278
+ },
279
+ {
280
+ "name": "search",
281
+ "in": "query",
282
+ "required": false,
283
+ "description": "Only tools whose name, description or toolkit contains this text, in any case.",
284
+ "schema": {
285
+ "description": "Only tools whose name, description or toolkit contains this text, in any case.",
286
+ "type": "string",
287
+ "minLength": 1,
288
+ "maxLength": 100
289
+ }
290
+ },
291
+ {
292
+ "name": "name",
293
+ "in": "query",
294
+ "required": false,
295
+ "description": "One tool by exact name, with its full input schema. 404 when you are not offered it.",
296
+ "schema": {
297
+ "description": "One tool by exact name, with its full input schema. 404 when you are not offered it.",
298
+ "type": "string",
299
+ "minLength": 1,
300
+ "maxLength": 80
301
+ }
302
+ },
303
+ {
304
+ "name": "account",
305
+ "in": "query",
306
+ "required": false,
307
+ "description": "Only one connection: its short id, its alias such as notion-personal, or scope:label. Needed for a name that several connections can run.",
308
+ "schema": {
309
+ "description": "Only one connection: its short id, its alias such as notion-personal, or scope:label. Needed for a name that several connections can run.",
310
+ "type": "string",
311
+ "minLength": 1,
312
+ "maxLength": 100
313
+ }
314
+ },
315
+ {
316
+ "name": "limit",
317
+ "in": "query",
318
+ "required": false,
319
+ "description": "With view overview, how many tools to list when toolkit or search is set. 40 by default.",
320
+ "schema": {
321
+ "description": "With view overview, how many tools to list when toolkit or search is set. 40 by default.",
322
+ "type": "integer",
323
+ "minimum": 1,
324
+ "maximum": 100
325
+ }
326
+ }
327
+ ],
328
+ "responses": {
329
+ "2XX": {
330
+ "description": "Success envelope",
331
+ "content": {
332
+ "application/json": {
333
+ "schema": {
334
+ "$ref": "#/components/schemas/ApiSuccess"
335
+ }
336
+ }
337
+ }
338
+ },
339
+ "4XX": {
340
+ "description": "Error envelope",
341
+ "content": {
342
+ "application/json": {
343
+ "schema": {
344
+ "$ref": "#/components/schemas/ApiError"
345
+ }
346
+ }
347
+ }
348
+ },
349
+ "5XX": {
350
+ "description": "Error envelope",
351
+ "content": {
352
+ "application/json": {
353
+ "schema": {
354
+ "$ref": "#/components/schemas/ApiError"
355
+ }
356
+ }
357
+ }
358
+ }
359
+ }
360
+ }
361
+ },
144
362
  "/api/auth/github/repositories": {
145
363
  "get": {
146
364
  "operationId": "githubRepositories",
@@ -6049,6 +6267,10 @@
6049
6267
  "type": "string",
6050
6268
  "minLength": 1
6051
6269
  },
6270
+ "auto": {
6271
+ "description": "True starts the session on Auto: Kosuke picks the Model and Provider of each Turn. False keeps it off. Absent follows your Default Model.",
6272
+ "type": "boolean"
6273
+ },
6052
6274
  "status": {
6053
6275
  "type": "string",
6054
6276
  "const": "backlog"
@@ -6092,6 +6314,10 @@
6092
6314
  "type": "string",
6093
6315
  "minLength": 1
6094
6316
  },
6317
+ "auto": {
6318
+ "description": "True starts the session on Auto: Kosuke picks the Model and Provider of each Turn. False keeps it off. Absent follows your Default Model.",
6319
+ "type": "boolean"
6320
+ },
6095
6321
  "status": {
6096
6322
  "type": "string",
6097
6323
  "const": "backlog"
@@ -6221,7 +6447,7 @@
6221
6447
  },
6222
6448
  "put": {
6223
6449
  "operationId": "sessionsUpdate",
6224
- "summary": "Change title, status, model, Assignee or priority. Which changes are legal depends on the current status; the handler decides.",
6450
+ "summary": "Change title, status, model, Auto, Assignee or priority. Which changes are legal depends on the current status; the handler decides.",
6225
6451
  "tags": ["sessions"],
6226
6452
  "x-kosuke-command": ["sessions", "update"],
6227
6453
  "x-kosuke-sandbox-token": true,
@@ -6282,6 +6508,10 @@
6282
6508
  "type": "string",
6283
6509
  "minLength": 1
6284
6510
  },
6511
+ "auto": {
6512
+ "description": "True puts the session on Auto, from its next Turn. False takes it off; send it with model when choosing a Model. Absent leaves it as it is.",
6513
+ "type": "boolean"
6514
+ },
6285
6515
  "ownerId": {
6286
6516
  "anyOf": [
6287
6517
  {
@@ -7170,6 +7400,10 @@
7170
7400
  "type": "string",
7171
7401
  "minLength": 1
7172
7402
  },
7403
+ "auto": {
7404
+ "description": "True puts the session on Auto from this Turn: Kosuke picks its Model and Provider. False takes it off, with model or the session's own. Absent keeps the session as it is.",
7405
+ "type": "boolean"
7406
+ },
7173
7407
  "previewContext": {
7174
7408
  "type": "object",
7175
7409
  "properties": {
@@ -7273,6 +7507,11 @@
7273
7507
  "type": "string",
7274
7508
  "minLength": 1
7275
7509
  },
7510
+ "auto": {
7511
+ "description": "As the JSON body's auto.",
7512
+ "type": "string",
7513
+ "enum": ["true", "false"]
7514
+ },
7276
7515
  "previewContext": {
7277
7516
  "description": "JSON-encoded { frontendName, path }.",
7278
7517
  "type": "string"
@@ -9001,6 +9240,11 @@
9001
9240
  "type": "string",
9002
9241
  "minLength": 1
9003
9242
  },
9243
+ "auto": {
9244
+ "description": "true puts the ticket on Auto: Kosuke picks the Model and Provider of each Turn. false takes it off; send it with model when choosing a Model. Absent leaves it as it is.",
9245
+ "type": "string",
9246
+ "enum": ["true", "false"]
9247
+ },
9004
9248
  "ownerId": {
9005
9249
  "description": "A member id to assign, or an empty string to unassign.",
9006
9250
  "type": "string"
@@ -13799,7 +14043,7 @@
13799
14043
  "/api/user/subscriptions": {
13800
14044
  "get": {
13801
14045
  "operationId": "userSubscriptionsGet",
13802
- "summary": "Read your Claude, Codex, GLM Coding Plan, Kimi Code and DeepSeek API subscriptions and your default model. Per provider: the login in use (connected, needsReauth, accountEmail, and maskedApiKey for a pasted key) and every login in the order it is used (logins).",
14046
+ "summary": "Read your Claude, Codex, GLM Coding Plan, Kimi Code and DeepSeek API subscriptions and your default model, with defaultAuto when it is Auto. Per provider: the login in use (connected, needsReauth, accountEmail, and maskedApiKey for a pasted key) and every login in the order it is used (logins).",
13803
14047
  "tags": ["user"],
13804
14048
  "x-kosuke-command": ["user", "subscriptions", "get"],
13805
14049
  "x-kosuke-sandbox-token": true,
@@ -13841,7 +14085,7 @@
13841
14085
  },
13842
14086
  "patch": {
13843
14087
  "operationId": "userSubscriptionsSetDefaultModel",
13844
- "summary": "Save the model new chat sessions start on, as an encoded id such as anthropic:sonnet__medium, or null to let Kosuke choose. Answers 204.",
14088
+ "summary": "Save the model new chat sessions start on, as an encoded id such as anthropic:sonnet__medium, or null to let Kosuke choose, or defaultAuto: true to start them on Auto. Answers 204.",
13845
14089
  "tags": ["user"],
13846
14090
  "x-kosuke-command": ["user", "subscriptions", "set-default-model"],
13847
14091
  "requestBody": {
@@ -13853,9 +14097,12 @@
13853
14097
  "properties": {
13854
14098
  "defaultModel": {
13855
14099
  "type": ["string", "null"]
14100
+ },
14101
+ "defaultAuto": {
14102
+ "description": "True makes Auto your Default Model; defaultModel stays as it is unless sent.",
14103
+ "type": "boolean"
13856
14104
  }
13857
- },
13858
- "required": ["defaultModel"]
14105
+ }
13859
14106
  }
13860
14107
  }
13861
14108
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kosuke-ai/cli",
3
- "version": "6.7.0",
3
+ "version": "6.9.0",
4
4
  "description": "Kosuke platform CLI. The client customers install to drive app.kosuke.ai from their own machine.",
5
5
  "license": "MIT",
6
6
  "repository": {