@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
@@ -0,0 +1,57 @@
1
+ import type { SkhemaClient } from '@skhema/sdk';
2
+ /** Best-effort content type from a filename extension. */
3
+ export declare function guessContentType(fileName: string): string;
4
+ /**
5
+ * Upload a local file as a resource on an element: sign an upload URL, stream
6
+ * the bytes to it, then confirm. A failure at any step throws (the confirm step
7
+ * surfaces the gateway `requestId`); the command layer therefore never emits a
8
+ * partial-success envelope.
9
+ */
10
+ export declare function uploadResource(opts: {
11
+ client: SkhemaClient;
12
+ workspaceId: string;
13
+ elementId: string;
14
+ filePath: string;
15
+ attribution?: string;
16
+ fetchImpl?: typeof fetch;
17
+ }): Promise<unknown>;
18
+ /**
19
+ * Resolve a resource's signed download URL and stream it to `out` (a path, `-`
20
+ * for stdout, or — when omitted — the original filename in the cwd). External
21
+ * link resources (no stored file) are reported as a usage error.
22
+ */
23
+ export declare function downloadResource(opts: {
24
+ client: SkhemaClient;
25
+ workspaceId: string;
26
+ resourceId: string;
27
+ out?: string;
28
+ fetchImpl?: typeof fetch;
29
+ }): Promise<{
30
+ path: string;
31
+ bytes: number;
32
+ }>;
33
+ /** Fetch `href` and stream it to a path or stdout. Returns bytes written. */
34
+ export declare function streamUrlTo(doFetch: typeof fetch, href: string, target: string): Promise<{
35
+ path: string;
36
+ bytes: number;
37
+ }>;
38
+ export interface ExportResult {
39
+ delivered: 'download' | 'email';
40
+ documentId: string;
41
+ format: string;
42
+ filename?: string;
43
+ path?: string;
44
+ recipient?: string;
45
+ }
46
+ /**
47
+ * Run an export: `exec` performs the SDK call that returns the export job
48
+ * payload; for a download delivery we fetch the signed URL and stream it to
49
+ * `out` (default: the server-supplied filename in the cwd); for an email
50
+ * delivery we just report it. `out === '-'` streams to stdout.
51
+ */
52
+ export declare function performExport(opts: {
53
+ exec: () => Promise<unknown>;
54
+ out?: string;
55
+ fetchImpl?: typeof fetch;
56
+ }): Promise<ExportResult>;
57
+ //# sourceMappingURL=transfer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"transfer.d.ts","sourceRoot":"","sources":["../../../src/lib/api/transfer.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAA;AAkC/C,0DAA0D;AAC1D,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAGzD;AASD;;;;;GAKG;AACH,wBAAsB,cAAc,CAAC,IAAI,EAAE;IACzC,MAAM,EAAE,YAAY,CAAA;IACpB,WAAW,EAAE,MAAM,CAAA;IACnB,SAAS,EAAE,MAAM,CAAA;IACjB,QAAQ,EAAE,MAAM,CAAA;IAChB,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,SAAS,CAAC,EAAE,OAAO,KAAK,CAAA;CACzB,GAAG,OAAO,CAAC,OAAO,CAAC,CA6CnB;AAkBD;;;;GAIG;AACH,wBAAsB,gBAAgB,CAAC,IAAI,EAAE;IAC3C,MAAM,EAAE,YAAY,CAAA;IACpB,WAAW,EAAE,MAAM,CAAA;IACnB,UAAU,EAAE,MAAM,CAAA;IAClB,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,SAAS,CAAC,EAAE,OAAO,KAAK,CAAA;CACzB,GAAG,OAAO,CAAC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC,CAyB3C;AAED,6EAA6E;AAC7E,wBAAsB,WAAW,CAC/B,OAAO,EAAE,OAAO,KAAK,EACrB,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,MAAM,GACb,OAAO,CAAC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC,CAkB1C;AAqBD,MAAM,WAAW,YAAY;IAC3B,SAAS,EAAE,UAAU,GAAG,OAAO,CAAA;IAC/B,UAAU,EAAE,MAAM,CAAA;IAClB,MAAM,EAAE,MAAM,CAAA;IACd,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB;AAED;;;;;GAKG;AACH,wBAAsB,aAAa,CAAC,IAAI,EAAE;IACxC,IAAI,EAAE,MAAM,OAAO,CAAC,OAAO,CAAC,CAAA;IAC5B,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,SAAS,CAAC,EAAE,OAAO,KAAK,CAAA;CACzB,GAAG,OAAO,CAAC,YAAY,CAAC,CA2BxB"}
@@ -0,0 +1,153 @@
1
+ import fs from 'fs';
2
+ import path from 'path';
3
+ import { Readable } from 'stream';
4
+ import { pipeline } from 'stream/promises';
5
+ import { UsageError } from './run-command.js';
6
+ /**
7
+ * File transfer + export orchestration. These helpers hold the multi-step
8
+ * client flows (upload = sign → PUT → confirm; export = generate → fetch signed
9
+ * URL → write) so they are unit-testable against a mocked SDK client and a
10
+ * mocked `fetch`, independently of the Commander wiring. Bytes are streamed, not
11
+ * buffered, so large files do not sit in memory.
12
+ */
13
+ const CONTENT_TYPES = {
14
+ pdf: 'application/pdf',
15
+ png: 'image/png',
16
+ jpg: 'image/jpeg',
17
+ jpeg: 'image/jpeg',
18
+ gif: 'image/gif',
19
+ svg: 'image/svg+xml',
20
+ txt: 'text/plain',
21
+ md: 'text/markdown',
22
+ csv: 'text/csv',
23
+ json: 'application/json',
24
+ html: 'text/html',
25
+ doc: 'application/msword',
26
+ docx: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
27
+ xls: 'application/vnd.ms-excel',
28
+ xlsx: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
29
+ zip: 'application/zip',
30
+ };
31
+ /** Best-effort content type from a filename extension. */
32
+ export function guessContentType(fileName) {
33
+ const ext = path.extname(fileName).slice(1).toLowerCase();
34
+ return CONTENT_TYPES[ext] ?? 'application/octet-stream';
35
+ }
36
+ /**
37
+ * Upload a local file as a resource on an element: sign an upload URL, stream
38
+ * the bytes to it, then confirm. A failure at any step throws (the confirm step
39
+ * surfaces the gateway `requestId`); the command layer therefore never emits a
40
+ * partial-success envelope.
41
+ */
42
+ export async function uploadResource(opts) {
43
+ const doFetch = opts.fetchImpl ?? globalThis.fetch;
44
+ let size;
45
+ try {
46
+ size = fs.statSync(opts.filePath).size;
47
+ }
48
+ catch {
49
+ throw new UsageError(`Cannot read file: ${opts.filePath}`);
50
+ }
51
+ const fileName = path.basename(opts.filePath);
52
+ const contentType = guessContentType(fileName);
53
+ const signed = await opts.client.elements.resources.createUploadUrl(opts.workspaceId, opts.elementId, { fileName, contentType });
54
+ const putResponse = await doFetch(signed.signedUrl, {
55
+ method: 'PUT',
56
+ headers: {
57
+ 'Content-Type': contentType,
58
+ 'Content-Length': String(size),
59
+ },
60
+ body: Readable.toWeb(fs.createReadStream(opts.filePath)),
61
+ // Node/undici require duplex when streaming a request body.
62
+ duplex: 'half',
63
+ });
64
+ if (!putResponse.ok) {
65
+ throw new Error(`Uploading file bytes failed (HTTP ${putResponse.status}). The resource was not created.`);
66
+ }
67
+ return opts.client.elements.resources.confirm(opts.workspaceId, opts.elementId, {
68
+ path: signed.path,
69
+ ...(opts.attribution ? { attribution: opts.attribution } : {}),
70
+ });
71
+ }
72
+ /** Filename embedded in a resource storage path: `…/<ts>_<original-name>`. */
73
+ function fileNameFromUrl(href, fallback) {
74
+ try {
75
+ const base = path.basename(new URL(href).pathname);
76
+ const decoded = decodeURIComponent(base);
77
+ return decoded.replace(/^\d+_/, '') || fallback;
78
+ }
79
+ catch {
80
+ return fallback;
81
+ }
82
+ }
83
+ /**
84
+ * Resolve a resource's signed download URL and stream it to `out` (a path, `-`
85
+ * for stdout, or — when omitted — the original filename in the cwd). External
86
+ * link resources (no stored file) are reported as a usage error.
87
+ */
88
+ export async function downloadResource(opts) {
89
+ const doFetch = opts.fetchImpl ?? globalThis.fetch;
90
+ const resolved = await opts.client.elements.resources.getDownloadUrl(opts.workspaceId, opts.resourceId);
91
+ const href = resolved.signedUrl ?? resolved.url;
92
+ if (!href) {
93
+ throw new UsageError('Resource has no downloadable file or URL.');
94
+ }
95
+ if (!resolved.signedUrl && resolved.url) {
96
+ throw new UsageError(`Resource is an external link, not a stored file: ${resolved.url}`);
97
+ }
98
+ const target = opts.out && opts.out !== '-'
99
+ ? opts.out
100
+ : opts.out === '-'
101
+ ? '-'
102
+ : fileNameFromUrl(href, `${opts.resourceId}.bin`);
103
+ return streamUrlTo(doFetch, href, target);
104
+ }
105
+ /** Fetch `href` and stream it to a path or stdout. Returns bytes written. */
106
+ export async function streamUrlTo(doFetch, href, target) {
107
+ const response = await doFetch(href);
108
+ if (!response.ok || !response.body) {
109
+ throw new Error(`Download failed (HTTP ${response.status}).`);
110
+ }
111
+ const source = Readable.fromWeb(response.body);
112
+ let bytes = 0;
113
+ source.on('data', (chunk) => {
114
+ bytes += chunk.length;
115
+ });
116
+ if (target === '-') {
117
+ await pipeline(source, process.stdout);
118
+ return { path: '(stdout)', bytes };
119
+ }
120
+ await pipeline(source, fs.createWriteStream(target));
121
+ return { path: path.resolve(target), bytes };
122
+ }
123
+ /**
124
+ * Run an export: `exec` performs the SDK call that returns the export job
125
+ * payload; for a download delivery we fetch the signed URL and stream it to
126
+ * `out` (default: the server-supplied filename in the cwd); for an email
127
+ * delivery we just report it. `out === '-'` streams to stdout.
128
+ */
129
+ export async function performExport(opts) {
130
+ const doFetch = opts.fetchImpl ?? globalThis.fetch;
131
+ const res = (await opts.exec());
132
+ if (res.delivered === 'email') {
133
+ return {
134
+ delivered: 'email',
135
+ documentId: res.documentId,
136
+ format: res.format,
137
+ recipient: res.recipient,
138
+ };
139
+ }
140
+ const target = opts.out && opts.out !== '-'
141
+ ? opts.out
142
+ : opts.out === '-'
143
+ ? '-'
144
+ : res.filename;
145
+ const written = await streamUrlTo(doFetch, res.url, target);
146
+ return {
147
+ delivered: 'download',
148
+ documentId: res.documentId,
149
+ format: res.format,
150
+ filename: res.filename,
151
+ path: written.path,
152
+ };
153
+ }
@@ -0,0 +1,39 @@
1
+ export type ApiKeyPermission = 'read' | 'write' | 'read-write';
2
+ export type ApiKeyScope = 'org' | 'user';
3
+ export interface ApiKeySummary {
4
+ id: string;
5
+ keyPrefix: string;
6
+ name: string;
7
+ organizationId: string;
8
+ userId: string | null;
9
+ permission: ApiKeyPermission;
10
+ scope: ApiKeyScope;
11
+ createdBy: string;
12
+ createdAt: string;
13
+ lastUsedAt: string | null;
14
+ revokedAt: string | null;
15
+ }
16
+ export interface CreatedApiKey extends ApiKeySummary {
17
+ /** The raw key — returned once, never retrievable again. */
18
+ rawKey: string;
19
+ warning?: string;
20
+ }
21
+ export interface ListApiKeysResult {
22
+ keys: ApiKeySummary[];
23
+ limits: {
24
+ used: number;
25
+ max: number;
26
+ };
27
+ }
28
+ export declare function createApiKey(accessToken: string, input: {
29
+ organizationId: string;
30
+ name: string;
31
+ permission: ApiKeyPermission;
32
+ scope: ApiKeyScope;
33
+ }): Promise<CreatedApiKey>;
34
+ export declare function listApiKeys(accessToken: string, organizationId: string): Promise<ListApiKeysResult>;
35
+ export declare function revokeApiKey(accessToken: string, keyId: string): Promise<{
36
+ revoked: boolean;
37
+ keyId: string;
38
+ }>;
39
+ //# sourceMappingURL=api-key-admin.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"api-key-admin.d.ts","sourceRoot":"","sources":["../../../src/lib/auth/api-key-admin.ts"],"names":[],"mappings":"AAaA,MAAM,MAAM,gBAAgB,GAAG,MAAM,GAAG,OAAO,GAAG,YAAY,CAAA;AAC9D,MAAM,MAAM,WAAW,GAAG,KAAK,GAAG,MAAM,CAAA;AAExC,MAAM,WAAW,aAAa;IAC5B,EAAE,EAAE,MAAM,CAAA;IACV,SAAS,EAAE,MAAM,CAAA;IACjB,IAAI,EAAE,MAAM,CAAA;IACZ,cAAc,EAAE,MAAM,CAAA;IACtB,MAAM,EAAE,MAAM,GAAG,IAAI,CAAA;IACrB,UAAU,EAAE,gBAAgB,CAAA;IAC5B,KAAK,EAAE,WAAW,CAAA;IAClB,SAAS,EAAE,MAAM,CAAA;IACjB,SAAS,EAAE,MAAM,CAAA;IACjB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAA;IACzB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAA;CACzB;AAED,MAAM,WAAW,aAAc,SAAQ,aAAa;IAClD,4DAA4D;IAC5D,MAAM,EAAE,MAAM,CAAA;IACd,OAAO,CAAC,EAAE,MAAM,CAAA;CACjB;AAED,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE,aAAa,EAAE,CAAA;IACrB,MAAM,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAA;KAAE,CAAA;CACtC;AAsDD,wBAAsB,YAAY,CAChC,WAAW,EAAE,MAAM,EACnB,KAAK,EAAE;IACL,cAAc,EAAE,MAAM,CAAA;IACtB,IAAI,EAAE,MAAM,CAAA;IACZ,UAAU,EAAE,gBAAgB,CAAA;IAC5B,KAAK,EAAE,WAAW,CAAA;CACnB,GACA,OAAO,CAAC,aAAa,CAAC,CAKxB;AAED,wBAAsB,WAAW,CAC/B,WAAW,EAAE,MAAM,EACnB,cAAc,EAAE,MAAM,GACrB,OAAO,CAAC,iBAAiB,CAAC,CAK5B;AAED,wBAAsB,YAAY,CAChC,WAAW,EAAE,MAAM,EACnB,KAAK,EAAE,MAAM,GACZ,OAAO,CAAC;IAAE,OAAO,EAAE,OAAO,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC,CAK9C"}
@@ -0,0 +1,66 @@
1
+ import { SkhemaApiError } from '@skhema/sdk';
2
+ import { SUPABASE_PUBLISHABLE_KEY, SUPABASE_URL } from '../config.js';
3
+ /**
4
+ * Client for the `api-key-manage` edge function — the same function the web
5
+ * dashboard uses to create/list/revoke API keys. It authenticates with the
6
+ * caller's OAuth bearer (`verifyAuth` + `requireUser`), so issuance authorization
7
+ * (org owner/admin, plan limits, one-time key display, security emails) is
8
+ * enforced server-side and unchanged. This is NOT the public gateway.
9
+ */
10
+ const API_KEY_MANAGE_URL = `${SUPABASE_URL}/functions/v1/api-key-manage`;
11
+ async function callApiKeyManage(accessToken, body) {
12
+ const response = await fetch(API_KEY_MANAGE_URL, {
13
+ method: 'POST',
14
+ headers: {
15
+ 'Content-Type': 'application/json',
16
+ Authorization: `Bearer ${accessToken}`,
17
+ apikey: SUPABASE_PUBLISHABLE_KEY,
18
+ },
19
+ body: JSON.stringify(body),
20
+ });
21
+ const requestId = response.headers.get('x-request-id') ?? undefined;
22
+ const text = await response.text();
23
+ const parsed = text ? JSON.parse(text) : {};
24
+ if (!response.ok) {
25
+ // api-key-manage returns `{ error: "message" }`; normalize to the SDK's
26
+ // error taxonomy so runCommand maps 402/403 to the right exit codes.
27
+ const message = parsed.error ??
28
+ `api-key-manage failed with status ${response.status}`;
29
+ throw new SkhemaApiError(response.status, statusToCode(response.status), message, requestId);
30
+ }
31
+ return parsed;
32
+ }
33
+ function statusToCode(status) {
34
+ switch (status) {
35
+ case 400:
36
+ return 'bad_request';
37
+ case 401:
38
+ return 'unauthorized';
39
+ case 402:
40
+ return 'payment_required';
41
+ case 403:
42
+ return 'forbidden';
43
+ case 404:
44
+ return 'not_found';
45
+ default:
46
+ return 'error';
47
+ }
48
+ }
49
+ export async function createApiKey(accessToken, input) {
50
+ return (await callApiKeyManage(accessToken, {
51
+ action: 'create',
52
+ ...input,
53
+ }));
54
+ }
55
+ export async function listApiKeys(accessToken, organizationId) {
56
+ return (await callApiKeyManage(accessToken, {
57
+ action: 'list',
58
+ organizationId,
59
+ }));
60
+ }
61
+ export async function revokeApiKey(accessToken, keyId) {
62
+ return (await callApiKeyManage(accessToken, {
63
+ action: 'revoke',
64
+ keyId,
65
+ }));
66
+ }
@@ -0,0 +1,8 @@
1
+ /** Persist an API key (keychain preferred, file fallback). */
2
+ export declare function storeApiKey(rawKey: string): void;
3
+ /** Return the stored API key, or null when none is stored. */
4
+ export declare function getStoredApiKey(): string | null;
5
+ /** Remove the stored API key from both keychain and file. */
6
+ export declare function clearApiKey(): void;
7
+ export declare function getApiKeyStorageLocation(): string;
8
+ //# sourceMappingURL=api-key-store.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"api-key-store.d.ts","sourceRoot":"","sources":["../../../src/lib/auth/api-key-store.ts"],"names":[],"mappings":"AA4FA,8DAA8D;AAC9D,wBAAgB,WAAW,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAKhD;AAED,8DAA8D;AAC9D,wBAAgB,eAAe,IAAI,MAAM,GAAG,IAAI,CAS/C;AAED,6DAA6D;AAC7D,wBAAgB,WAAW,IAAI,IAAI,CAGlC;AAED,wBAAgB,wBAAwB,IAAI,MAAM,CAMjD"}
@@ -0,0 +1,109 @@
1
+ import { execSync } from 'child_process';
2
+ import fs from 'fs';
3
+ import os from 'os';
4
+ import path from 'path';
5
+ import { API_KEY_FILE, CREDENTIALS_DIR, KEYCHAIN_ACCOUNT_API_KEY, KEYCHAIN_SERVICE, } from '../config.js';
6
+ /**
7
+ * Storage for the `sk_live_…` API key credential lane. Mirrors the OAuth
8
+ * token-store pattern: macOS Keychain first (under a distinct account so it
9
+ * never collides with the OAuth session), plain-file fallback at
10
+ * `~/.skhema/api-key.json` (mode 0600) elsewhere. The raw key is never logged.
11
+ */
12
+ function getApiKeyFilePath() {
13
+ return path.join(os.homedir(), CREDENTIALS_DIR, API_KEY_FILE);
14
+ }
15
+ function tryKeychainStore(data) {
16
+ if (process.platform !== 'darwin')
17
+ return false;
18
+ try {
19
+ try {
20
+ execSync(`security delete-generic-password -s "${KEYCHAIN_SERVICE}" -a "${KEYCHAIN_ACCOUNT_API_KEY}" 2>/dev/null`, { stdio: 'ignore' });
21
+ }
22
+ catch {
23
+ /* ignore if it doesn't exist */
24
+ }
25
+ execSync(`security add-generic-password -s "${KEYCHAIN_SERVICE}" -a "${KEYCHAIN_ACCOUNT_API_KEY}" -w "${data.replace(/"/g, '\\"')}"`, { stdio: 'ignore' });
26
+ return true;
27
+ }
28
+ catch {
29
+ return false;
30
+ }
31
+ }
32
+ function tryKeychainLoad() {
33
+ if (process.platform !== 'darwin')
34
+ return null;
35
+ try {
36
+ const result = execSync(`security find-generic-password -s "${KEYCHAIN_SERVICE}" -a "${KEYCHAIN_ACCOUNT_API_KEY}" -w 2>/dev/null`, { encoding: 'utf-8' });
37
+ return result.trim();
38
+ }
39
+ catch {
40
+ return null;
41
+ }
42
+ }
43
+ function tryKeychainDelete() {
44
+ if (process.platform !== 'darwin')
45
+ return false;
46
+ try {
47
+ execSync(`security delete-generic-password -s "${KEYCHAIN_SERVICE}" -a "${KEYCHAIN_ACCOUNT_API_KEY}" 2>/dev/null`, { stdio: 'ignore' });
48
+ return true;
49
+ }
50
+ catch {
51
+ return false;
52
+ }
53
+ }
54
+ function storeToFile(data) {
55
+ const dir = path.join(os.homedir(), CREDENTIALS_DIR);
56
+ fs.mkdirSync(dir, { recursive: true, mode: 0o700 });
57
+ fs.writeFileSync(getApiKeyFilePath(), data, { mode: 0o600 });
58
+ }
59
+ function loadFromFile() {
60
+ try {
61
+ return fs.readFileSync(getApiKeyFilePath(), 'utf-8');
62
+ }
63
+ catch {
64
+ return null;
65
+ }
66
+ }
67
+ function deleteFile() {
68
+ try {
69
+ const filePath = getApiKeyFilePath();
70
+ if (fs.existsSync(filePath))
71
+ fs.unlinkSync(filePath);
72
+ }
73
+ catch {
74
+ /* ignore */
75
+ }
76
+ }
77
+ /** Persist an API key (keychain preferred, file fallback). */
78
+ export function storeApiKey(rawKey) {
79
+ const data = JSON.stringify({ apiKey: rawKey });
80
+ if (!tryKeychainStore(data)) {
81
+ storeToFile(data);
82
+ }
83
+ }
84
+ /** Return the stored API key, or null when none is stored. */
85
+ export function getStoredApiKey() {
86
+ const data = tryKeychainLoad() ?? loadFromFile();
87
+ if (!data)
88
+ return null;
89
+ try {
90
+ const parsed = JSON.parse(data);
91
+ return parsed.apiKey ?? null;
92
+ }
93
+ catch {
94
+ return null;
95
+ }
96
+ }
97
+ /** Remove the stored API key from both keychain and file. */
98
+ export function clearApiKey() {
99
+ tryKeychainDelete();
100
+ deleteFile();
101
+ }
102
+ export function getApiKeyStorageLocation() {
103
+ if (process.platform === 'darwin' && tryKeychainLoad()) {
104
+ return `macOS Keychain (${KEYCHAIN_SERVICE}/${KEYCHAIN_ACCOUNT_API_KEY})`;
105
+ }
106
+ if (fs.existsSync(getApiKeyFilePath()))
107
+ return getApiKeyFilePath();
108
+ return 'Not stored';
109
+ }
@@ -0,0 +1,4 @@
1
+ export declare function startDeviceFlow(options?: {
2
+ organizationId?: string;
3
+ }): Promise<void>;
4
+ //# sourceMappingURL=device-flow.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"device-flow.d.ts","sourceRoot":"","sources":["../../../src/lib/auth/device-flow.ts"],"names":[],"mappings":"AA4FA,wBAAsB,eAAe,CAAC,OAAO,CAAC,EAAE;IAC9C,cAAc,CAAC,EAAE,MAAM,CAAA;CACxB,GAAG,OAAO,CAAC,IAAI,CAAC,CA8DhB"}
@@ -0,0 +1,109 @@
1
+ import chalk from 'chalk';
2
+ import ora from 'ora';
3
+ import { AUTH_BASE_URL, CLI_CLIENT_ID, OAUTH_SCOPES } from '../config.js';
4
+ import { log } from '../output.js';
5
+ import { fetchUserInfo } from './oauth-client.js';
6
+ import { storeCredentials } from './token-store.js';
7
+ async function requestDeviceCode(options) {
8
+ const body = {
9
+ client_id: CLI_CLIENT_ID,
10
+ scope: OAUTH_SCOPES,
11
+ };
12
+ if (options?.organizationId) {
13
+ body.organization_id = options.organizationId;
14
+ }
15
+ const response = await fetch(`${AUTH_BASE_URL}/api/auth/oauth/device/authorize`, {
16
+ method: 'POST',
17
+ headers: { 'Content-Type': 'application/json' },
18
+ body: JSON.stringify(body),
19
+ });
20
+ if (!response.ok) {
21
+ const error = await response.json().catch(() => ({}));
22
+ throw new Error(`Failed to request device code: ${error.error || response.statusText}`);
23
+ }
24
+ return response.json();
25
+ }
26
+ async function pollForToken(deviceCode, interval, expiresIn) {
27
+ const deadline = Date.now() + expiresIn * 1000;
28
+ let pollInterval = interval * 1000;
29
+ while (Date.now() < deadline) {
30
+ await new Promise((resolve) => setTimeout(resolve, pollInterval));
31
+ const response = await fetch(`${AUTH_BASE_URL}/api/auth/oauth/token`, {
32
+ method: 'POST',
33
+ headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
34
+ body: new URLSearchParams({
35
+ grant_type: 'urn:ietf:params:oauth:grant-type:device_code',
36
+ client_id: CLI_CLIENT_ID,
37
+ device_code: deviceCode,
38
+ }),
39
+ });
40
+ if (response.ok) {
41
+ return response.json();
42
+ }
43
+ const error = (await response
44
+ .json()
45
+ .catch(() => ({ error: 'unknown' })));
46
+ switch (error.error) {
47
+ case 'authorization_pending':
48
+ // Continue polling
49
+ break;
50
+ case 'slow_down':
51
+ pollInterval += 5000;
52
+ break;
53
+ case 'expired_token':
54
+ throw new Error('Device code expired. Please try again.');
55
+ case 'access_denied':
56
+ throw new Error('Authorization was denied.');
57
+ default:
58
+ throw new Error(`Unexpected error: ${error.error}`);
59
+ }
60
+ }
61
+ throw new Error('Device code expired. Please try again.');
62
+ }
63
+ export async function startDeviceFlow(options) {
64
+ const spinner = ora('Requesting device code...').start();
65
+ const deviceCode = await requestDeviceCode(options);
66
+ spinner.stop();
67
+ // Display the user code prominently
68
+ log('');
69
+ log(chalk.bold(' To authenticate, visit:'));
70
+ log(` ${chalk.cyan(deviceCode.verification_uri)}`);
71
+ log('');
72
+ log(chalk.bold(' And enter the code:'));
73
+ log(` ${chalk.bold.yellow(deviceCode.user_code)}`);
74
+ log('');
75
+ log(chalk.dim(` Or visit: ${deviceCode.verification_uri_complete}`));
76
+ log(chalk.dim(` Code expires in ${Math.floor(deviceCode.expires_in / 60)} minutes.`));
77
+ log('');
78
+ const pollSpinner = ora('Waiting for authorization...').start();
79
+ try {
80
+ const tokens = await pollForToken(deviceCode.device_code, deviceCode.interval, deviceCode.expires_in);
81
+ pollSpinner.text = 'Fetching user info...';
82
+ const userInfo = await fetchUserInfo(tokens.access_token);
83
+ const credentials = {
84
+ access_token: tokens.access_token,
85
+ refresh_token: tokens.refresh_token,
86
+ token_type: tokens.token_type,
87
+ expires_at: Date.now() + tokens.expires_in * 1000,
88
+ scope: tokens.scope,
89
+ user: {
90
+ id: userInfo.sub,
91
+ email: userInfo.email,
92
+ name: userInfo.name,
93
+ },
94
+ organization: userInfo.organizations?.[0]
95
+ ? {
96
+ id: userInfo.organizations[0].id,
97
+ name: userInfo.organizations[0].name,
98
+ slug: userInfo.organizations[0].slug,
99
+ }
100
+ : undefined,
101
+ };
102
+ await storeCredentials(credentials);
103
+ pollSpinner.succeed(`Logged in as ${chalk.cyan(userInfo.name)} (${userInfo.email})`);
104
+ }
105
+ catch (error) {
106
+ pollSpinner.fail('Authentication failed');
107
+ throw error;
108
+ }
109
+ }
@@ -0,0 +1,16 @@
1
+ export declare function clearContributorStatusCache(): void;
2
+ export interface ContributorStatus {
3
+ isContributor: boolean;
4
+ approved: boolean;
5
+ contributorId: string | null;
6
+ }
7
+ /**
8
+ * Check if the authenticated user is an approved contributor.
9
+ * Uses a 1-hour local cache to avoid API calls on every invocation.
10
+ */
11
+ export declare function checkContributorStatus(): Promise<ContributorStatus>;
12
+ /**
13
+ * Convenience: returns true only if the user is an approved contributor.
14
+ */
15
+ export declare function isApprovedContributor(): Promise<boolean>;
16
+ //# sourceMappingURL=entitlements.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"entitlements.d.ts","sourceRoot":"","sources":["../../../src/lib/auth/entitlements.ts"],"names":[],"mappings":"AA0BA,wBAAgB,2BAA2B,IAAI,IAAI,CASlD;AAoBD,MAAM,WAAW,iBAAiB;IAChC,aAAa,EAAE,OAAO,CAAA;IACtB,QAAQ,EAAE,OAAO,CAAA;IACjB,aAAa,EAAE,MAAM,GAAG,IAAI,CAAA;CAC7B;AA2BD;;;GAGG;AACH,wBAAsB,sBAAsB,IAAI,OAAO,CAAC,iBAAiB,CAAC,CAqBzE;AAED;;GAEG;AACH,wBAAsB,qBAAqB,IAAI,OAAO,CAAC,OAAO,CAAC,CAG9D"}
@@ -0,0 +1,89 @@
1
+ import fs from 'fs';
2
+ import os from 'os';
3
+ import path from 'path';
4
+ import { CREDENTIALS_DIR, SUPABASE_PUBLISHABLE_KEY, SUPABASE_URL, } from '../config.js';
5
+ import { getStoredCredentials } from './token-store.js';
6
+ const CONTRIBUTOR_MANAGE_URL = `${SUPABASE_URL}/functions/v1/contributor-manage`;
7
+ const CACHE_TTL = 60 * 60 * 1000; // 1 hour
8
+ function getCachePath() {
9
+ return path.join(os.homedir(), CREDENTIALS_DIR, 'contributor-status.json');
10
+ }
11
+ export function clearContributorStatusCache() {
12
+ try {
13
+ const filePath = getCachePath();
14
+ if (fs.existsSync(filePath)) {
15
+ fs.unlinkSync(filePath);
16
+ }
17
+ }
18
+ catch {
19
+ /* ignore */
20
+ }
21
+ }
22
+ function readCache(userId) {
23
+ try {
24
+ const raw = fs.readFileSync(getCachePath(), 'utf-8');
25
+ const cache = JSON.parse(raw);
26
+ if (cache.userId !== userId)
27
+ return null;
28
+ if (Date.now() - cache.checkedAt > CACHE_TTL)
29
+ return null;
30
+ return cache;
31
+ }
32
+ catch {
33
+ return null;
34
+ }
35
+ }
36
+ function writeCache(entry) {
37
+ const dir = path.join(os.homedir(), CREDENTIALS_DIR);
38
+ fs.mkdirSync(dir, { recursive: true, mode: 0o700 });
39
+ fs.writeFileSync(getCachePath(), JSON.stringify(entry), { mode: 0o600 });
40
+ }
41
+ async function fetchContributorStatus(accessToken) {
42
+ const response = await fetch(CONTRIBUTOR_MANAGE_URL, {
43
+ method: 'POST',
44
+ headers: {
45
+ 'Content-Type': 'application/json',
46
+ Authorization: `Bearer ${accessToken}`,
47
+ apikey: SUPABASE_PUBLISHABLE_KEY,
48
+ },
49
+ body: JSON.stringify({ action: 'check-status' }),
50
+ });
51
+ if (!response.ok) {
52
+ return { isContributor: false, approved: false, contributorId: null };
53
+ }
54
+ const data = (await response.json());
55
+ return {
56
+ isContributor: data.isContributor === true,
57
+ approved: data.approved === true,
58
+ contributorId: data.contributorId ?? null,
59
+ };
60
+ }
61
+ /**
62
+ * Check if the authenticated user is an approved contributor.
63
+ * Uses a 1-hour local cache to avoid API calls on every invocation.
64
+ */
65
+ export async function checkContributorStatus() {
66
+ const credentials = await getStoredCredentials();
67
+ if (!credentials?.access_token) {
68
+ return { isContributor: false, approved: false, contributorId: null };
69
+ }
70
+ const userId = credentials.user.id;
71
+ const cached = readCache(userId);
72
+ if (cached) {
73
+ return {
74
+ isContributor: cached.isContributor,
75
+ approved: cached.approved,
76
+ contributorId: cached.contributorId,
77
+ };
78
+ }
79
+ const status = await fetchContributorStatus(credentials.access_token);
80
+ writeCache({ userId, ...status, checkedAt: Date.now() });
81
+ return status;
82
+ }
83
+ /**
84
+ * Convenience: returns true only if the user is an approved contributor.
85
+ */
86
+ export async function isApprovedContributor() {
87
+ const status = await checkContributorStatus();
88
+ return status.approved;
89
+ }