mirror of
https://github.com/qwibitai/nanoclaw.git
synced 2026-06-04 10:14:47 +08:00
feat: fetch gateway skill from OneCLI API with static fallback
This commit is contained in:
@@ -0,0 +1,85 @@
|
||||
---
|
||||
name: onecli-gateway
|
||||
description: >-
|
||||
OneCLI Gateway: transparent HTTPS proxy that injects stored credentials
|
||||
into outbound calls. You MUST use this skill when the user asks you to
|
||||
read emails, check calendar, access GitHub repos, create issues, check
|
||||
Stripe payments, or interact with ANY external service or API. Do NOT
|
||||
use browser extensions or OAuth CLI tools. Make HTTP requests directly;
|
||||
the gateway injects credentials automatically.
|
||||
compatibility: Requires HTTPS_PROXY set in environment (automatic when launched via `onecli run`)
|
||||
metadata:
|
||||
author: onecli
|
||||
version: "0.5.0"
|
||||
---
|
||||
|
||||
# OneCLI Gateway
|
||||
|
||||
Your outbound HTTPS traffic is transparently proxied through the OneCLI
|
||||
gateway, which injects stored credentials at the proxy boundary. You never
|
||||
see or handle credential values directly.
|
||||
|
||||
## How to Access External Services
|
||||
|
||||
You have direct HTTP access to external APIs. OAuth apps (Gmail, GitHub,
|
||||
Google Calendar, Google Drive, etc.) and API key services are all available
|
||||
through the gateway. Just make the request directly; the gateway injects
|
||||
credentials if the app is connected. If not, it returns an error with a
|
||||
connect URL you can present to the user.
|
||||
|
||||
## Making Requests
|
||||
|
||||
Call the real API URL. The gateway intercepts the request and injects
|
||||
credentials automatically.
|
||||
|
||||
```bash
|
||||
curl -s "https://gmail.googleapis.com/gmail/v1/users/me/messages?maxResults=5"
|
||||
curl -s "https://api.github.com/user/repos?per_page=10"
|
||||
curl -s "https://api.stripe.com/v1/charges?limit=5"
|
||||
```
|
||||
|
||||
Standard HTTP clients (curl, fetch, requests, axios, Go net/http, git) all
|
||||
honor the `HTTPS_PROXY` environment variable automatically. You do not need
|
||||
to set any auth headers.
|
||||
|
||||
## Credential Stubs for MCP Servers
|
||||
|
||||
Some MCP servers need local credential files to start. Stubs for connected
|
||||
apps are pre-written automatically. Files containing `"onecli-managed"`
|
||||
values are managed by OneCLI — do NOT modify or delete them.
|
||||
|
||||
If an MCP server won't start due to missing credentials, create stubs
|
||||
**before** starting it. Use `"onecli-managed"` as the placeholder for all
|
||||
secret values, with file permissions `0600`. See the guide at:
|
||||
https://www.onecli.sh/docs/guides/credential-stubs/general-app
|
||||
|
||||
## When a Request Fails
|
||||
|
||||
If you get a 401, 403, or a gateway error (e.g., `app_not_connected`):
|
||||
|
||||
**Step 1 — Show the user a connect link.** Use the `connect_url` from the
|
||||
error response:
|
||||
|
||||
> To connect [service], open this link:
|
||||
> [connect_url from the error response]
|
||||
|
||||
If there is no `connect_url` in the error, tell the user to open the
|
||||
OneCLI dashboard and connect the service there.
|
||||
|
||||
**Step 2 — Retry after the user connects.** Let the user know you will
|
||||
retry once they have connected. When they confirm, retry the original
|
||||
request. If the retry still fails, ask if they need help with the setup.
|
||||
|
||||
## Rules
|
||||
|
||||
- **Never** say "I don't have access to X" without first making the HTTP
|
||||
request through the proxy.
|
||||
- **Never** use browser extensions, gcloud, or manual auth flows. The
|
||||
gateway handles credentials for you.
|
||||
- **Never** ask the user for API keys or tokens directly. Direct them to
|
||||
connect the service in the OneCLI dashboard.
|
||||
- **Never** suggest the user open Gmail/Calendar/GitHub in their browser
|
||||
when they ask you to read or interact with those services. You have API
|
||||
access. Use it.
|
||||
- If the gateway returns a policy error (403 with a JSON body), respect
|
||||
the block. Do not retry or circumvent it.
|
||||
@@ -1,67 +0,0 @@
|
||||
---
|
||||
name: onecli-gateway
|
||||
description: >-
|
||||
Handle credentials and authentication for external services. Use when you
|
||||
hit a 401, 403, or app_not_connected error, or when the user asks you to
|
||||
access an external service (Gmail, GitHub, Slack, Calendar, Stripe, etc.).
|
||||
Do NOT use browser extensions or manual auth flows — make HTTP requests
|
||||
directly; the OneCLI proxy injects credentials automatically.
|
||||
---
|
||||
|
||||
# OneCLI Gateway: Credentials & Authentication
|
||||
|
||||
Your container routes all HTTPS traffic through the OneCLI proxy, which
|
||||
injects stored credentials (API keys, OAuth tokens) at the proxy boundary.
|
||||
You never see or handle credential values directly.
|
||||
|
||||
## Making Requests
|
||||
|
||||
Call the real API URL. The proxy intercepts and injects credentials automatically.
|
||||
|
||||
```bash
|
||||
curl -s "https://gmail.googleapis.com/gmail/v1/users/me/messages?maxResults=5"
|
||||
curl -s "https://api.github.com/user/repos?per_page=10"
|
||||
curl -s "https://api.stripe.com/v1/charges?limit=5"
|
||||
```
|
||||
|
||||
Any HTTP client (curl, fetch, axios, Python requests, Go net/http, git) honors
|
||||
`HTTPS_PROXY` automatically. You do not need to set auth headers.
|
||||
|
||||
If a tool or library validates credentials locally before making the request,
|
||||
pass any placeholder value (a fake string). The proxy replaces it with real
|
||||
credentials at request time.
|
||||
|
||||
## When a Request Fails (401 / 403 / app_not_connected)
|
||||
|
||||
### Step 1 — Show the user a connect link
|
||||
|
||||
If the error response includes a `connect_url`, share it directly:
|
||||
|
||||
> To connect [service], open this link:
|
||||
> [connect_url from the error response]
|
||||
|
||||
If there's no `connect_url`, tell the user to open the OneCLI dashboard and
|
||||
connect the service there.
|
||||
|
||||
Do NOT ask the user for API keys or tokens. Do NOT suggest pasting credentials
|
||||
into chat. The fix is always connecting the service in OneCLI.
|
||||
|
||||
### Step 2 — Retry after the user connects
|
||||
|
||||
After showing the link, let the user know you'll retry once they've connected.
|
||||
When they confirm (or after a reasonable pause), retry the original request.
|
||||
|
||||
If the retry still fails, ask the user if they need help with the OneCLI setup.
|
||||
|
||||
## Rules
|
||||
|
||||
- **Never** say "I don't have access to X" without first making the HTTP
|
||||
request through the proxy.
|
||||
- **Never** use browser extensions, gcloud, or manual auth flows. The proxy
|
||||
handles credentials for you.
|
||||
- **Never** ask the user for API keys, tokens, or passwords directly.
|
||||
- **Never** suggest the user open Gmail/Calendar/GitHub in their browser
|
||||
when they ask you to read or interact with those services. You have API
|
||||
access — use it.
|
||||
- If the proxy returns a policy error (403 with a JSON body), respect the
|
||||
block. Do not retry or circumvent it.
|
||||
+1
-1
@@ -26,7 +26,7 @@
|
||||
"dependencies": {
|
||||
"@clack/core": "^1.2.0",
|
||||
"@clack/prompts": "^1.2.0",
|
||||
"@onecli-sh/sdk": "^0.3.1",
|
||||
"@onecli-sh/sdk": "^0.5.0",
|
||||
"better-sqlite3": "11.10.0",
|
||||
"chat": "^4.24.0",
|
||||
"cron-parser": "5.5.0",
|
||||
|
||||
Generated
+5
-5
@@ -15,8 +15,8 @@ importers:
|
||||
specifier: ^1.2.0
|
||||
version: 1.2.0
|
||||
'@onecli-sh/sdk':
|
||||
specifier: ^0.3.1
|
||||
version: 0.3.1
|
||||
specifier: ^0.5.0
|
||||
version: 0.5.0
|
||||
better-sqlite3:
|
||||
specifier: 11.10.0
|
||||
version: 11.10.0
|
||||
@@ -303,8 +303,8 @@ packages:
|
||||
'@emnapi/core': ^1.7.1
|
||||
'@emnapi/runtime': ^1.7.1
|
||||
|
||||
'@onecli-sh/sdk@0.3.1':
|
||||
resolution: {integrity: sha512-oMSa4DUCVS52vec41nFOg3XdCBTbMVEZdCFCsaUd9sRXVorCPWd3VyZq4giXsmk4g09DA/zLjsnrY7l6G94Ulg==}
|
||||
'@onecli-sh/sdk@0.5.0':
|
||||
resolution: {integrity: sha512-oe5Yx9o98v6N1PgzcCR7nULHHqcqKWNJIDOHGOSNX+l20mLlZpFUqfKPeFmsojBNRQMoqbvZQKUlFMp6gVuYBA==}
|
||||
engines: {node: '>=20'}
|
||||
|
||||
'@oxc-project/types@0.124.0':
|
||||
@@ -1665,7 +1665,7 @@ snapshots:
|
||||
'@tybys/wasm-util': 0.10.1
|
||||
optional: true
|
||||
|
||||
'@onecli-sh/sdk@0.3.1': {}
|
||||
'@onecli-sh/sdk@0.5.0': {}
|
||||
|
||||
'@oxc-project/types@0.124.0': {}
|
||||
|
||||
|
||||
+20
-3
@@ -132,7 +132,7 @@ async function spawnContainer(session: Session): Promise<void> {
|
||||
// buildMounts and buildContainerArgs so side effects (mkdir, etc.) fire once.
|
||||
const { provider, contribution } = resolveProviderContribution(session, agentGroup, containerConfig);
|
||||
|
||||
const mounts = buildMounts(agentGroup, session, containerConfig, contribution);
|
||||
const mounts = await buildMounts(agentGroup, session, containerConfig, contribution);
|
||||
const containerName = `nanoclaw-v2-${agentGroup.folder}-${Date.now()}`;
|
||||
// OneCLI agent identifier is always the agent group id — stable across
|
||||
// sessions and reversible via getAgentGroup() for approval routing.
|
||||
@@ -239,12 +239,12 @@ function resolveProviderContribution(
|
||||
return { provider, contribution };
|
||||
}
|
||||
|
||||
function buildMounts(
|
||||
async function buildMounts(
|
||||
agentGroup: AgentGroup,
|
||||
session: Session,
|
||||
containerConfig: import('./container-config.js').ContainerConfig,
|
||||
providerContribution: ProviderContainerContribution,
|
||||
): VolumeMount[] {
|
||||
): Promise<VolumeMount[]> {
|
||||
const projectRoot = process.cwd();
|
||||
|
||||
// Per-group filesystem state lives forever after first creation. Init is
|
||||
@@ -252,6 +252,23 @@ function buildMounts(
|
||||
// is a no-op for groups that have spawned before.
|
||||
initGroupFilesystem(agentGroup);
|
||||
|
||||
// Fetch the latest gateway skill from the API; fall back to the static copy.
|
||||
const skillDir = path.join(projectRoot, 'container', 'skills', 'onecli-gateway');
|
||||
const skillPath = path.join(skillDir, 'SKILL.md');
|
||||
const fallbackPath = path.join(skillDir, 'SKILL.fallback.md');
|
||||
try {
|
||||
const skill = await onecli.getGatewaySkill();
|
||||
const existing = fs.existsSync(skillPath) ? fs.readFileSync(skillPath, 'utf8') : '';
|
||||
if (skill && skill !== existing) {
|
||||
fs.writeFileSync(skillPath, skill);
|
||||
}
|
||||
} catch {
|
||||
if (!fs.existsSync(skillPath) && fs.existsSync(fallbackPath)) {
|
||||
fs.copyFileSync(fallbackPath, skillPath);
|
||||
}
|
||||
log.warn('Could not fetch gateway skill from OneCLI API; using static fallback');
|
||||
}
|
||||
|
||||
// Sync skill symlinks based on container.json selection before mounting.
|
||||
const claudeDir = path.join(DATA_DIR, 'v2-sessions', agentGroup.id, '.claude-shared');
|
||||
syncSkillSymlinks(claudeDir, containerConfig);
|
||||
|
||||
Reference in New Issue
Block a user