@kosuke-ai/cli 1.0.1-next.5 → 1.0.1-next.6
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.
- package/README.md +104 -0
- package/dist/commands/mcp.js +85 -0
- package/dist/generated/tools/deploy/cancel.js +20 -0
- package/dist/generated/tools/deploy/messages.js +20 -0
- package/dist/generated/tools/deploy/run.js +52 -0
- package/dist/generated/tools/index.js +138 -0
- package/dist/generated/tools/preview/get.js +24 -0
- package/dist/generated/tools/preview/health.js +24 -0
- package/dist/generated/tools/preview/reset.js +23 -0
- package/dist/generated/tools/preview/share-create.js +24 -0
- package/dist/generated/tools/preview/share-delete.js +24 -0
- package/dist/generated/tools/preview/share-get.js +24 -0
- package/dist/generated/tools/preview/shared-artifact.js +25 -0
- package/dist/generated/tools/preview/shared.js +20 -0
- package/dist/generated/tools/projects/create.js +26 -0
- package/dist/generated/tools/projects/delete.js +20 -0
- package/dist/generated/tools/projects/env-vars-add.js +50 -0
- package/dist/generated/tools/projects/env-vars-remove.js +31 -0
- package/dist/generated/tools/projects/get.js +20 -0
- package/dist/generated/tools/projects/list.js +12 -0
- package/dist/generated/tools/projects/services-add.js +111 -0
- package/dist/generated/tools/projects/services-list.js +20 -0
- package/dist/generated/tools/projects/services-remove.js +25 -0
- package/dist/generated/tools/projects/services-update.js +104 -0
- package/dist/generated/tools/projects/settings-get.js +20 -0
- package/dist/generated/tools/projects/settings-import.js +208 -0
- package/dist/generated/tools/projects/settings-update.js +72 -0
- package/dist/generated/tools/projects/skills.js +32 -0
- package/dist/generated/tools/projects/update.js +24 -0
- package/dist/generated/tools/sessions/activate.js +25 -0
- package/dist/generated/tools/sessions/artifacts-get.js +30 -0
- package/dist/generated/tools/sessions/artifacts-list.js +25 -0
- package/dist/generated/tools/sessions/checklist-generate.js +25 -0
- package/dist/generated/tools/sessions/checklist-get.js +25 -0
- package/dist/generated/tools/sessions/comments-add.js +53 -0
- package/dist/generated/tools/sessions/comments-delete.js +30 -0
- package/dist/generated/tools/sessions/comments-list.js +25 -0
- package/dist/generated/tools/sessions/comments-update.js +62 -0
- package/dist/generated/tools/sessions/create.js +69 -0
- package/dist/generated/tools/sessions/delete.js +25 -0
- package/dist/generated/tools/sessions/diff-files.js +25 -0
- package/dist/generated/tools/sessions/diff-patch.js +64 -0
- package/dist/generated/tools/sessions/edit-prompt.js +69 -0
- package/dist/generated/tools/sessions/files-get.js +30 -0
- package/dist/generated/tools/sessions/files-list.js +25 -0
- package/dist/generated/tools/sessions/files-write.js +37 -0
- package/dist/generated/tools/sessions/get.js +25 -0
- package/dist/generated/tools/sessions/git-commit-message.js +25 -0
- package/dist/generated/tools/sessions/git-commit.js +29 -0
- package/dist/generated/tools/sessions/git-push.js +25 -0
- package/dist/generated/tools/sessions/git-state.js +25 -0
- package/dist/generated/tools/sessions/jobs-cancel.js +30 -0
- package/dist/generated/tools/sessions/jobs-get.js +30 -0
- package/dist/generated/tools/sessions/list.js +56 -0
- package/dist/generated/tools/sessions/messages.js +25 -0
- package/dist/generated/tools/sessions/search.js +17 -0
- package/dist/generated/tools/sessions/send.js +137 -0
- package/dist/generated/tools/sessions/update.js +63 -0
- package/dist/generated/tools/workspaces/access-status.js +28 -0
- package/dist/generated/tools/workspaces/ai-provider-get.js +20 -0
- package/dist/generated/tools/workspaces/ai-provider-remove.js +39 -0
- package/dist/generated/tools/workspaces/ai-provider-set.js +106 -0
- package/dist/generated/tools/workspaces/credits.js +20 -0
- package/dist/generated/tools/workspaces/get.js +20 -0
- package/dist/generated/tools/workspaces/getting-started-get.js +20 -0
- package/dist/generated/tools/workspaces/getting-started-update.js +27 -0
- package/dist/generated/tools/workspaces/list.js +12 -0
- package/dist/generated/tools/workspaces/members.js +24 -0
- package/dist/generated/tools/workspaces/sandbox-limit.js +20 -0
- package/dist/generated/tools/workspaces/stop-sandboxes.js +31 -0
- package/dist/generated/tools/workspaces/usage.js +28 -0
- package/dist/program.js +4 -2
- package/dist/runtime/mcp-attachments.js +110 -0
- package/dist/runtime/mcp-composite-support.js +49 -0
- package/dist/runtime/mcp-composite.js +309 -0
- package/dist/runtime/mcp-guard.js +74 -0
- package/dist/runtime/mcp-invoke.js +123 -0
- package/dist/runtime/mcp-open-pr.js +83 -0
- package/dist/runtime/mcp-redact.js +31 -0
- package/dist/runtime/mcp-server.js +219 -0
- package/dist/runtime/mcp-tool.js +6 -0
- package/package.json +2 -1
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { operation } from '../../commands/workspaces/access-status.js';
|
|
2
|
+
export const tool = {
|
|
3
|
+
"name": "workspaces_access_status",
|
|
4
|
+
"toolset": "workspaces",
|
|
5
|
+
"description": "Read which providers you, or the workspace, can run.",
|
|
6
|
+
"inputSchema": {
|
|
7
|
+
"type": "object",
|
|
8
|
+
"properties": {
|
|
9
|
+
"workspaceId": {
|
|
10
|
+
"type": "string",
|
|
11
|
+
"description": "Workspace id."
|
|
12
|
+
},
|
|
13
|
+
"scope": {
|
|
14
|
+
"type": "string",
|
|
15
|
+
"description": "Whose provider access to report. Anything other than 'workspace' reads as 'member'.",
|
|
16
|
+
"enum": [
|
|
17
|
+
"member",
|
|
18
|
+
"workspace"
|
|
19
|
+
]
|
|
20
|
+
}
|
|
21
|
+
},
|
|
22
|
+
"required": [
|
|
23
|
+
"workspaceId"
|
|
24
|
+
],
|
|
25
|
+
"additionalProperties": false
|
|
26
|
+
},
|
|
27
|
+
operation,
|
|
28
|
+
};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { operation } from '../../commands/workspaces/ai-provider-get.js';
|
|
2
|
+
export const tool = {
|
|
3
|
+
"name": "workspaces_ai_provider_get",
|
|
4
|
+
"toolset": "workspaces",
|
|
5
|
+
"description": "Read the workspace credentials and active auth method per provider. Secrets are masked.",
|
|
6
|
+
"inputSchema": {
|
|
7
|
+
"type": "object",
|
|
8
|
+
"properties": {
|
|
9
|
+
"workspaceId": {
|
|
10
|
+
"type": "string",
|
|
11
|
+
"description": "Workspace id."
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
"required": [
|
|
15
|
+
"workspaceId"
|
|
16
|
+
],
|
|
17
|
+
"additionalProperties": false
|
|
18
|
+
},
|
|
19
|
+
operation,
|
|
20
|
+
};
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { operation } from '../../commands/workspaces/ai-provider-remove.js';
|
|
2
|
+
export const tool = {
|
|
3
|
+
"name": "workspaces_ai_provider_remove",
|
|
4
|
+
"toolset": "workspaces",
|
|
5
|
+
"description": "Remove the credentials of one auth method. Which methods a provider takes is not in the schema. Admins only.",
|
|
6
|
+
"inputSchema": {
|
|
7
|
+
"type": "object",
|
|
8
|
+
"properties": {
|
|
9
|
+
"workspaceId": {
|
|
10
|
+
"type": "string",
|
|
11
|
+
"description": "Workspace id."
|
|
12
|
+
},
|
|
13
|
+
"provider": {
|
|
14
|
+
"type": "string",
|
|
15
|
+
"enum": [
|
|
16
|
+
"anthropic",
|
|
17
|
+
"openai"
|
|
18
|
+
]
|
|
19
|
+
},
|
|
20
|
+
"authMethod": {
|
|
21
|
+
"type": "string",
|
|
22
|
+
"description": "Must belong to the provider: anthropic takes the first three, openai api_key or codex.",
|
|
23
|
+
"enum": [
|
|
24
|
+
"api_key",
|
|
25
|
+
"aws_bedrock",
|
|
26
|
+
"claude_code",
|
|
27
|
+
"codex"
|
|
28
|
+
]
|
|
29
|
+
}
|
|
30
|
+
},
|
|
31
|
+
"required": [
|
|
32
|
+
"workspaceId",
|
|
33
|
+
"provider",
|
|
34
|
+
"authMethod"
|
|
35
|
+
],
|
|
36
|
+
"additionalProperties": false
|
|
37
|
+
},
|
|
38
|
+
operation,
|
|
39
|
+
};
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { operation } from '../../commands/workspaces/ai-provider-set.js';
|
|
2
|
+
export const tool = {
|
|
3
|
+
"name": "workspaces_ai_provider_set",
|
|
4
|
+
"toolset": "workspaces",
|
|
5
|
+
"description": "Store or switch workspace credentials. The body is one of the operation variants. Admins only.",
|
|
6
|
+
"inputSchema": {
|
|
7
|
+
"type": "object",
|
|
8
|
+
"properties": {
|
|
9
|
+
"workspaceId": {
|
|
10
|
+
"type": "string",
|
|
11
|
+
"description": "Workspace id."
|
|
12
|
+
},
|
|
13
|
+
"body": {
|
|
14
|
+
"description": "The whole request body.",
|
|
15
|
+
"oneOf": [
|
|
16
|
+
{
|
|
17
|
+
"type": "object",
|
|
18
|
+
"properties": {
|
|
19
|
+
"operation": {
|
|
20
|
+
"type": "string",
|
|
21
|
+
"const": "anthropic_api_key"
|
|
22
|
+
},
|
|
23
|
+
"anthropicApiKey": {
|
|
24
|
+
"type": "string",
|
|
25
|
+
"minLength": 1
|
|
26
|
+
}
|
|
27
|
+
},
|
|
28
|
+
"required": [
|
|
29
|
+
"operation"
|
|
30
|
+
]
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
"type": "object",
|
|
34
|
+
"properties": {
|
|
35
|
+
"operation": {
|
|
36
|
+
"type": "string",
|
|
37
|
+
"const": "anthropic_aws_bedrock"
|
|
38
|
+
},
|
|
39
|
+
"region": {
|
|
40
|
+
"type": "string",
|
|
41
|
+
"minLength": 1
|
|
42
|
+
},
|
|
43
|
+
"bedrockApiKey": {
|
|
44
|
+
"type": "string",
|
|
45
|
+
"minLength": 1
|
|
46
|
+
},
|
|
47
|
+
"validationModel": {
|
|
48
|
+
"type": "string"
|
|
49
|
+
}
|
|
50
|
+
},
|
|
51
|
+
"required": [
|
|
52
|
+
"operation",
|
|
53
|
+
"region"
|
|
54
|
+
]
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
"type": "object",
|
|
58
|
+
"properties": {
|
|
59
|
+
"operation": {
|
|
60
|
+
"type": "string",
|
|
61
|
+
"const": "anthropic_claude_code"
|
|
62
|
+
}
|
|
63
|
+
},
|
|
64
|
+
"required": [
|
|
65
|
+
"operation"
|
|
66
|
+
]
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
"type": "object",
|
|
70
|
+
"properties": {
|
|
71
|
+
"operation": {
|
|
72
|
+
"type": "string",
|
|
73
|
+
"const": "openai_api_key"
|
|
74
|
+
},
|
|
75
|
+
"openaiApiKey": {
|
|
76
|
+
"type": "string",
|
|
77
|
+
"minLength": 1
|
|
78
|
+
}
|
|
79
|
+
},
|
|
80
|
+
"required": [
|
|
81
|
+
"operation"
|
|
82
|
+
]
|
|
83
|
+
},
|
|
84
|
+
{
|
|
85
|
+
"type": "object",
|
|
86
|
+
"properties": {
|
|
87
|
+
"operation": {
|
|
88
|
+
"type": "string",
|
|
89
|
+
"const": "openai_codex"
|
|
90
|
+
}
|
|
91
|
+
},
|
|
92
|
+
"required": [
|
|
93
|
+
"operation"
|
|
94
|
+
]
|
|
95
|
+
}
|
|
96
|
+
]
|
|
97
|
+
}
|
|
98
|
+
},
|
|
99
|
+
"required": [
|
|
100
|
+
"workspaceId",
|
|
101
|
+
"body"
|
|
102
|
+
],
|
|
103
|
+
"additionalProperties": false
|
|
104
|
+
},
|
|
105
|
+
operation,
|
|
106
|
+
};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { operation } from '../../commands/workspaces/credits.js';
|
|
2
|
+
export const tool = {
|
|
3
|
+
"name": "workspaces_credits",
|
|
4
|
+
"toolset": "workspaces",
|
|
5
|
+
"description": "Read the credit balance and limit of a workspace.",
|
|
6
|
+
"inputSchema": {
|
|
7
|
+
"type": "object",
|
|
8
|
+
"properties": {
|
|
9
|
+
"workspaceId": {
|
|
10
|
+
"type": "string",
|
|
11
|
+
"description": "Workspace id."
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
"required": [
|
|
15
|
+
"workspaceId"
|
|
16
|
+
],
|
|
17
|
+
"additionalProperties": false
|
|
18
|
+
},
|
|
19
|
+
operation,
|
|
20
|
+
};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { operation } from '../../commands/workspaces/get.js';
|
|
2
|
+
export const tool = {
|
|
3
|
+
"name": "workspaces_get",
|
|
4
|
+
"toolset": "workspaces",
|
|
5
|
+
"description": "Read one workspace and your role in it.",
|
|
6
|
+
"inputSchema": {
|
|
7
|
+
"type": "object",
|
|
8
|
+
"properties": {
|
|
9
|
+
"workspaceId": {
|
|
10
|
+
"type": "string",
|
|
11
|
+
"description": "Workspace id."
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
"required": [
|
|
15
|
+
"workspaceId"
|
|
16
|
+
],
|
|
17
|
+
"additionalProperties": false
|
|
18
|
+
},
|
|
19
|
+
operation,
|
|
20
|
+
};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { operation } from '../../commands/workspaces/getting-started-get.js';
|
|
2
|
+
export const tool = {
|
|
3
|
+
"name": "workspaces_getting_started_get",
|
|
4
|
+
"toolset": "workspaces",
|
|
5
|
+
"description": "Read the getting-started guide and which milestones are done.",
|
|
6
|
+
"inputSchema": {
|
|
7
|
+
"type": "object",
|
|
8
|
+
"properties": {
|
|
9
|
+
"workspaceId": {
|
|
10
|
+
"type": "string",
|
|
11
|
+
"description": "Workspace id."
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
"required": [
|
|
15
|
+
"workspaceId"
|
|
16
|
+
],
|
|
17
|
+
"additionalProperties": false
|
|
18
|
+
},
|
|
19
|
+
operation,
|
|
20
|
+
};
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { operation } from '../../commands/workspaces/getting-started-update.js';
|
|
2
|
+
export const tool = {
|
|
3
|
+
"name": "workspaces_getting_started_update",
|
|
4
|
+
"toolset": "workspaces",
|
|
5
|
+
"description": "Dismiss the guide (empty body or action=dismiss) or record the preview milestone.",
|
|
6
|
+
"inputSchema": {
|
|
7
|
+
"type": "object",
|
|
8
|
+
"properties": {
|
|
9
|
+
"workspaceId": {
|
|
10
|
+
"type": "string",
|
|
11
|
+
"description": "Workspace id."
|
|
12
|
+
},
|
|
13
|
+
"action": {
|
|
14
|
+
"type": "string",
|
|
15
|
+
"enum": [
|
|
16
|
+
"dismiss",
|
|
17
|
+
"preview_opened"
|
|
18
|
+
]
|
|
19
|
+
}
|
|
20
|
+
},
|
|
21
|
+
"required": [
|
|
22
|
+
"workspaceId"
|
|
23
|
+
],
|
|
24
|
+
"additionalProperties": false
|
|
25
|
+
},
|
|
26
|
+
operation,
|
|
27
|
+
};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { operation } from '../../commands/workspaces/list.js';
|
|
2
|
+
export const tool = {
|
|
3
|
+
"name": "workspaces_list",
|
|
4
|
+
"toolset": "workspaces",
|
|
5
|
+
"description": "List the workspaces you belong to.",
|
|
6
|
+
"inputSchema": {
|
|
7
|
+
"type": "object",
|
|
8
|
+
"properties": {},
|
|
9
|
+
"additionalProperties": false
|
|
10
|
+
},
|
|
11
|
+
operation,
|
|
12
|
+
};
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { operation } from '../../commands/workspaces/members.js';
|
|
2
|
+
export const tool = {
|
|
3
|
+
"name": "workspaces_members",
|
|
4
|
+
"toolset": "workspaces",
|
|
5
|
+
"description": "List the members of a workspace, and its pending invitations unless excluded.",
|
|
6
|
+
"inputSchema": {
|
|
7
|
+
"type": "object",
|
|
8
|
+
"properties": {
|
|
9
|
+
"workspaceId": {
|
|
10
|
+
"type": "string",
|
|
11
|
+
"description": "Workspace id."
|
|
12
|
+
},
|
|
13
|
+
"includeInvitations": {
|
|
14
|
+
"type": "boolean",
|
|
15
|
+
"description": "Include pending invitations. Defaults to true; only the literal 'false' turns it off."
|
|
16
|
+
}
|
|
17
|
+
},
|
|
18
|
+
"required": [
|
|
19
|
+
"workspaceId"
|
|
20
|
+
],
|
|
21
|
+
"additionalProperties": false
|
|
22
|
+
},
|
|
23
|
+
operation,
|
|
24
|
+
};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { operation } from '../../commands/workspaces/sandbox-limit.js';
|
|
2
|
+
export const tool = {
|
|
3
|
+
"name": "workspaces_sandbox_limit",
|
|
4
|
+
"toolset": "workspaces",
|
|
5
|
+
"description": "Read how many sandboxes may run at once and which sessions are running now.",
|
|
6
|
+
"inputSchema": {
|
|
7
|
+
"type": "object",
|
|
8
|
+
"properties": {
|
|
9
|
+
"workspaceId": {
|
|
10
|
+
"type": "string",
|
|
11
|
+
"description": "Workspace id."
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
"required": [
|
|
15
|
+
"workspaceId"
|
|
16
|
+
],
|
|
17
|
+
"additionalProperties": false
|
|
18
|
+
},
|
|
19
|
+
operation,
|
|
20
|
+
};
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { operation } from '../../commands/workspaces/stop-sandboxes.js';
|
|
2
|
+
export const tool = {
|
|
3
|
+
"name": "workspaces_stop_sandboxes",
|
|
4
|
+
"toolset": "workspaces",
|
|
5
|
+
"description": "Pause the sandboxes of the given chat sessions.",
|
|
6
|
+
"inputSchema": {
|
|
7
|
+
"type": "object",
|
|
8
|
+
"properties": {
|
|
9
|
+
"workspaceId": {
|
|
10
|
+
"type": "string",
|
|
11
|
+
"description": "Workspace id."
|
|
12
|
+
},
|
|
13
|
+
"sessionIds": {
|
|
14
|
+
"minItems": 1,
|
|
15
|
+
"maxItems": 50,
|
|
16
|
+
"type": "array",
|
|
17
|
+
"items": {
|
|
18
|
+
"type": "string",
|
|
19
|
+
"format": "uuid",
|
|
20
|
+
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
},
|
|
24
|
+
"required": [
|
|
25
|
+
"workspaceId",
|
|
26
|
+
"sessionIds"
|
|
27
|
+
],
|
|
28
|
+
"additionalProperties": false
|
|
29
|
+
},
|
|
30
|
+
operation,
|
|
31
|
+
};
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { operation } from '../../commands/workspaces/usage.js';
|
|
2
|
+
export const tool = {
|
|
3
|
+
"name": "workspaces_usage",
|
|
4
|
+
"toolset": "workspaces",
|
|
5
|
+
"description": "Read sandbox usage by day and by project. start must precede end and the range is capped at 366 days.",
|
|
6
|
+
"inputSchema": {
|
|
7
|
+
"type": "object",
|
|
8
|
+
"properties": {
|
|
9
|
+
"workspaceId": {
|
|
10
|
+
"type": "string",
|
|
11
|
+
"description": "Workspace id."
|
|
12
|
+
},
|
|
13
|
+
"start": {
|
|
14
|
+
"type": "string",
|
|
15
|
+
"description": "Range start, any Date-parsable string. Default: end minus 30 days."
|
|
16
|
+
},
|
|
17
|
+
"end": {
|
|
18
|
+
"type": "string",
|
|
19
|
+
"description": "Range end, any Date-parsable string. Default: now."
|
|
20
|
+
}
|
|
21
|
+
},
|
|
22
|
+
"required": [
|
|
23
|
+
"workspaceId"
|
|
24
|
+
],
|
|
25
|
+
"additionalProperties": false
|
|
26
|
+
},
|
|
27
|
+
operation,
|
|
28
|
+
};
|
package/dist/program.js
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import { Command } from 'commander';
|
|
2
2
|
import { attachAuthCommands } from './commands/auth.js';
|
|
3
|
+
import { attachMcpCommands } from './commands/mcp.js';
|
|
3
4
|
import { operations } from './generated/commands/index.js';
|
|
4
5
|
import { attachOperation } from './runtime/attach.js';
|
|
5
6
|
import { createRuntime, ExitSignal } from './runtime/runtime.js';
|
|
6
7
|
import { version } from './runtime/version.js';
|
|
7
8
|
/**
|
|
8
|
-
* The command tree: `auth` by hand, everything else from the
|
|
9
|
-
* descriptors. Global flags are read before any subcommand runs, so the
|
|
9
|
+
* The command tree: `auth` and `mcp` by hand, everything else from the
|
|
10
|
+
* generated descriptors. Global flags are read before any subcommand runs, so the
|
|
10
11
|
* runtime the subcommands share is built once, from them.
|
|
11
12
|
*/
|
|
12
13
|
export function createProgram(options = {}) {
|
|
@@ -29,6 +30,7 @@ export function createProgram(options = {}) {
|
|
|
29
30
|
output: (outcome) => runtimeFor().output(outcome),
|
|
30
31
|
};
|
|
31
32
|
attachAuthCommands(program, lazy, options.auth);
|
|
33
|
+
attachMcpCommands(program, lazy, options.mcp);
|
|
32
34
|
for (const operation of operations)
|
|
33
35
|
attachOperation(program, operation, lazy);
|
|
34
36
|
return program;
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { closeSync, constants, fstatSync, mkdirSync, mkdtempSync, openSync, readFileSync, realpathSync, rmSync, statSync, writeFileSync, } from 'node:fs';
|
|
2
|
+
import { tmpdir } from 'node:os';
|
|
3
|
+
import { basename, isAbsolute, join, relative, resolve, sep } from 'node:path';
|
|
4
|
+
import { RefusedArgument } from './mcp-guard.js';
|
|
5
|
+
/**
|
|
6
|
+
* Where an attachment may come from. On the CLI a person types the path; here
|
|
7
|
+
* the model chooses it, so a prompt that says "attach ~/.config/kosuke/credentials.json"
|
|
8
|
+
* would otherwise upload a token to the platform and into the transcript. Every
|
|
9
|
+
* path is resolved through its symlinks first, then held to one root, and a
|
|
10
|
+
* hidden segment (`.ssh`, `.aws`, `.config`, `.env`) is refused even inside it:
|
|
11
|
+
* those hold credentials far more often than screenshots.
|
|
12
|
+
*
|
|
13
|
+
* The bytes that leave are then read once, through a handle opened on the
|
|
14
|
+
* path that passed and checked to be a regular file, and staged as a private
|
|
15
|
+
* copy for the shared multipart code to pick up. Reading the original path
|
|
16
|
+
* again later would trust a name a concurrent writer could have re-pointed,
|
|
17
|
+
* and a FIFO or a device under the root would hang the server for good.
|
|
18
|
+
*/
|
|
19
|
+
/** Mirrors the server's cap, which the shared attachment code enforces again. */
|
|
20
|
+
const MAX_ATTACHMENT_BYTES = 10 * 1024 * 1024;
|
|
21
|
+
export function defaultAttachmentPolicy(env = process.env) {
|
|
22
|
+
return { root: resolve(env.KOSUKE_MCP_ATTACHMENT_ROOT ?? process.cwd()) };
|
|
23
|
+
}
|
|
24
|
+
/** The real path of a file that may be attached, or a refusal that says why. */
|
|
25
|
+
export function confineAttachment(path, policy) {
|
|
26
|
+
const given = isAbsolute(path) ? path : resolve(policy.root, path);
|
|
27
|
+
let root;
|
|
28
|
+
let real;
|
|
29
|
+
try {
|
|
30
|
+
root = realpathSync(policy.root);
|
|
31
|
+
}
|
|
32
|
+
catch {
|
|
33
|
+
throw new RefusedArgument(`The attachment root ${policy.root} does not exist.`);
|
|
34
|
+
}
|
|
35
|
+
try {
|
|
36
|
+
real = realpathSync(given);
|
|
37
|
+
}
|
|
38
|
+
catch {
|
|
39
|
+
throw new RefusedArgument(`Attachment ${path} does not exist or cannot be read.`);
|
|
40
|
+
}
|
|
41
|
+
const inside = relative(root, real);
|
|
42
|
+
if (inside === '' || inside.startsWith('..') || isAbsolute(inside)) {
|
|
43
|
+
throw new RefusedArgument(`Attachment ${path} resolves to ${real}, outside the attachment root ${root}. ` +
|
|
44
|
+
`Only files under the root can be attached; start the server with ` +
|
|
45
|
+
`--attachment-root or KOSUKE_MCP_ATTACHMENT_ROOT to change it.`);
|
|
46
|
+
}
|
|
47
|
+
const hidden = inside.split(sep).find((segment) => segment.startsWith('.'));
|
|
48
|
+
if (hidden) {
|
|
49
|
+
throw new RefusedArgument(`Attachment ${path} is under a hidden path (${hidden}), which this server does not ` +
|
|
50
|
+
`attach: such paths hold credentials more often than screenshots.`);
|
|
51
|
+
}
|
|
52
|
+
// Checked before any open: opening a FIFO for reading blocks until a writer
|
|
53
|
+
// arrives, and a directory or a device is not something to upload either.
|
|
54
|
+
if (!statSync(real).isFile()) {
|
|
55
|
+
throw new RefusedArgument(`Attachment ${path} is not a regular file.`);
|
|
56
|
+
}
|
|
57
|
+
return real;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Read each confined file once through its own handle and stage a private
|
|
61
|
+
* copy under a fresh 0700 directory. The handle is what is checked (a regular
|
|
62
|
+
* file, within the size cap) and what is read, so a path swapped after the
|
|
63
|
+
* check changes nothing; the copy keeps the file's name, which is what the
|
|
64
|
+
* platform sees, and lives only for this one request.
|
|
65
|
+
*/
|
|
66
|
+
export function stageAttachments(realPaths) {
|
|
67
|
+
if (!realPaths.length)
|
|
68
|
+
return { paths: [], cleanup: () => { } };
|
|
69
|
+
const directory = mkdtempSync(join(tmpdir(), 'kosuke-mcp-attachment-'));
|
|
70
|
+
const cleanup = () => rmSync(directory, { recursive: true, force: true });
|
|
71
|
+
try {
|
|
72
|
+
const paths = realPaths.map((real, index) => {
|
|
73
|
+
const bytes = readOnce(real);
|
|
74
|
+
const slot = join(directory, String(index));
|
|
75
|
+
mkdirSync(slot, { mode: 0o700 });
|
|
76
|
+
const copy = join(slot, basename(real));
|
|
77
|
+
writeFileSync(copy, bytes, { mode: 0o600 });
|
|
78
|
+
return copy;
|
|
79
|
+
});
|
|
80
|
+
return { paths, cleanup };
|
|
81
|
+
}
|
|
82
|
+
catch (error) {
|
|
83
|
+
cleanup();
|
|
84
|
+
throw error;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
function readOnce(real) {
|
|
88
|
+
// O_NOFOLLOW: the leaf must still be the file realpath saw, not a symlink
|
|
89
|
+
// planted since. O_NONBLOCK: should it have become a FIFO, open returns
|
|
90
|
+
// instead of waiting, and fstat refuses it below.
|
|
91
|
+
let fd;
|
|
92
|
+
try {
|
|
93
|
+
fd = openSync(real, constants.O_RDONLY | constants.O_NOFOLLOW | constants.O_NONBLOCK);
|
|
94
|
+
}
|
|
95
|
+
catch {
|
|
96
|
+
throw new RefusedArgument(`Attachment ${real} cannot be opened.`);
|
|
97
|
+
}
|
|
98
|
+
try {
|
|
99
|
+
const stat = fstatSync(fd);
|
|
100
|
+
if (!stat.isFile())
|
|
101
|
+
throw new RefusedArgument(`Attachment ${real} is not a regular file.`);
|
|
102
|
+
if (stat.size > MAX_ATTACHMENT_BYTES) {
|
|
103
|
+
throw new RefusedArgument(`Attachment ${real} is larger than the 10 MiB attachment limit.`);
|
|
104
|
+
}
|
|
105
|
+
return readFileSync(fd);
|
|
106
|
+
}
|
|
107
|
+
finally {
|
|
108
|
+
closeSync(fd);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What a composite tool is made of: the contract the server gives it, and the
|
|
3
|
+
* small readings of an invocation the three tools share. Kept apart from the
|
|
4
|
+
* tools themselves so the open-pr helpers can use them without a cycle.
|
|
5
|
+
*/
|
|
6
|
+
export const COMPOSITE_TOOLSET = 'composite';
|
|
7
|
+
export function pick(args, keys) {
|
|
8
|
+
return Object.fromEntries(keys.filter((key) => args[key] !== undefined).map((k) => [k, args[k]]));
|
|
9
|
+
}
|
|
10
|
+
export function data(invocation) {
|
|
11
|
+
const body = invocation.body;
|
|
12
|
+
const value = body?.data;
|
|
13
|
+
return typeof value === 'object' && value !== null ? value : {};
|
|
14
|
+
}
|
|
15
|
+
export function tail(messages, count = 3) {
|
|
16
|
+
if (!Array.isArray(messages))
|
|
17
|
+
return [];
|
|
18
|
+
return messages.slice(-count).map((m) => ({ role: m.role, content: messageText(m) }));
|
|
19
|
+
}
|
|
20
|
+
/** Blocks whose `content` is prose addressed to the reader; tool and plan blocks are not. */
|
|
21
|
+
const PROSE_BLOCKS = new Set(['text', 'clarification']);
|
|
22
|
+
/**
|
|
23
|
+
* The text of a message. A user message carries it in `content`; an
|
|
24
|
+
* assistant message carries `content: null` and its text in `blocks`, beside
|
|
25
|
+
* tool blocks. Prose blocks joined in order, so a clarification question is
|
|
26
|
+
* what comes back for a job that is awaiting one; `content` as the fallback;
|
|
27
|
+
* null when there is no text at all.
|
|
28
|
+
*/
|
|
29
|
+
export function messageText(message) {
|
|
30
|
+
const blocks = Array.isArray(message.blocks) ? message.blocks : [];
|
|
31
|
+
const text = blocks
|
|
32
|
+
.filter((block) => PROSE_BLOCKS.has(String(block.type)) && typeof block.content === 'string')
|
|
33
|
+
.map((block) => block.content)
|
|
34
|
+
.join('\n\n')
|
|
35
|
+
.trim();
|
|
36
|
+
if (text)
|
|
37
|
+
return text;
|
|
38
|
+
return typeof message.content === 'string' && message.content.trim() ? message.content : null;
|
|
39
|
+
}
|
|
40
|
+
export function done(last, payload, warnings = last.warnings) {
|
|
41
|
+
return { ok: true, status: last.status, body: { data: payload }, warnings };
|
|
42
|
+
}
|
|
43
|
+
/** A step that failed: its error, named, so the model knows how far it got. */
|
|
44
|
+
export function failed(step, name) {
|
|
45
|
+
const body = typeof step.body === 'object' && step.body !== null
|
|
46
|
+
? { step: name, ...step.body }
|
|
47
|
+
: { step: name, error: step.body ?? `HTTP ${step.status}` };
|
|
48
|
+
return { ...step, body };
|
|
49
|
+
}
|