@skhema/cli 0.3.1
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 +163 -0
- package/bin/skhema.js +2 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +92 -0
- package/dist/commands/agent.d.ts +3 -0
- package/dist/commands/agent.d.ts.map +1 -0
- package/dist/commands/agent.js +154 -0
- package/dist/commands/api.d.ts +19 -0
- package/dist/commands/api.d.ts.map +1 -0
- package/dist/commands/api.js +97 -0
- package/dist/commands/auth.d.ts +3 -0
- package/dist/commands/auth.d.ts.map +1 -0
- package/dist/commands/auth.js +403 -0
- package/dist/commands/component.d.ts +3 -0
- package/dist/commands/component.d.ts.map +1 -0
- package/dist/commands/component.js +221 -0
- package/dist/commands/contribute.d.ts +3 -0
- package/dist/commands/contribute.d.ts.map +1 -0
- package/dist/commands/contribute.js +318 -0
- package/dist/commands/element.d.ts +7 -0
- package/dist/commands/element.d.ts.map +1 -0
- package/dist/commands/element.js +167 -0
- package/dist/commands/export.d.ts +9 -0
- package/dist/commands/export.d.ts.map +1 -0
- package/dist/commands/export.js +42 -0
- package/dist/commands/init.d.ts +49 -0
- package/dist/commands/init.d.ts.map +1 -0
- package/dist/commands/init.js +275 -0
- package/dist/commands/link.d.ts +8 -0
- package/dist/commands/link.d.ts.map +1 -0
- package/dist/commands/link.js +70 -0
- package/dist/commands/resource.d.ts +3 -0
- package/dist/commands/resource.d.ts.map +1 -0
- package/dist/commands/resource.js +88 -0
- package/dist/commands/skills.d.ts +3 -0
- package/dist/commands/skills.d.ts.map +1 -0
- package/dist/commands/skills.js +113 -0
- package/dist/commands/strategy.d.ts +3 -0
- package/dist/commands/strategy.d.ts.map +1 -0
- package/dist/commands/strategy.js +143 -0
- package/dist/commands/webhook.d.ts +3 -0
- package/dist/commands/webhook.d.ts.map +1 -0
- package/dist/commands/webhook.js +77 -0
- package/dist/commands/workspace.d.ts +8 -0
- package/dist/commands/workspace.d.ts.map +1 -0
- package/dist/commands/workspace.js +219 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +1 -0
- package/dist/lib/api/client.d.ts +27 -0
- package/dist/lib/api/client.d.ts.map +1 -0
- package/dist/lib/api/client.js +71 -0
- package/dist/lib/api/command-helpers.d.ts +27 -0
- package/dist/lib/api/command-helpers.d.ts.map +1 -0
- package/dist/lib/api/command-helpers.js +77 -0
- package/dist/lib/api/credential-context.d.ts +3 -0
- package/dist/lib/api/credential-context.d.ts.map +1 -0
- package/dist/lib/api/credential-context.js +13 -0
- package/dist/lib/api/export-cli.d.ts +14 -0
- package/dist/lib/api/export-cli.d.ts.map +1 -0
- package/dist/lib/api/export-cli.js +26 -0
- package/dist/lib/api/formats.d.ts +12 -0
- package/dist/lib/api/formats.d.ts.map +1 -0
- package/dist/lib/api/formats.js +18 -0
- package/dist/lib/api/passthrough.d.ts +34 -0
- package/dist/lib/api/passthrough.d.ts.map +1 -0
- package/dist/lib/api/passthrough.js +106 -0
- package/dist/lib/api/payload.d.ts +16 -0
- package/dist/lib/api/payload.d.ts.map +1 -0
- package/dist/lib/api/payload.js +53 -0
- package/dist/lib/api/run-command.d.ts +69 -0
- package/dist/lib/api/run-command.d.ts.map +1 -0
- package/dist/lib/api/run-command.js +127 -0
- package/dist/lib/api/scope.d.ts +8 -0
- package/dist/lib/api/scope.d.ts.map +1 -0
- package/dist/lib/api/scope.js +16 -0
- package/dist/lib/api/transfer.d.ts +57 -0
- package/dist/lib/api/transfer.d.ts.map +1 -0
- package/dist/lib/api/transfer.js +153 -0
- package/dist/lib/auth/api-key-admin.d.ts +39 -0
- package/dist/lib/auth/api-key-admin.d.ts.map +1 -0
- package/dist/lib/auth/api-key-admin.js +66 -0
- package/dist/lib/auth/api-key-store.d.ts +8 -0
- package/dist/lib/auth/api-key-store.d.ts.map +1 -0
- package/dist/lib/auth/api-key-store.js +109 -0
- package/dist/lib/auth/device-flow.d.ts +4 -0
- package/dist/lib/auth/device-flow.d.ts.map +1 -0
- package/dist/lib/auth/device-flow.js +109 -0
- package/dist/lib/auth/entitlements.d.ts +16 -0
- package/dist/lib/auth/entitlements.d.ts.map +1 -0
- package/dist/lib/auth/entitlements.js +89 -0
- package/dist/lib/auth/localhost-flow.d.ts +4 -0
- package/dist/lib/auth/localhost-flow.d.ts.map +1 -0
- package/dist/lib/auth/localhost-flow.js +134 -0
- package/dist/lib/auth/oauth-client.d.ts +33 -0
- package/dist/lib/auth/oauth-client.d.ts.map +1 -0
- package/dist/lib/auth/oauth-client.js +96 -0
- package/dist/lib/auth/token-store.d.ts +6 -0
- package/dist/lib/auth/token-store.d.ts.map +1 -0
- package/dist/lib/auth/token-store.js +136 -0
- package/dist/lib/auth/types.d.ts +49 -0
- package/dist/lib/auth/types.d.ts.map +1 -0
- package/dist/lib/auth/types.js +1 -0
- package/dist/lib/banner.d.ts +2 -0
- package/dist/lib/banner.d.ts.map +1 -0
- package/dist/lib/banner.js +82 -0
- package/dist/lib/config-store.d.ts +23 -0
- package/dist/lib/config-store.d.ts.map +1 -0
- package/dist/lib/config-store.js +53 -0
- package/dist/lib/config.d.ts +20 -0
- package/dist/lib/config.d.ts.map +1 -0
- package/dist/lib/config.js +33 -0
- package/dist/lib/contribute/author-resolver.d.ts +13 -0
- package/dist/lib/contribute/author-resolver.d.ts.map +1 -0
- package/dist/lib/contribute/author-resolver.js +63 -0
- package/dist/lib/contribute/embed-generator.d.ts +4 -0
- package/dist/lib/contribute/embed-generator.d.ts.map +1 -0
- package/dist/lib/contribute/embed-generator.js +108 -0
- package/dist/lib/contribute/link-generator.d.ts +11 -0
- package/dist/lib/contribute/link-generator.d.ts.map +1 -0
- package/dist/lib/contribute/link-generator.js +119 -0
- package/dist/lib/contribute/method-schema.d.ts +239 -0
- package/dist/lib/contribute/method-schema.d.ts.map +1 -0
- package/dist/lib/contribute/method-schema.js +172 -0
- package/dist/lib/contribute/profile-resolver.d.ts +2 -0
- package/dist/lib/contribute/profile-resolver.d.ts.map +1 -0
- package/dist/lib/contribute/profile-resolver.js +25 -0
- package/dist/lib/contribute/types.d.ts +91 -0
- package/dist/lib/contribute/types.d.ts.map +1 -0
- package/dist/lib/contribute/types.js +1 -0
- package/dist/lib/mcp/registrar.d.ts +26 -0
- package/dist/lib/mcp/registrar.d.ts.map +1 -0
- package/dist/lib/mcp/registrar.js +69 -0
- package/dist/lib/mcp/registry.d.ts +54 -0
- package/dist/lib/mcp/registry.d.ts.map +1 -0
- package/dist/lib/mcp/registry.js +105 -0
- package/dist/lib/open.d.ts +2 -0
- package/dist/lib/open.d.ts.map +1 -0
- package/dist/lib/open.js +24 -0
- package/dist/lib/output.d.ts +25 -0
- package/dist/lib/output.d.ts.map +1 -0
- package/dist/lib/output.js +74 -0
- package/dist/lib/skills/installer.d.ts +4 -0
- package/dist/lib/skills/installer.d.ts.map +1 -0
- package/dist/lib/skills/installer.js +100 -0
- package/dist/lib/skills/lock.d.ts +4 -0
- package/dist/lib/skills/lock.d.ts.map +1 -0
- package/dist/lib/skills/lock.js +28 -0
- package/dist/lib/skills/platform-detect.d.ts +4 -0
- package/dist/lib/skills/platform-detect.d.ts.map +1 -0
- package/dist/lib/skills/platform-detect.js +89 -0
- package/dist/lib/skills/types.d.ts +29 -0
- package/dist/lib/skills/types.d.ts.map +1 -0
- package/dist/lib/skills/types.js +1 -0
- package/dist/postinstall.d.ts +2 -0
- package/dist/postinstall.d.ts.map +1 -0
- package/dist/postinstall.js +11 -0
- package/package.json +67 -0
- package/skills/.manifest.json +15 -0
- package/skills/skhema-challenge-framing/SKILL.md +28 -0
- package/skills/skhema-coherence-check/SKILL.md +28 -0
- package/skills/skhema-element-decomposition/SKILL.md +28 -0
- package/skills/skhema-element-writer/SKILL.md +20 -0
- package/skills/skhema-judgment-audit/SKILL.md +28 -0
- package/skills/skhema-semantic-sharpening/SKILL.md +28 -0
- package/skills/skhema-strategy-advisor/SKILL.md +20 -0
- package/skills/skhema-workspace-navigator/SKILL.md +20 -0
package/README.md
ADDED
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
# @skhema/cli
|
|
2
|
+
|
|
3
|
+
The Skhema command-line interface (`skhema`). Authenticate as a human or an
|
|
4
|
+
agent, then drive the [Skhema Public API](https://skhema.com/docs/api) from your
|
|
5
|
+
terminal, CI, or an autonomous agent — workspaces, elements, components,
|
|
6
|
+
strategies, exports, resources, and webhooks, plus one-command agent onboarding.
|
|
7
|
+
|
|
8
|
+
## Install
|
|
9
|
+
|
|
10
|
+
```sh
|
|
11
|
+
npm i -g @skhema/cli
|
|
12
|
+
skhema --help
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
Requires Node ≥ 18.
|
|
16
|
+
|
|
17
|
+
## Agent quickstart
|
|
18
|
+
|
|
19
|
+
Point an agent (OpenClaw / Hermes / any CI job) at Skhema in one line — install
|
|
20
|
+
skills, register the MCP server, and verify an API connection:
|
|
21
|
+
|
|
22
|
+
```sh
|
|
23
|
+
SKHEMA_API_KEY=sk_live_… npx @skhema/cli init --yes --json
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
`skhema init`:
|
|
27
|
+
|
|
28
|
+
- **Verifies a credential** — resolves `--api-key` / `SKHEMA_API_KEY` / a stored
|
|
29
|
+
key / an OAuth session (same precedence as every command), exchanges + probes
|
|
30
|
+
it, and reports the org and permission. An explicit `--api-key` is stored for
|
|
31
|
+
future commands (never overwriting a different stored key without `--force`).
|
|
32
|
+
- **Installs skills** into every detected agent platform (idempotent — reruns
|
|
33
|
+
report `unchanged`).
|
|
34
|
+
- **Registers the MCP server** (`https://mcp-server.skhema.com/mcp`, Streamable
|
|
35
|
+
HTTP) in detected clients by safely merging a dedicated config file and writing
|
|
36
|
+
a `.bak` backup — **or printing copy-paste instructions** when a client's config
|
|
37
|
+
isn't cleanly writable. `--yes` registers non-interactively; without it, `init`
|
|
38
|
+
reports what it _would_ do and prompts only on a TTY. MCP auth is per-client
|
|
39
|
+
browser OAuth on first use — the CLI only wires the URL, never a credential.
|
|
40
|
+
|
|
41
|
+
`--yes` never prompts and does everything resolvable, reporting what it skipped.
|
|
42
|
+
Re-running is safe: every step reports already-done work as such.
|
|
43
|
+
|
|
44
|
+
### Raw API passthrough
|
|
45
|
+
|
|
46
|
+
Reach any current or future gateway route without waiting for a dedicated verb —
|
|
47
|
+
with the CLI's credential resolution, error mapping, and exit codes:
|
|
48
|
+
|
|
49
|
+
```sh
|
|
50
|
+
skhema api GET /workspaces # leading /v1 is optional
|
|
51
|
+
skhema api POST /workspaces/ws_1/elements --data @element.json
|
|
52
|
+
skhema api GET /workspaces --query limit=10 --query type=strategy
|
|
53
|
+
echo '{"name":"Q3"}' | skhema api POST /workspaces --data -
|
|
54
|
+
skhema api routes # every live route from the OpenAPI spec
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
`--data` takes a JSON literal, `@file`, or `-` (stdin). `--query k=v` repeats. The
|
|
58
|
+
passthrough refuses any host other than the Skhema gateway.
|
|
59
|
+
|
|
60
|
+
## Credential lanes
|
|
61
|
+
|
|
62
|
+
The CLI accepts **two kinds** of credential and resolves them in a fixed
|
|
63
|
+
precedence order (highest first):
|
|
64
|
+
|
|
65
|
+
1. `--api-key <sk_live_…>` global flag (this invocation only)
|
|
66
|
+
2. `SKHEMA_API_KEY` environment variable
|
|
67
|
+
3. A stored API key (`skhema auth key use <key>`)
|
|
68
|
+
4. A stored OAuth session (`skhema auth login`)
|
|
69
|
+
|
|
70
|
+
API keys (lanes 1–3) are for **CI and agents** — no browser, no device flow. The
|
|
71
|
+
OAuth session (lane 4) is for **interactive human** use. The organization is
|
|
72
|
+
baked into an API key, so you never pass an org id when using one.
|
|
73
|
+
|
|
74
|
+
`skhema auth status` reports exactly which lane is active and for which org:
|
|
75
|
+
|
|
76
|
+
```sh
|
|
77
|
+
skhema auth status
|
|
78
|
+
# Credential: SKHEMA_API_KEY environment variable
|
|
79
|
+
# Key: sk_live_...a1b2
|
|
80
|
+
# Verified: Yes
|
|
81
|
+
# Permission: read-write
|
|
82
|
+
# Organization: org_…
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
### Managing API keys
|
|
86
|
+
|
|
87
|
+
```sh
|
|
88
|
+
skhema auth login # interactive OAuth (needed to manage keys)
|
|
89
|
+
skhema auth key create --name ci --permission read # prints the key ONCE
|
|
90
|
+
skhema auth key create --name ci --use # …and store it as the CLI credential
|
|
91
|
+
skhema auth key list # masked, with usage + status
|
|
92
|
+
skhema auth key revoke <key-id>
|
|
93
|
+
skhema auth key use <sk_live_…> # store a key you already have
|
|
94
|
+
skhema auth key remove # forget the stored key
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
`create` / `list` / `revoke` call the same server-side `api-key-manage` function
|
|
98
|
+
the web dashboard uses, authenticated with your OAuth session — issuance
|
|
99
|
+
authorization (org owner/admin, plan limits, one-time key display, security
|
|
100
|
+
emails) is enforced there, unchanged. Raw keys are printed only at creation and
|
|
101
|
+
are otherwise always masked.
|
|
102
|
+
|
|
103
|
+
### Headless / CI
|
|
104
|
+
|
|
105
|
+
```sh
|
|
106
|
+
export SKHEMA_API_KEY=sk_live_…
|
|
107
|
+
skhema --json workspace use <workspace-id> # every command now just works
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
## Command conventions
|
|
111
|
+
|
|
112
|
+
Every command supports the global flags `--json`, `--quiet`, `--verbose`, and
|
|
113
|
+
`--api-key <key>`.
|
|
114
|
+
|
|
115
|
+
### `--json` envelope
|
|
116
|
+
|
|
117
|
+
Under `--json`, commands emit one stable envelope on stdout:
|
|
118
|
+
|
|
119
|
+
```jsonc
|
|
120
|
+
// success
|
|
121
|
+
{ "ok": true, "command": "workspace use", "data": { "defaultWorkspaceId": "ws_1" } }
|
|
122
|
+
// failure
|
|
123
|
+
{ "ok": false, "command": "auth key create", "error": { "code": "forbidden", "message": "…", "requestId": "req_…" } }
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
### Exit codes
|
|
127
|
+
|
|
128
|
+
Deterministic, so agents can branch on them:
|
|
129
|
+
|
|
130
|
+
| Code | Meaning |
|
|
131
|
+
| ---- | ------------------------------------------ |
|
|
132
|
+
| `0` | Success |
|
|
133
|
+
| `1` | Generic / API error |
|
|
134
|
+
| `2` | Usage error (bad or missing arguments) |
|
|
135
|
+
| `3` | Auth required (no usable credential) |
|
|
136
|
+
| `4` | Permission or plan denied (HTTP 403 / 402) |
|
|
137
|
+
| `5` | Rate limited (HTTP 429) |
|
|
138
|
+
|
|
139
|
+
### Default workspace
|
|
140
|
+
|
|
141
|
+
```sh
|
|
142
|
+
skhema workspace use <workspace-id> # stored in ~/.skhema/config.json (0600)
|
|
143
|
+
skhema workspace use --clear
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
Later workspace-scoped commands read this default when `--workspace` is omitted.
|
|
147
|
+
|
|
148
|
+
## Configuration & storage
|
|
149
|
+
|
|
150
|
+
| What | Where |
|
|
151
|
+
| ------------------------------- | -------------------------------------------------------------------- |
|
|
152
|
+
| OAuth session | macOS Keychain (`com.skhema.cli`) → `~/.skhema/credentials.json` |
|
|
153
|
+
| Stored API key | macOS Keychain (`com.skhema.cli/api-key`) → `~/.skhema/api-key.json` |
|
|
154
|
+
| Preferences (default workspace) | `~/.skhema/config.json` |
|
|
155
|
+
|
|
156
|
+
Environment overrides for non-production environments: `SKHEMA_API_URL`,
|
|
157
|
+
`SKHEMA_AUTH_URL`. The underlying API client is
|
|
158
|
+
[`@skhema/sdk`](https://www.npmjs.com/package/@skhema/sdk) — use it directly
|
|
159
|
+
when you want Skhema in your own code rather than your terminal.
|
|
160
|
+
|
|
161
|
+
## License
|
|
162
|
+
|
|
163
|
+
MIT
|
package/bin/skhema.js
ADDED
package/dist/cli.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":""}
|
package/dist/cli.js
ADDED
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import { createRequire } from 'module';
|
|
3
|
+
import { registerAgentCommands } from './commands/agent.js';
|
|
4
|
+
import { registerApiCommands } from './commands/api.js';
|
|
5
|
+
import { registerAuthCommands } from './commands/auth.js';
|
|
6
|
+
import { registerComponentCommands } from './commands/component.js';
|
|
7
|
+
import { registerContributeCommands } from './commands/contribute.js';
|
|
8
|
+
import { registerElementCommands } from './commands/element.js';
|
|
9
|
+
import { registerWorkspaceExportCommand } from './commands/export.js';
|
|
10
|
+
import { registerInitCommand } from './commands/init.js';
|
|
11
|
+
import { registerLinkCommands } from './commands/link.js';
|
|
12
|
+
import { registerResourceCommands } from './commands/resource.js';
|
|
13
|
+
import { registerSkillsCommands } from './commands/skills.js';
|
|
14
|
+
import { registerStrategyCommands } from './commands/strategy.js';
|
|
15
|
+
import { registerWebhookCommands } from './commands/webhook.js';
|
|
16
|
+
import { registerWorkspaceCommands } from './commands/workspace.js';
|
|
17
|
+
import { setApiKeyFlag } from './lib/api/credential-context.js';
|
|
18
|
+
import { isApprovedContributor } from './lib/auth/entitlements.js';
|
|
19
|
+
import { getStoredCredentials } from './lib/auth/token-store.js';
|
|
20
|
+
import { showBanner } from './lib/banner.js';
|
|
21
|
+
import { outputError, setGlobalOptions } from './lib/output.js';
|
|
22
|
+
const require = createRequire(import.meta.url);
|
|
23
|
+
const pkg = require('../package.json');
|
|
24
|
+
const program = new Command();
|
|
25
|
+
program
|
|
26
|
+
.name('skhema')
|
|
27
|
+
.description('Skhema CLI — authentication and AI skills management')
|
|
28
|
+
.version(pkg.version)
|
|
29
|
+
.option('--json', 'Output structured JSON')
|
|
30
|
+
.option('--verbose', 'Verbose output')
|
|
31
|
+
.option('--quiet', 'Suppress non-essential output')
|
|
32
|
+
.option('--api-key <key>', 'Use this API key for this invocation (highest credential precedence)')
|
|
33
|
+
.hook('preAction', (thisCommand) => {
|
|
34
|
+
const opts = thisCommand.opts();
|
|
35
|
+
setGlobalOptions({
|
|
36
|
+
json: opts.json ?? false,
|
|
37
|
+
verbose: opts.verbose ?? false,
|
|
38
|
+
quiet: opts.quiet ?? false,
|
|
39
|
+
});
|
|
40
|
+
setApiKeyFlag(opts.apiKey);
|
|
41
|
+
})
|
|
42
|
+
.action(() => {
|
|
43
|
+
// Show banner when run with no subcommand
|
|
44
|
+
showBanner(pkg.version);
|
|
45
|
+
program.help();
|
|
46
|
+
});
|
|
47
|
+
registerInitCommand(program);
|
|
48
|
+
registerAuthCommands(program);
|
|
49
|
+
registerWorkspaceCommands(program);
|
|
50
|
+
registerElementCommands(program);
|
|
51
|
+
registerLinkCommands(program);
|
|
52
|
+
registerWorkspaceExportCommand(program);
|
|
53
|
+
registerComponentCommands(program);
|
|
54
|
+
registerStrategyCommands(program);
|
|
55
|
+
registerResourceCommands(program);
|
|
56
|
+
registerWebhookCommands(program);
|
|
57
|
+
registerAgentCommands(program);
|
|
58
|
+
registerApiCommands(program);
|
|
59
|
+
registerSkillsCommands(program);
|
|
60
|
+
registerContributeCommands(program);
|
|
61
|
+
async function main() {
|
|
62
|
+
// Gate all contribute invocations (including --help) before Commander parses.
|
|
63
|
+
// Commander handles --help internally before preAction fires, so we check early.
|
|
64
|
+
const args = process.argv.slice(2);
|
|
65
|
+
const isContributeInvocation = args[0] === 'contribute';
|
|
66
|
+
if (isContributeInvocation) {
|
|
67
|
+
const approved = await isApprovedContributor().catch(() => false);
|
|
68
|
+
if (!approved) {
|
|
69
|
+
const credentials = await getStoredCredentials();
|
|
70
|
+
if (!credentials?.access_token) {
|
|
71
|
+
outputError('Authentication required. Run "skhema auth login" first.', 'contribute');
|
|
72
|
+
}
|
|
73
|
+
else {
|
|
74
|
+
outputError('Contributor access required. Apply at https://skhema.com/contribute', 'contribute');
|
|
75
|
+
}
|
|
76
|
+
process.exit(1);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
else {
|
|
80
|
+
// Hide contribute from top-level --help for non-contributors
|
|
81
|
+
const contributeCmd = program.commands.find((c) => c.name() === 'contribute');
|
|
82
|
+
if (contributeCmd) {
|
|
83
|
+
const approved = await isApprovedContributor().catch(() => false);
|
|
84
|
+
if (!approved) {
|
|
85
|
+
;
|
|
86
|
+
contributeCmd._hidden = true;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
await program.parseAsync();
|
|
91
|
+
}
|
|
92
|
+
main();
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"agent.d.ts","sourceRoot":"","sources":["../../src/commands/agent.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAA;AAyBnC,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAuK5D"}
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
import { AgentAuthError, createAgentClient } from '@skhema/agent-sdk';
|
|
2
|
+
import { FileCredentialStore } from '@skhema/agent-sdk/node';
|
|
3
|
+
import chalk from 'chalk';
|
|
4
|
+
import ora from 'ora';
|
|
5
|
+
import { AUTH_BASE_URL } from '../lib/config.js';
|
|
6
|
+
import { getGlobalOptions, log, logHeader, outputError, outputSuccess, } from '../lib/output.js';
|
|
7
|
+
/** Resolve the auth API base (the SDK expects the `/api/auth` root). */
|
|
8
|
+
function authApiBase(override) {
|
|
9
|
+
const base = override ?? AUTH_BASE_URL;
|
|
10
|
+
return base.endsWith('/api/auth')
|
|
11
|
+
? base
|
|
12
|
+
: `${base.replace(/\/$/, '')}/api/auth`;
|
|
13
|
+
}
|
|
14
|
+
function errorMessage(error) {
|
|
15
|
+
if (error instanceof AgentAuthError)
|
|
16
|
+
return `${error.message} (${error.code})`;
|
|
17
|
+
if (error instanceof Error)
|
|
18
|
+
return error.message;
|
|
19
|
+
return 'Unknown error';
|
|
20
|
+
}
|
|
21
|
+
export function registerAgentCommands(program) {
|
|
22
|
+
const agent = program
|
|
23
|
+
.command('agent')
|
|
24
|
+
.description('Enroll this runtime as a registered agent and sign requests');
|
|
25
|
+
agent
|
|
26
|
+
.command('enroll')
|
|
27
|
+
.argument('<token>', 'One-time enrollment token from the Skhema dashboard')
|
|
28
|
+
.description('Enroll this runtime as a registered agent (keys are generated locally)')
|
|
29
|
+
.option('--name <name>', 'A label for this agent runtime')
|
|
30
|
+
.option('--mode <mode>', 'Agent mode: delegated | autonomous', 'autonomous')
|
|
31
|
+
.option('--auth-url <url>', 'Override the Skhema auth server base URL')
|
|
32
|
+
.action(async (token, options) => {
|
|
33
|
+
const store = new FileCredentialStore();
|
|
34
|
+
const existing = await store.load();
|
|
35
|
+
if (existing) {
|
|
36
|
+
log(chalk.yellow(`Already enrolled as agent ${existing.agentId}. Run 'skhema agent logout' first.`));
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
const mode = options.mode === 'delegated' ? 'delegated' : 'autonomous';
|
|
40
|
+
const client = createAgentClient({
|
|
41
|
+
baseUrl: authApiBase(options.authUrl),
|
|
42
|
+
store,
|
|
43
|
+
});
|
|
44
|
+
const spinner = ora('Generating keys and enrolling...').start();
|
|
45
|
+
try {
|
|
46
|
+
const cred = await client.enroll(token, { name: options.name, mode });
|
|
47
|
+
spinner.succeed(`Enrolled as agent ${chalk.cyan(cred.agentId)}`);
|
|
48
|
+
if (getGlobalOptions().json) {
|
|
49
|
+
outputSuccess({ agentId: cred.agentId, hostId: cred.hostId, mode: cred.mode }, 'agent enroll');
|
|
50
|
+
}
|
|
51
|
+
else {
|
|
52
|
+
log(` Host: ${chalk.cyan(cred.hostId)}`);
|
|
53
|
+
log(` Mode: ${cred.mode}`);
|
|
54
|
+
log(chalk.dim(' Credential stored at ~/.skhema/agent.json — keep it secret.'));
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
catch (error) {
|
|
58
|
+
spinner.fail('Enrollment failed');
|
|
59
|
+
outputError(errorMessage(error), 'agent enroll');
|
|
60
|
+
process.exit(1);
|
|
61
|
+
}
|
|
62
|
+
});
|
|
63
|
+
agent
|
|
64
|
+
.command('status')
|
|
65
|
+
.description("Show this runtime's agent identity")
|
|
66
|
+
.option('--verify', 'Verify the credential against the server')
|
|
67
|
+
.action(async (options) => {
|
|
68
|
+
const store = new FileCredentialStore();
|
|
69
|
+
const cred = await store.load();
|
|
70
|
+
if (!cred) {
|
|
71
|
+
log(chalk.yellow('Not enrolled. Run `skhema agent enroll <token>`.'));
|
|
72
|
+
if (getGlobalOptions().json) {
|
|
73
|
+
outputSuccess({ enrolled: false }, 'agent status');
|
|
74
|
+
}
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
let capabilities;
|
|
78
|
+
if (options.verify) {
|
|
79
|
+
const client = createAgentClient({ baseUrl: cred.baseUrl, store });
|
|
80
|
+
const spinner = ora('Verifying with server...').start();
|
|
81
|
+
try {
|
|
82
|
+
const session = await client.session();
|
|
83
|
+
spinner.succeed('Verified');
|
|
84
|
+
capabilities = (session.agent.capabilityGrants ?? []).map((g) => g.capability);
|
|
85
|
+
}
|
|
86
|
+
catch (error) {
|
|
87
|
+
spinner.fail('Verification failed');
|
|
88
|
+
outputError(errorMessage(error), 'agent status');
|
|
89
|
+
process.exit(1);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
if (getGlobalOptions().json) {
|
|
93
|
+
outputSuccess({
|
|
94
|
+
enrolled: true,
|
|
95
|
+
agentId: cred.agentId,
|
|
96
|
+
hostId: cred.hostId,
|
|
97
|
+
mode: cred.mode,
|
|
98
|
+
baseUrl: cred.baseUrl,
|
|
99
|
+
createdAt: cred.createdAt,
|
|
100
|
+
verified: options.verify ? true : undefined,
|
|
101
|
+
capabilities,
|
|
102
|
+
}, 'agent status');
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
logHeader('Agent Status');
|
|
106
|
+
log(` Agent: ${chalk.cyan(cred.agentId)}`);
|
|
107
|
+
log(` Host: ${chalk.cyan(cred.hostId)}`);
|
|
108
|
+
log(` Mode: ${cred.mode}`);
|
|
109
|
+
log(` Server: ${cred.baseUrl}`);
|
|
110
|
+
log(` Enrolled: ${new Date(cred.createdAt).toLocaleString()}`);
|
|
111
|
+
if (options.verify) {
|
|
112
|
+
log(` Verified: ${chalk.green('yes')}`);
|
|
113
|
+
if (capabilities && capabilities.length > 0) {
|
|
114
|
+
log(` Capabilities: ${capabilities.join(', ')}`);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
});
|
|
118
|
+
agent
|
|
119
|
+
.command('rotate')
|
|
120
|
+
.description("Rotate this agent's signing key (identity and grants unchanged)")
|
|
121
|
+
.action(async () => {
|
|
122
|
+
const store = new FileCredentialStore();
|
|
123
|
+
const cred = await store.load();
|
|
124
|
+
if (!cred) {
|
|
125
|
+
log(chalk.yellow('Not enrolled. Run `skhema agent enroll <token>`.'));
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
const client = createAgentClient({ baseUrl: cred.baseUrl, store });
|
|
129
|
+
const spinner = ora('Rotating agent key...').start();
|
|
130
|
+
try {
|
|
131
|
+
await client.rotate();
|
|
132
|
+
spinner.succeed('Agent key rotated. The previous key no longer works.');
|
|
133
|
+
if (getGlobalOptions().json) {
|
|
134
|
+
outputSuccess({ rotated: true, agentId: cred.agentId }, 'agent rotate');
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
catch (error) {
|
|
138
|
+
spinner.fail('Rotation failed');
|
|
139
|
+
outputError(errorMessage(error), 'agent rotate');
|
|
140
|
+
process.exit(1);
|
|
141
|
+
}
|
|
142
|
+
});
|
|
143
|
+
agent
|
|
144
|
+
.command('logout')
|
|
145
|
+
.description("Remove this runtime's stored agent credential")
|
|
146
|
+
.action(async () => {
|
|
147
|
+
const store = new FileCredentialStore();
|
|
148
|
+
await store.clear();
|
|
149
|
+
log(chalk.green('Agent credential removed.'));
|
|
150
|
+
if (getGlobalOptions().json) {
|
|
151
|
+
outputSuccess({ loggedOut: true }, 'agent logout');
|
|
152
|
+
}
|
|
153
|
+
});
|
|
154
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
interface OpenApiOperation {
|
|
3
|
+
summary?: string;
|
|
4
|
+
}
|
|
5
|
+
interface OpenApiSpec {
|
|
6
|
+
paths?: Record<string, Record<string, OpenApiOperation>>;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Flatten an OpenAPI document to a sorted `{ method, path, summary }` list.
|
|
10
|
+
* Exported for unit testing against a spec fixture.
|
|
11
|
+
*/
|
|
12
|
+
export declare function renderRouteRows(spec: OpenApiSpec): Array<{
|
|
13
|
+
method: string;
|
|
14
|
+
path: string;
|
|
15
|
+
summary: string;
|
|
16
|
+
}>;
|
|
17
|
+
export declare function registerApiCommands(program: Command): void;
|
|
18
|
+
export {};
|
|
19
|
+
//# sourceMappingURL=api.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"api.d.ts","sourceRoot":"","sources":["../../src/commands/api.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAA;AAuBnC,UAAU,gBAAgB;IACxB,OAAO,CAAC,EAAE,MAAM,CAAA;CACjB;AACD,UAAU,WAAW;IACnB,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,gBAAgB,CAAC,CAAC,CAAA;CACzD;AASD;;;GAGG;AACH,wBAAgB,eAAe,CAC7B,IAAI,EAAE,WAAW,GAChB,KAAK,CAAC;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAC,CAkB1D;AAED,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAkF1D"}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { readFile } from 'fs/promises';
|
|
2
|
+
import { resolveClient } from '../lib/api/client.js';
|
|
3
|
+
import { setApiKeyFlag } from '../lib/api/credential-context.js';
|
|
4
|
+
import { normalizeMethod, normalizePath, parseQueryPairs, resolveDataBody, } from '../lib/api/passthrough.js';
|
|
5
|
+
import { runCommand, UsageError } from '../lib/api/run-command.js';
|
|
6
|
+
import { API_BASE_URL } from '../lib/config.js';
|
|
7
|
+
import { getGlobalOptions, log, logTable } from '../lib/output.js';
|
|
8
|
+
/** Read all of stdin as UTF-8 (used by `--data -`). */
|
|
9
|
+
async function readStdin() {
|
|
10
|
+
const chunks = [];
|
|
11
|
+
for await (const chunk of process.stdin) {
|
|
12
|
+
chunks.push(chunk);
|
|
13
|
+
}
|
|
14
|
+
return Buffer.concat(chunks).toString('utf-8');
|
|
15
|
+
}
|
|
16
|
+
const HTTP_METHODS = new Set(['get', 'post', 'put', 'delete', 'patch']);
|
|
17
|
+
/** Gateway OpenAPI path keys use the regex fragment `([^/]+)` for a param. */
|
|
18
|
+
function prettifyPath(pathKey) {
|
|
19
|
+
return pathKey.replace(/\(\[\^\/\]\+\)/g, '{param}');
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Flatten an OpenAPI document to a sorted `{ method, path, summary }` list.
|
|
23
|
+
* Exported for unit testing against a spec fixture.
|
|
24
|
+
*/
|
|
25
|
+
export function renderRouteRows(spec) {
|
|
26
|
+
const rows = [];
|
|
27
|
+
for (const [pathKey, operations] of Object.entries(spec.paths ?? {})) {
|
|
28
|
+
for (const [method, op] of Object.entries(operations)) {
|
|
29
|
+
if (!HTTP_METHODS.has(method.toLowerCase()))
|
|
30
|
+
continue;
|
|
31
|
+
rows.push({
|
|
32
|
+
method: method.toUpperCase(),
|
|
33
|
+
path: prettifyPath(pathKey),
|
|
34
|
+
summary: op?.summary ?? '',
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
rows.sort((a, b) => a.path === b.path
|
|
39
|
+
? a.method.localeCompare(b.method)
|
|
40
|
+
: a.path.localeCompare(b.path));
|
|
41
|
+
return rows;
|
|
42
|
+
}
|
|
43
|
+
export function registerApiCommands(program) {
|
|
44
|
+
const api = program
|
|
45
|
+
.command('api')
|
|
46
|
+
.description('Raw authenticated access to the Skhema Public API')
|
|
47
|
+
.argument('[method]', 'HTTP method (GET, POST, PUT, DELETE), or `routes` to list the surface')
|
|
48
|
+
.argument('[path]', 'Gateway path, e.g. /workspaces (leading /v1 optional)')
|
|
49
|
+
.option('--data <json|@file|->', 'JSON body: literal, @file, or - for stdin')
|
|
50
|
+
.option('--query <key=value>', 'Query parameter (repeatable)', (value, prev) => [...prev, value], [])
|
|
51
|
+
.option('--api-key <key>', 'API key for this invocation')
|
|
52
|
+
.action(async (method, rawPath, options) => {
|
|
53
|
+
await runCommand(`api ${method ?? ''}`.trim(), async () => {
|
|
54
|
+
if (options.apiKey)
|
|
55
|
+
setApiKeyFlag(options.apiKey);
|
|
56
|
+
if (!method || !rawPath) {
|
|
57
|
+
throw new UsageError('Usage: skhema api <method> <path> (or `skhema api routes`).');
|
|
58
|
+
}
|
|
59
|
+
const httpMethod = normalizeMethod(method);
|
|
60
|
+
const path = normalizePath(rawPath, API_BASE_URL);
|
|
61
|
+
const query = parseQueryPairs(options.query);
|
|
62
|
+
const body = await resolveDataBody(options.data, {
|
|
63
|
+
readFile: (p) => readFile(p, 'utf-8'),
|
|
64
|
+
readStdin,
|
|
65
|
+
});
|
|
66
|
+
const { client } = await resolveClient();
|
|
67
|
+
const data = await client.request(httpMethod, path, {
|
|
68
|
+
body,
|
|
69
|
+
query,
|
|
70
|
+
});
|
|
71
|
+
// Human mode: pretty-print the response body. --json mode: the response
|
|
72
|
+
// is the envelope's `data` (emitted by runCommand).
|
|
73
|
+
if (!getGlobalOptions().json) {
|
|
74
|
+
log(JSON.stringify(data ?? null, null, 2));
|
|
75
|
+
}
|
|
76
|
+
return data;
|
|
77
|
+
});
|
|
78
|
+
});
|
|
79
|
+
api
|
|
80
|
+
.command('routes')
|
|
81
|
+
.description('List every live gateway route from /v1/openapi.json')
|
|
82
|
+
.action(async () => {
|
|
83
|
+
await runCommand('api routes', async () => {
|
|
84
|
+
const { client } = await resolveClient();
|
|
85
|
+
const spec = await client.meta.openapi();
|
|
86
|
+
if (getGlobalOptions().json) {
|
|
87
|
+
// Under --json the raw spec is the payload (the envelope wraps it).
|
|
88
|
+
return spec;
|
|
89
|
+
}
|
|
90
|
+
const rows = renderRouteRows(spec);
|
|
91
|
+
logTable(['Method', 'Path', 'Summary'], rows.map((r) => [r.method, r.path, r.summary]));
|
|
92
|
+
log('');
|
|
93
|
+
log(`${rows.length} routes.`);
|
|
94
|
+
return { count: rows.length };
|
|
95
|
+
});
|
|
96
|
+
});
|
|
97
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../../src/commands/auth.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAA;AAqEnC,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAyT3D"}
|