@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.
Files changed (168) hide show
  1. package/README.md +163 -0
  2. package/bin/skhema.js +2 -0
  3. package/dist/cli.d.ts +2 -0
  4. package/dist/cli.d.ts.map +1 -0
  5. package/dist/cli.js +92 -0
  6. package/dist/commands/agent.d.ts +3 -0
  7. package/dist/commands/agent.d.ts.map +1 -0
  8. package/dist/commands/agent.js +154 -0
  9. package/dist/commands/api.d.ts +19 -0
  10. package/dist/commands/api.d.ts.map +1 -0
  11. package/dist/commands/api.js +97 -0
  12. package/dist/commands/auth.d.ts +3 -0
  13. package/dist/commands/auth.d.ts.map +1 -0
  14. package/dist/commands/auth.js +403 -0
  15. package/dist/commands/component.d.ts +3 -0
  16. package/dist/commands/component.d.ts.map +1 -0
  17. package/dist/commands/component.js +221 -0
  18. package/dist/commands/contribute.d.ts +3 -0
  19. package/dist/commands/contribute.d.ts.map +1 -0
  20. package/dist/commands/contribute.js +318 -0
  21. package/dist/commands/element.d.ts +7 -0
  22. package/dist/commands/element.d.ts.map +1 -0
  23. package/dist/commands/element.js +167 -0
  24. package/dist/commands/export.d.ts +9 -0
  25. package/dist/commands/export.d.ts.map +1 -0
  26. package/dist/commands/export.js +42 -0
  27. package/dist/commands/init.d.ts +49 -0
  28. package/dist/commands/init.d.ts.map +1 -0
  29. package/dist/commands/init.js +275 -0
  30. package/dist/commands/link.d.ts +8 -0
  31. package/dist/commands/link.d.ts.map +1 -0
  32. package/dist/commands/link.js +70 -0
  33. package/dist/commands/resource.d.ts +3 -0
  34. package/dist/commands/resource.d.ts.map +1 -0
  35. package/dist/commands/resource.js +88 -0
  36. package/dist/commands/skills.d.ts +3 -0
  37. package/dist/commands/skills.d.ts.map +1 -0
  38. package/dist/commands/skills.js +113 -0
  39. package/dist/commands/strategy.d.ts +3 -0
  40. package/dist/commands/strategy.d.ts.map +1 -0
  41. package/dist/commands/strategy.js +143 -0
  42. package/dist/commands/webhook.d.ts +3 -0
  43. package/dist/commands/webhook.d.ts.map +1 -0
  44. package/dist/commands/webhook.js +77 -0
  45. package/dist/commands/workspace.d.ts +8 -0
  46. package/dist/commands/workspace.d.ts.map +1 -0
  47. package/dist/commands/workspace.js +219 -0
  48. package/dist/index.d.ts +2 -0
  49. package/dist/index.d.ts.map +1 -0
  50. package/dist/index.js +1 -0
  51. package/dist/lib/api/client.d.ts +27 -0
  52. package/dist/lib/api/client.d.ts.map +1 -0
  53. package/dist/lib/api/client.js +71 -0
  54. package/dist/lib/api/command-helpers.d.ts +27 -0
  55. package/dist/lib/api/command-helpers.d.ts.map +1 -0
  56. package/dist/lib/api/command-helpers.js +77 -0
  57. package/dist/lib/api/credential-context.d.ts +3 -0
  58. package/dist/lib/api/credential-context.d.ts.map +1 -0
  59. package/dist/lib/api/credential-context.js +13 -0
  60. package/dist/lib/api/export-cli.d.ts +14 -0
  61. package/dist/lib/api/export-cli.d.ts.map +1 -0
  62. package/dist/lib/api/export-cli.js +26 -0
  63. package/dist/lib/api/formats.d.ts +12 -0
  64. package/dist/lib/api/formats.d.ts.map +1 -0
  65. package/dist/lib/api/formats.js +18 -0
  66. package/dist/lib/api/passthrough.d.ts +34 -0
  67. package/dist/lib/api/passthrough.d.ts.map +1 -0
  68. package/dist/lib/api/passthrough.js +106 -0
  69. package/dist/lib/api/payload.d.ts +16 -0
  70. package/dist/lib/api/payload.d.ts.map +1 -0
  71. package/dist/lib/api/payload.js +53 -0
  72. package/dist/lib/api/run-command.d.ts +69 -0
  73. package/dist/lib/api/run-command.d.ts.map +1 -0
  74. package/dist/lib/api/run-command.js +127 -0
  75. package/dist/lib/api/scope.d.ts +8 -0
  76. package/dist/lib/api/scope.d.ts.map +1 -0
  77. package/dist/lib/api/scope.js +16 -0
  78. package/dist/lib/api/transfer.d.ts +57 -0
  79. package/dist/lib/api/transfer.d.ts.map +1 -0
  80. package/dist/lib/api/transfer.js +153 -0
  81. package/dist/lib/auth/api-key-admin.d.ts +39 -0
  82. package/dist/lib/auth/api-key-admin.d.ts.map +1 -0
  83. package/dist/lib/auth/api-key-admin.js +66 -0
  84. package/dist/lib/auth/api-key-store.d.ts +8 -0
  85. package/dist/lib/auth/api-key-store.d.ts.map +1 -0
  86. package/dist/lib/auth/api-key-store.js +109 -0
  87. package/dist/lib/auth/device-flow.d.ts +4 -0
  88. package/dist/lib/auth/device-flow.d.ts.map +1 -0
  89. package/dist/lib/auth/device-flow.js +109 -0
  90. package/dist/lib/auth/entitlements.d.ts +16 -0
  91. package/dist/lib/auth/entitlements.d.ts.map +1 -0
  92. package/dist/lib/auth/entitlements.js +89 -0
  93. package/dist/lib/auth/localhost-flow.d.ts +4 -0
  94. package/dist/lib/auth/localhost-flow.d.ts.map +1 -0
  95. package/dist/lib/auth/localhost-flow.js +134 -0
  96. package/dist/lib/auth/oauth-client.d.ts +33 -0
  97. package/dist/lib/auth/oauth-client.d.ts.map +1 -0
  98. package/dist/lib/auth/oauth-client.js +96 -0
  99. package/dist/lib/auth/token-store.d.ts +6 -0
  100. package/dist/lib/auth/token-store.d.ts.map +1 -0
  101. package/dist/lib/auth/token-store.js +136 -0
  102. package/dist/lib/auth/types.d.ts +49 -0
  103. package/dist/lib/auth/types.d.ts.map +1 -0
  104. package/dist/lib/auth/types.js +1 -0
  105. package/dist/lib/banner.d.ts +2 -0
  106. package/dist/lib/banner.d.ts.map +1 -0
  107. package/dist/lib/banner.js +82 -0
  108. package/dist/lib/config-store.d.ts +23 -0
  109. package/dist/lib/config-store.d.ts.map +1 -0
  110. package/dist/lib/config-store.js +53 -0
  111. package/dist/lib/config.d.ts +20 -0
  112. package/dist/lib/config.d.ts.map +1 -0
  113. package/dist/lib/config.js +33 -0
  114. package/dist/lib/contribute/author-resolver.d.ts +13 -0
  115. package/dist/lib/contribute/author-resolver.d.ts.map +1 -0
  116. package/dist/lib/contribute/author-resolver.js +63 -0
  117. package/dist/lib/contribute/embed-generator.d.ts +4 -0
  118. package/dist/lib/contribute/embed-generator.d.ts.map +1 -0
  119. package/dist/lib/contribute/embed-generator.js +108 -0
  120. package/dist/lib/contribute/link-generator.d.ts +11 -0
  121. package/dist/lib/contribute/link-generator.d.ts.map +1 -0
  122. package/dist/lib/contribute/link-generator.js +119 -0
  123. package/dist/lib/contribute/method-schema.d.ts +239 -0
  124. package/dist/lib/contribute/method-schema.d.ts.map +1 -0
  125. package/dist/lib/contribute/method-schema.js +172 -0
  126. package/dist/lib/contribute/profile-resolver.d.ts +2 -0
  127. package/dist/lib/contribute/profile-resolver.d.ts.map +1 -0
  128. package/dist/lib/contribute/profile-resolver.js +25 -0
  129. package/dist/lib/contribute/types.d.ts +91 -0
  130. package/dist/lib/contribute/types.d.ts.map +1 -0
  131. package/dist/lib/contribute/types.js +1 -0
  132. package/dist/lib/mcp/registrar.d.ts +26 -0
  133. package/dist/lib/mcp/registrar.d.ts.map +1 -0
  134. package/dist/lib/mcp/registrar.js +69 -0
  135. package/dist/lib/mcp/registry.d.ts +54 -0
  136. package/dist/lib/mcp/registry.d.ts.map +1 -0
  137. package/dist/lib/mcp/registry.js +105 -0
  138. package/dist/lib/open.d.ts +2 -0
  139. package/dist/lib/open.d.ts.map +1 -0
  140. package/dist/lib/open.js +24 -0
  141. package/dist/lib/output.d.ts +25 -0
  142. package/dist/lib/output.d.ts.map +1 -0
  143. package/dist/lib/output.js +74 -0
  144. package/dist/lib/skills/installer.d.ts +4 -0
  145. package/dist/lib/skills/installer.d.ts.map +1 -0
  146. package/dist/lib/skills/installer.js +100 -0
  147. package/dist/lib/skills/lock.d.ts +4 -0
  148. package/dist/lib/skills/lock.d.ts.map +1 -0
  149. package/dist/lib/skills/lock.js +28 -0
  150. package/dist/lib/skills/platform-detect.d.ts +4 -0
  151. package/dist/lib/skills/platform-detect.d.ts.map +1 -0
  152. package/dist/lib/skills/platform-detect.js +89 -0
  153. package/dist/lib/skills/types.d.ts +29 -0
  154. package/dist/lib/skills/types.d.ts.map +1 -0
  155. package/dist/lib/skills/types.js +1 -0
  156. package/dist/postinstall.d.ts +2 -0
  157. package/dist/postinstall.d.ts.map +1 -0
  158. package/dist/postinstall.js +11 -0
  159. package/package.json +67 -0
  160. package/skills/.manifest.json +15 -0
  161. package/skills/skhema-challenge-framing/SKILL.md +28 -0
  162. package/skills/skhema-coherence-check/SKILL.md +28 -0
  163. package/skills/skhema-element-decomposition/SKILL.md +28 -0
  164. package/skills/skhema-element-writer/SKILL.md +20 -0
  165. package/skills/skhema-judgment-audit/SKILL.md +28 -0
  166. package/skills/skhema-semantic-sharpening/SKILL.md +28 -0
  167. package/skills/skhema-strategy-advisor/SKILL.md +20 -0
  168. 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
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ import '../dist/cli.js'
package/dist/cli.d.ts ADDED
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=cli.d.ts.map
@@ -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,3 @@
1
+ import { Command } from 'commander';
2
+ export declare function registerAgentCommands(program: Command): void;
3
+ //# sourceMappingURL=agent.d.ts.map
@@ -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,3 @@
1
+ import { Command } from 'commander';
2
+ export declare function registerAuthCommands(program: Command): void;
3
+ //# sourceMappingURL=auth.d.ts.map
@@ -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"}