@vendoai/vendo 0.4.0

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 (212) hide show
  1. package/LICENSE +202 -0
  2. package/README.md +28 -0
  3. package/bin/vendo.mjs +4 -0
  4. package/dist/ai-sdk.d.ts +35 -0
  5. package/dist/ai-sdk.js +62 -0
  6. package/dist/auth-presets/auth-js.d.ts +14 -0
  7. package/dist/auth-presets/auth-js.js +91 -0
  8. package/dist/auth-presets/auth0.d.ts +23 -0
  9. package/dist/auth-presets/auth0.js +98 -0
  10. package/dist/auth-presets/clerk.d.ts +23 -0
  11. package/dist/auth-presets/clerk.js +65 -0
  12. package/dist/auth-presets/conformance.d.ts +42 -0
  13. package/dist/auth-presets/conformance.js +139 -0
  14. package/dist/auth-presets/identity.d.ts +56 -0
  15. package/dist/auth-presets/identity.js +152 -0
  16. package/dist/auth-presets/index.d.ts +14 -0
  17. package/dist/auth-presets/index.js +12 -0
  18. package/dist/auth-presets/jwt.d.ts +16 -0
  19. package/dist/auth-presets/jwt.js +54 -0
  20. package/dist/auth-presets/shared.d.ts +35 -0
  21. package/dist/auth-presets/shared.js +1 -0
  22. package/dist/auth-presets/supabase.d.ts +42 -0
  23. package/dist/auth-presets/supabase.js +229 -0
  24. package/dist/byo-approvals.d.ts +37 -0
  25. package/dist/byo-approvals.js +189 -0
  26. package/dist/capability-misses.d.ts +38 -0
  27. package/dist/capability-misses.js +172 -0
  28. package/dist/catalog.d.ts +19 -0
  29. package/dist/catalog.js +152 -0
  30. package/dist/cli/cloud/args.d.ts +3 -0
  31. package/dist/cli/cloud/args.js +32 -0
  32. package/dist/cli/cloud/auth.d.ts +17 -0
  33. package/dist/cli/cloud/auth.js +123 -0
  34. package/dist/cli/cloud/client.d.ts +28 -0
  35. package/dist/cli/cloud/client.js +133 -0
  36. package/dist/cli/cloud/command.d.ts +22 -0
  37. package/dist/cli/cloud/command.js +73 -0
  38. package/dist/cli/cloud/deploy.d.ts +26 -0
  39. package/dist/cli/cloud/deploy.js +187 -0
  40. package/dist/cli/cloud/device-login.d.ts +32 -0
  41. package/dist/cli/cloud/device-login.js +259 -0
  42. package/dist/cli/cloud/index.d.ts +5 -0
  43. package/dist/cli/cloud/index.js +59 -0
  44. package/dist/cli/cloud/keys.d.ts +2 -0
  45. package/dist/cli/cloud/keys.js +31 -0
  46. package/dist/cli/cloud/members.d.ts +3 -0
  47. package/dist/cli/cloud/members.js +25 -0
  48. package/dist/cli/cloud/output.d.ts +4 -0
  49. package/dist/cli/cloud/output.js +12 -0
  50. package/dist/cli/cloud/pending-claim.d.ts +29 -0
  51. package/dist/cli/cloud/pending-claim.js +38 -0
  52. package/dist/cli/cloud/read.d.ts +3 -0
  53. package/dist/cli/cloud/read.js +12 -0
  54. package/dist/cli/cloud/services.d.ts +2 -0
  55. package/dist/cli/cloud/services.js +9 -0
  56. package/dist/cli/cloud/session.d.ts +13 -0
  57. package/dist/cli/cloud/session.js +41 -0
  58. package/dist/cli/cloud-init.d.ts +60 -0
  59. package/dist/cli/cloud-init.js +149 -0
  60. package/dist/cli/dep-versions.d.ts +26 -0
  61. package/dist/cli/dep-versions.js +77 -0
  62. package/dist/cli/doctor-codes.d.ts +58 -0
  63. package/dist/cli/doctor-codes.js +60 -0
  64. package/dist/cli/doctor-live.d.ts +67 -0
  65. package/dist/cli/doctor-live.js +169 -0
  66. package/dist/cli/doctor.d.ts +52 -0
  67. package/dist/cli/doctor.js +467 -0
  68. package/dist/cli/eject.d.ts +29 -0
  69. package/dist/cli/eject.js +141 -0
  70. package/dist/cli/extract/apply.d.ts +26 -0
  71. package/dist/cli/extract/apply.js +123 -0
  72. package/dist/cli/extract/claude-cli-harness.d.ts +18 -0
  73. package/dist/cli/extract/claude-cli-harness.js +132 -0
  74. package/dist/cli/extract/claude-harness.d.ts +14 -0
  75. package/dist/cli/extract/claude-harness.js +127 -0
  76. package/dist/cli/extract/codex-cli-harness.d.ts +31 -0
  77. package/dist/cli/extract/codex-cli-harness.js +154 -0
  78. package/dist/cli/extract/delegate.d.ts +21 -0
  79. package/dist/cli/extract/delegate.js +77 -0
  80. package/dist/cli/extract/extraction.d.ts +65 -0
  81. package/dist/cli/extract/extraction.js +300 -0
  82. package/dist/cli/extract/gateway-fuel.d.ts +69 -0
  83. package/dist/cli/extract/gateway-fuel.js +69 -0
  84. package/dist/cli/extract/harness.d.ts +114 -0
  85. package/dist/cli/extract/harness.js +84 -0
  86. package/dist/cli/extract/index.d.ts +9 -0
  87. package/dist/cli/extract/index.js +9 -0
  88. package/dist/cli/extract/npx-engine-harness.d.ts +73 -0
  89. package/dist/cli/extract/npx-engine-harness.js +225 -0
  90. package/dist/cli/extract/stages.d.ts +149 -0
  91. package/dist/cli/extract/stages.js +398 -0
  92. package/dist/cli/framework.d.ts +8 -0
  93. package/dist/cli/framework.js +36 -0
  94. package/dist/cli/init-auth.d.ts +67 -0
  95. package/dist/cli/init-auth.js +142 -0
  96. package/dist/cli/init-scaffolds.d.ts +28 -0
  97. package/dist/cli/init-scaffolds.js +235 -0
  98. package/dist/cli/init.d.ts +115 -0
  99. package/dist/cli/init.js +1008 -0
  100. package/dist/cli/mcp/index.d.ts +7 -0
  101. package/dist/cli/mcp/index.js +59 -0
  102. package/dist/cli/mcp/registry.d.ts +9 -0
  103. package/dist/cli/mcp/registry.js +117 -0
  104. package/dist/cli/mcp/server-json.d.ts +12 -0
  105. package/dist/cli/mcp/server-json.js +65 -0
  106. package/dist/cli/mcp/server.schema.json +574 -0
  107. package/dist/cli/mcp/verify-domain.d.ts +11 -0
  108. package/dist/cli/mcp/verify-domain.js +48 -0
  109. package/dist/cli/playground/app/embed-entry.d.ts +20 -0
  110. package/dist/cli/playground/app/embed-entry.js +46 -0
  111. package/dist/cli/playground/app/fake-client.d.ts +3 -0
  112. package/dist/cli/playground/app/fake-client.js +215 -0
  113. package/dist/cli/playground/app/fixtures.d.ts +47 -0
  114. package/dist/cli/playground/app/fixtures.js +472 -0
  115. package/dist/cli/playground/app/main.d.ts +1 -0
  116. package/dist/cli/playground/app/main.js +108 -0
  117. package/dist/cli/playground/app/scenario-mount.d.ts +13 -0
  118. package/dist/cli/playground/app/scenario-mount.js +48 -0
  119. package/dist/cli/playground/app/scenarios.d.ts +25 -0
  120. package/dist/cli/playground/app/scenarios.js +152 -0
  121. package/dist/cli/playground/app/theme-editor.d.ts +13 -0
  122. package/dist/cli/playground/app/theme-editor.js +149 -0
  123. package/dist/cli/playground/app/theme-state.d.ts +29 -0
  124. package/dist/cli/playground/app/theme-state.js +151 -0
  125. package/dist/cli/playground/bundle.gen.d.ts +2 -0
  126. package/dist/cli/playground/bundle.gen.js +2 -0
  127. package/dist/cli/playground/embed-bundle.gen.d.ts +2 -0
  128. package/dist/cli/playground/embed-bundle.gen.js +2 -0
  129. package/dist/cli/playground.d.ts +33 -0
  130. package/dist/cli/playground.js +120 -0
  131. package/dist/cli/pretty.d.ts +48 -0
  132. package/dist/cli/pretty.js +330 -0
  133. package/dist/cli/refine.d.ts +43 -0
  134. package/dist/cli/refine.js +181 -0
  135. package/dist/cli/semantics.d.ts +31 -0
  136. package/dist/cli/semantics.js +122 -0
  137. package/dist/cli/shared.d.ts +68 -0
  138. package/dist/cli/shared.js +184 -0
  139. package/dist/cli/sync.d.ts +36 -0
  140. package/dist/cli/sync.js +202 -0
  141. package/dist/cli/theme/color.d.ts +15 -0
  142. package/dist/cli/theme/color.js +188 -0
  143. package/dist/cli/theme/css-vars.d.ts +13 -0
  144. package/dist/cli/theme/css-vars.js +40 -0
  145. package/dist/cli/theme/entry-candidates.d.ts +5 -0
  146. package/dist/cli/theme/entry-candidates.js +22 -0
  147. package/dist/cli/theme/extract-theme.d.ts +184 -0
  148. package/dist/cli/theme/extract-theme.js +358 -0
  149. package/dist/cli/theme/walk.d.ts +2 -0
  150. package/dist/cli/theme/walk.js +24 -0
  151. package/dist/cli.d.ts +2 -0
  152. package/dist/cli.js +333 -0
  153. package/dist/cloud-apps.d.ts +17 -0
  154. package/dist/cloud-apps.js +46 -0
  155. package/dist/cloud-console.d.ts +32 -0
  156. package/dist/cloud-console.js +72 -0
  157. package/dist/cloud-tools.d.ts +23 -0
  158. package/dist/cloud-tools.js +196 -0
  159. package/dist/connections.d.ts +71 -0
  160. package/dist/connections.js +192 -0
  161. package/dist/deployment-identity.d.ts +12 -0
  162. package/dist/deployment-identity.js +69 -0
  163. package/dist/dev-creds/model.d.ts +65 -0
  164. package/dist/dev-creds/model.js +159 -0
  165. package/dist/dev-creds/resolve.d.ts +36 -0
  166. package/dist/dev-creds/resolve.js +57 -0
  167. package/dist/hosted-store.d.ts +52 -0
  168. package/dist/hosted-store.js +322 -0
  169. package/dist/index.d.ts +13 -0
  170. package/dist/index.js +1 -0
  171. package/dist/mastra.d.ts +46 -0
  172. package/dist/mastra.js +88 -0
  173. package/dist/react.d.ts +10 -0
  174. package/dist/react.js +32 -0
  175. package/dist/refine.d.ts +244 -0
  176. package/dist/refine.js +599 -0
  177. package/dist/remixable.d.ts +18 -0
  178. package/dist/remixable.js +42 -0
  179. package/dist/runtime-capture.d.ts +18 -0
  180. package/dist/runtime-capture.js +111 -0
  181. package/dist/sandbox-wire.d.ts +101 -0
  182. package/dist/sandbox-wire.js +101 -0
  183. package/dist/sandbox.d.ts +38 -0
  184. package/dist/sandbox.js +389 -0
  185. package/dist/server.d.ts +216 -0
  186. package/dist/server.js +1320 -0
  187. package/dist/sync-impact.d.ts +14 -0
  188. package/dist/sync-impact.js +75 -0
  189. package/dist/turn-liveness.d.ts +33 -0
  190. package/dist/turn-liveness.js +105 -0
  191. package/dist/wire/approvals.d.ts +10 -0
  192. package/dist/wire/approvals.js +59 -0
  193. package/dist/wire/apps.d.ts +5 -0
  194. package/dist/wire/apps.js +155 -0
  195. package/dist/wire/automations.d.ts +5 -0
  196. package/dist/wire/automations.js +57 -0
  197. package/dist/wire/box.d.ts +3 -0
  198. package/dist/wire/box.js +268 -0
  199. package/dist/wire/connections.d.ts +5 -0
  200. package/dist/wire/connections.js +47 -0
  201. package/dist/wire/context.d.ts +22 -0
  202. package/dist/wire/context.js +190 -0
  203. package/dist/wire/doctor.d.ts +19 -0
  204. package/dist/wire/doctor.js +116 -0
  205. package/dist/wire/misc.d.ts +18 -0
  206. package/dist/wire/misc.js +227 -0
  207. package/dist/wire/shared.d.ts +158 -0
  208. package/dist/wire/shared.js +134 -0
  209. package/dist/wire/threads.d.ts +3 -0
  210. package/dist/wire/threads.js +68 -0
  211. package/package.json +151 -0
  212. package/skills/vendo-setup/SKILL.md +128 -0
@@ -0,0 +1,19 @@
1
+ import type { ComponentCatalog, ComponentRegistry, NormalizedCatalog, VendoTheme } from "@vendoai/core";
2
+ /**
3
+ * Strictly parses catalog@1. Disk entries carry their JSON Schema for
4
+ * prompting AND validation: the same document drives both (04 §1).
5
+ */
6
+ export declare function runtimeCatalogFromJson(raw: string | undefined, file?: string): NormalizedCatalog;
7
+ /**
8
+ * 01 §14 (amended 2026-07-18): normalize a `createVendo({ catalog })` value —
9
+ * array form or name-keyed registry form — into the internal catalog. Registry
10
+ * entries: key → `name`, `props` → `propsSchema`, `component` dropped (the
11
+ * server never touches or executes it). Derivation happens here, once.
12
+ */
13
+ export declare function normalizeCatalogConfig(config: ComponentCatalog | ComponentRegistry | undefined): NormalizedCatalog;
14
+ /** AGENT-1 — 03 §3 item (4): the model-facing summary of the host components a
15
+ * generated view may use and how the host's brand should feel. One succinct
16
+ * block; the agent injects it only for venues that render trees. */
17
+ export declare function catalogThemeSummary(catalog: NormalizedCatalog, theme?: VendoTheme): string | undefined;
18
+ /** Explicit createVendo registrations win by name over disk registrations. */
19
+ export declare function mergeRuntimeCatalog(disk: NormalizedCatalog, explicit?: NormalizedCatalog): NormalizedCatalog;
@@ -0,0 +1,152 @@
1
+ import { catalogFileSchema } from "@vendoai/actions";
2
+ import { zodSchema } from "ai";
3
+ import Ajv from "ajv";
4
+ import addFormats from "ajv-formats";
5
+ const ajv = new Ajv({ allErrors: true, strict: false });
6
+ addFormats(ajv);
7
+ function permissivePropsSchema() {
8
+ return { "~standard": { validate: (value) => ({ value }) } };
9
+ }
10
+ function ajvIssuePath(error) {
11
+ const path = error.instancePath
12
+ .split("/")
13
+ .filter((segment) => segment.length > 0)
14
+ .map((segment) => (/^\d+$/.test(segment) ? Number(segment) : segment.replace(/~1/g, "/").replace(/~0/g, "~")));
15
+ const missing = error.params.missingProperty;
16
+ if (typeof missing === "string")
17
+ path.push(missing);
18
+ return path;
19
+ }
20
+ /** 04 §1 (amended 2026-07-18): a disk entry's JSON Schema is executable, not
21
+ * just prompt guidance — build the entry's runtime validator from it, closing
22
+ * the old pass-through gap. Uncompilable schemas fall back to permissive. */
23
+ function diskPropsValidator(schema, name) {
24
+ try {
25
+ const validate = ajv.compile(schema);
26
+ return {
27
+ "~standard": {
28
+ validate: (value) => {
29
+ if (validate(value))
30
+ return { value };
31
+ return {
32
+ issues: (validate.errors ?? []).map((error) => ({
33
+ message: error.message ?? "props did not match the catalog schema",
34
+ path: ajvIssuePath(error),
35
+ })),
36
+ };
37
+ },
38
+ },
39
+ };
40
+ }
41
+ catch (error) {
42
+ console.warn(`[vendo] catalog entry "${name}" has a props schema ajv could not compile (${error instanceof Error ? error.message : String(error)}); validating permissively.`);
43
+ return permissivePropsSchema();
44
+ }
45
+ }
46
+ function parseIssue(error) {
47
+ if (error !== null && typeof error === "object" && "issues" in error && Array.isArray(error.issues)) {
48
+ return error.issues.map((issue) => {
49
+ if (issue === null || typeof issue !== "object")
50
+ return String(issue);
51
+ const path = "path" in issue && Array.isArray(issue.path) && issue.path.length > 0
52
+ ? `${issue.path.join(".")}: `
53
+ : "";
54
+ return `${path}${"message" in issue ? String(issue.message) : String(issue)}`;
55
+ }).join("; ");
56
+ }
57
+ return error instanceof Error ? error.message : String(error);
58
+ }
59
+ /**
60
+ * Strictly parses catalog@1. Disk entries carry their JSON Schema for
61
+ * prompting AND validation: the same document drives both (04 §1).
62
+ */
63
+ export function runtimeCatalogFromJson(raw, file = ".vendo/catalog.json") {
64
+ if (raw === undefined)
65
+ return [];
66
+ try {
67
+ const parsed = catalogFileSchema.parse(JSON.parse(raw));
68
+ return parsed.entries.map((entry) => ({
69
+ name: entry.name,
70
+ description: entry.description,
71
+ propsSchema: diskPropsValidator(entry.propsSchema, entry.name),
72
+ propsJsonSchema: entry.propsSchema,
73
+ ...(entry.examples === undefined ? {} : { examples: entry.examples }),
74
+ }));
75
+ }
76
+ catch (error) {
77
+ console.error(`[vendo] Failed to load host components from ${file}: ${parseIssue(error)}. Run "vendo sync" to regenerate the file.`);
78
+ return [];
79
+ }
80
+ }
81
+ function isZodSchema(schema) {
82
+ const standard = schema["~standard"];
83
+ return standard.vendor === "zod";
84
+ }
85
+ /** Derive the model-facing JSON Schema from a zod entry (01 §14: derived
86
+ * internally, once, at normalization time). Non-zod standard schemas derive
87
+ * nothing — they still validate at runtime and prompt description-only,
88
+ * matching the contract's schema-less semantics. */
89
+ function derivedJsonSchema(schema, name) {
90
+ if (schema === undefined || !isZodSchema(schema))
91
+ return undefined;
92
+ try {
93
+ const { $schema: _meta, ...derived } = zodSchema(schema).jsonSchema;
94
+ return derived;
95
+ }
96
+ catch (error) {
97
+ console.warn(`[vendo] could not derive a JSON Schema for catalog entry "${name}" (${error instanceof Error ? error.message : String(error)}); the prompt will carry its description only.`);
98
+ return undefined;
99
+ }
100
+ }
101
+ function normalizeEntry(entry) {
102
+ const derived = derivedJsonSchema(entry.propsSchema, entry.name);
103
+ return {
104
+ name: entry.name,
105
+ description: entry.description,
106
+ ...(entry.propsSchema === undefined ? {} : { propsSchema: entry.propsSchema }),
107
+ ...(derived === undefined ? {} : { propsJsonSchema: derived }),
108
+ ...(entry.examples === undefined ? {} : { examples: entry.examples }),
109
+ ...(entry.remixable === undefined ? {} : { remixable: entry.remixable }),
110
+ };
111
+ }
112
+ /**
113
+ * 01 §14 (amended 2026-07-18): normalize a `createVendo({ catalog })` value —
114
+ * array form or name-keyed registry form — into the internal catalog. Registry
115
+ * entries: key → `name`, `props` → `propsSchema`, `component` dropped (the
116
+ * server never touches or executes it). Derivation happens here, once.
117
+ */
118
+ export function normalizeCatalogConfig(config) {
119
+ if (config === undefined)
120
+ return [];
121
+ if (Array.isArray(config))
122
+ return config.map(normalizeEntry);
123
+ return Object.entries(config).map(([name, entry]) => normalizeEntry({
124
+ name,
125
+ description: entry.description,
126
+ ...(entry.props === undefined ? {} : { propsSchema: entry.props }),
127
+ ...(entry.examples === undefined ? {} : { examples: entry.examples }),
128
+ ...(entry.remixable === undefined ? {} : { remixable: entry.remixable }),
129
+ }));
130
+ }
131
+ /** AGENT-1 — 03 §3 item (4): the model-facing summary of the host components a
132
+ * generated view may use and how the host's brand should feel. One succinct
133
+ * block; the agent injects it only for venues that render trees. */
134
+ export function catalogThemeSummary(catalog, theme) {
135
+ const sections = [];
136
+ if (catalog.length > 0) {
137
+ const lines = catalog.map((entry) => `- ${entry.name}: ${entry.description.split("\n", 1)[0] ?? ""}`.trimEnd());
138
+ sections.push(`Host components (usable in generated views beside the built-in primitives)\n${lines.join("\n")}`);
139
+ }
140
+ if (theme !== undefined) {
141
+ sections.push(`Theme: ${theme.density} density, ${theme.motion} motion, ${theme.typography.fontFamily} typography.`);
142
+ }
143
+ return sections.length > 0 ? sections.join("\n\n") : undefined;
144
+ }
145
+ /** Explicit createVendo registrations win by name over disk registrations. */
146
+ export function mergeRuntimeCatalog(disk, explicit = []) {
147
+ const explicitNames = new Set(explicit.map((entry) => entry.name));
148
+ return [
149
+ ...disk.filter((entry) => !explicitNames.has(entry.name)),
150
+ ...explicit,
151
+ ];
152
+ }
@@ -0,0 +1,3 @@
1
+ export declare function option(args: string[], name: string): string | undefined;
2
+ export declare function options(args: string[], name: string): string[];
3
+ export declare function positionals(args: string[], optionNames: string[]): string[];
@@ -0,0 +1,32 @@
1
+ export function option(args, name) {
2
+ const exact = args.indexOf(name);
3
+ if (exact >= 0)
4
+ return args[exact + 1];
5
+ return args.find((value) => value.startsWith(`${name}=`))?.slice(name.length + 1);
6
+ }
7
+ export function options(args, name) {
8
+ const values = [];
9
+ for (let index = 0; index < args.length; index += 1) {
10
+ const value = args[index];
11
+ if (value === name) {
12
+ const next = args[index + 1];
13
+ if (next === undefined || next.startsWith("--"))
14
+ throw new Error(`${name} requires a value`);
15
+ values.push(next);
16
+ index += 1;
17
+ }
18
+ else if (value.startsWith(`${name}=`)) {
19
+ values.push(value.slice(name.length + 1));
20
+ }
21
+ }
22
+ return values;
23
+ }
24
+ export function positionals(args, optionNames) {
25
+ const values = new Set();
26
+ for (const name of optionNames) {
27
+ const index = args.indexOf(name);
28
+ if (index >= 0 && args[index + 1] !== undefined)
29
+ values.add(args[index + 1]);
30
+ }
31
+ return args.filter((value) => !value.startsWith("--") && !values.has(value));
32
+ }
@@ -0,0 +1,17 @@
1
+ import { type CloudFetchOptions } from "./client.js";
2
+ import { type CloudSession } from "./session.js";
3
+ import { type Output } from "../shared.js";
4
+ type CloudFetcher = (path: string, options?: CloudFetchOptions) => Promise<unknown>;
5
+ export interface CloudAuthOptions {
6
+ output?: Output;
7
+ fetcher?: CloudFetcher;
8
+ writeSession?: (session: CloudSession) => Promise<void>;
9
+ deleteSession?: () => Promise<void>;
10
+ promptOtp?: (prompt: string) => Promise<string>;
11
+ home?: string;
12
+ env?: Record<string, string | undefined>;
13
+ }
14
+ export declare function runLogin(args: string[], options?: CloudAuthOptions): Promise<number>;
15
+ export declare function runLogout(_args: string[], options?: CloudAuthOptions): Promise<number>;
16
+ export declare function runWhoami(args: string[], options?: CloudAuthOptions): Promise<number>;
17
+ export {};
@@ -0,0 +1,123 @@
1
+ import { createInterface } from "node:readline/promises";
2
+ import { stdin, stdout } from "node:process";
3
+ import { option, positionals } from "./args.js";
4
+ import { cloudFetch } from "./client.js";
5
+ import { deleteCloudSession, writeCloudSession, } from "./session.js";
6
+ import { errorMessage, printJson } from "./output.js";
7
+ import { consoleOutput } from "../shared.js";
8
+ function syntheticSession(token) {
9
+ try {
10
+ const payload = token.split(".")[1];
11
+ if (!payload)
12
+ return { access_token: token };
13
+ const normalized = payload.replace(/-/g, "+").replace(/_/g, "/").padEnd(Math.ceil(payload.length / 4) * 4, "=");
14
+ const value = JSON.parse(atob(normalized));
15
+ return typeof value.exp === "number"
16
+ ? { access_token: token, expires_at: value.exp }
17
+ : { access_token: token };
18
+ }
19
+ catch {
20
+ return { access_token: token };
21
+ }
22
+ }
23
+ function verifiedSession(value) {
24
+ const candidate = typeof value === "object" && value !== null && "session" in value
25
+ ? value.session
26
+ : value;
27
+ if (typeof candidate !== "object" || candidate === null
28
+ || typeof candidate.access_token !== "string"
29
+ || typeof candidate.refresh_token !== "string"
30
+ || typeof candidate.expires_at !== "number") {
31
+ throw new Error("Vendo Cloud returned an invalid session");
32
+ }
33
+ const session = candidate;
34
+ return {
35
+ access_token: session.access_token,
36
+ refresh_token: session.refresh_token,
37
+ expires_at: session.expires_at,
38
+ };
39
+ }
40
+ async function interactiveOtp(prompt) {
41
+ const readline = createInterface({ input: stdin, output: stdout });
42
+ try {
43
+ return await readline.question(`${prompt}: `);
44
+ }
45
+ finally {
46
+ readline.close();
47
+ }
48
+ }
49
+ export async function runLogin(args, options = {}) {
50
+ const output = options.output ?? consoleOutput;
51
+ const token = option(args, "--token");
52
+ const writeSession = options.writeSession ?? ((session) => writeCloudSession(session, { home: options.home }));
53
+ if (token) {
54
+ try {
55
+ await writeSession(syntheticSession(token));
56
+ printJson(output, { loggedIn: true, mode: "token" });
57
+ return 0;
58
+ }
59
+ catch (error) {
60
+ output.error(errorMessage(error));
61
+ return 1;
62
+ }
63
+ }
64
+ const email = positionals(args, ["--token", "--api-url"])[0];
65
+ if (!email) {
66
+ output.error("Cloud login requires an email or --token <jwt>");
67
+ return 1;
68
+ }
69
+ const fetcher = options.fetcher ?? cloudFetch;
70
+ const common = {
71
+ apiUrl: option(args, "--api-url"),
72
+ env: options.env ?? process.env,
73
+ };
74
+ try {
75
+ await fetcher("/api/v1/auth/otp/start", { ...common, method: "POST", body: { email } });
76
+ const otp = (await (options.promptOtp ?? interactiveOtp)(`Enter the code sent to ${email}`)).trim();
77
+ if (!/^\d{6,10}$/.test(otp))
78
+ throw new Error("Email OTP must be a 6-10 digit code");
79
+ const result = await fetcher("/api/v1/auth/otp/verify", {
80
+ ...common,
81
+ method: "POST",
82
+ body: { email, token: otp },
83
+ });
84
+ await writeSession(verifiedSession(result));
85
+ printJson(output, { loggedIn: true, mode: "email", email });
86
+ return 0;
87
+ }
88
+ catch (error) {
89
+ output.error(errorMessage(error));
90
+ return 1;
91
+ }
92
+ }
93
+ export async function runLogout(_args, options = {}) {
94
+ const output = options.output ?? consoleOutput;
95
+ try {
96
+ await (options.deleteSession ?? (() => deleteCloudSession({ home: options.home })))();
97
+ printJson(output, { loggedOut: true });
98
+ return 0;
99
+ }
100
+ catch (error) {
101
+ output.error(errorMessage(error));
102
+ return 1;
103
+ }
104
+ }
105
+ export async function runWhoami(args, options = {}) {
106
+ const output = options.output ?? consoleOutput;
107
+ const fetcher = options.fetcher ?? cloudFetch;
108
+ try {
109
+ const orgs = await fetcher("/api/v1/orgs", {
110
+ auth: "user",
111
+ apiUrl: option(args, "--api-url"),
112
+ accessToken: option(args, "--token"),
113
+ home: options.home,
114
+ env: options.env ?? process.env,
115
+ });
116
+ printJson(output, orgs);
117
+ return 0;
118
+ }
119
+ catch (error) {
120
+ output.error(errorMessage(error));
121
+ return 1;
122
+ }
123
+ }
@@ -0,0 +1,28 @@
1
+ import { type CloudSession } from "./session.js";
2
+ export declare function isVendoKey(key: string): boolean;
3
+ export declare class CloudError extends Error {
4
+ readonly code: string;
5
+ readonly status: number;
6
+ constructor(code: string, message: string, status: number);
7
+ }
8
+ export interface CloudUrlOptions {
9
+ apiUrl?: string;
10
+ env?: Record<string, string | undefined>;
11
+ }
12
+ export declare function resolveCloudBaseUrl(options?: CloudUrlOptions): string;
13
+ export interface SessionStore {
14
+ read(): Promise<CloudSession | null>;
15
+ write(session: CloudSession): Promise<void>;
16
+ }
17
+ export interface CloudFetchOptions extends CloudUrlOptions {
18
+ method?: string;
19
+ body?: unknown;
20
+ auth?: "user" | "key";
21
+ apiKey?: string;
22
+ accessToken?: string;
23
+ fetchImpl?: typeof fetch;
24
+ home?: string;
25
+ sessionStore?: SessionStore;
26
+ signal?: AbortSignal;
27
+ }
28
+ export declare function cloudFetch<T = unknown>(path: string, options?: CloudFetchOptions): Promise<T>;
@@ -0,0 +1,133 @@
1
+ import { isSessionExpired, readCloudSession, writeCloudSession, } from "./session.js";
2
+ import { deploymentIdentityHeaders } from "../../deployment-identity.js";
3
+ import { CLI_VERSION } from "../shared.js";
4
+ const DEFAULT_CLOUD_URL = "https://console.vendo.run";
5
+ export function isVendoKey(key) {
6
+ return /^vnd_[0-9a-f]{40}$/.test(key);
7
+ }
8
+ export class CloudError extends Error {
9
+ code;
10
+ status;
11
+ constructor(code, message, status) {
12
+ super(message);
13
+ this.name = "CloudError";
14
+ this.code = code;
15
+ this.status = status;
16
+ }
17
+ }
18
+ export function resolveCloudBaseUrl(options = {}) {
19
+ const value = options.apiUrl ?? (options.env ?? process.env).VENDO_CLOUD_URL ?? DEFAULT_CLOUD_URL;
20
+ return value.replace(/\/+$/, "");
21
+ }
22
+ function requestUrl(path, options) {
23
+ const base = resolveCloudBaseUrl(options);
24
+ const suffix = base.endsWith("/api/v1") && path.startsWith("/api/v1/")
25
+ ? path.slice("/api/v1".length)
26
+ : path;
27
+ return `${base}${suffix.startsWith("/") ? suffix : `/${suffix}`}`;
28
+ }
29
+ async function responseBody(response) {
30
+ const text = await response.text();
31
+ if (text.length === 0)
32
+ return undefined;
33
+ try {
34
+ return JSON.parse(text);
35
+ }
36
+ catch {
37
+ return text;
38
+ }
39
+ }
40
+ function errorFrom(response, body) {
41
+ const envelope = body;
42
+ const code = typeof envelope?.error?.code === "string" ? envelope.error.code : `http-${response.status}`;
43
+ const message = typeof envelope?.error?.message === "string"
44
+ ? envelope.error.message
45
+ : `Vendo Cloud request failed (${response.status})`;
46
+ return new CloudError(code, message, response.status);
47
+ }
48
+ function defaultSessionStore(home) {
49
+ return {
50
+ read: () => readCloudSession({ home }),
51
+ write: (session) => writeCloudSession(session, { home }),
52
+ };
53
+ }
54
+ function sessionFrom(value) {
55
+ if (typeof value !== "object" || value === null || typeof value.access_token !== "string") {
56
+ throw new CloudError("invalid-session", "Vendo Cloud returned an invalid session", 500);
57
+ }
58
+ return value;
59
+ }
60
+ async function refreshUserSession(session, options, store) {
61
+ if (!session.refresh_token) {
62
+ throw new CloudError("session-expired", "Vendo Cloud session expired; run `vendo cloud login` again", 401);
63
+ }
64
+ const response = await (options.fetchImpl ?? fetch)(requestUrl("/api/v1/auth/refresh", options), {
65
+ method: "POST",
66
+ headers: {
67
+ accept: "application/json",
68
+ "content-type": "application/json",
69
+ "user-agent": `vendo-cli/${CLI_VERSION}`,
70
+ },
71
+ body: JSON.stringify({ refresh_token: session.refresh_token }),
72
+ });
73
+ const body = await responseBody(response);
74
+ if (!response.ok)
75
+ throw errorFrom(response, body);
76
+ const refreshed = sessionFrom(body);
77
+ await store.write(refreshed);
78
+ return refreshed;
79
+ }
80
+ async function send(path, options, token) {
81
+ const headers = {
82
+ accept: "application/json",
83
+ "user-agent": `vendo-cli/${CLI_VERSION}`,
84
+ };
85
+ if (options.body !== undefined)
86
+ headers["content-type"] = "application/json";
87
+ if (token !== undefined)
88
+ headers.authorization = `Bearer ${token}`;
89
+ // The console's shared auth middleware upserts deployment inventory and
90
+ // meters usage from these headers on real service calls — there is no
91
+ // heartbeat (shared with the runtime Cloud adapters; deployment-identity.ts).
92
+ if (options.auth === "key") {
93
+ Object.assign(headers, await deploymentIdentityHeaders());
94
+ }
95
+ const response = await (options.fetchImpl ?? fetch)(requestUrl(path, options), {
96
+ method: options.method ?? (options.body === undefined ? "GET" : "POST"),
97
+ headers,
98
+ body: options.body === undefined ? undefined : JSON.stringify(options.body),
99
+ signal: options.signal,
100
+ });
101
+ return { response, body: await responseBody(response) };
102
+ }
103
+ export async function cloudFetch(path, options = {}) {
104
+ let token;
105
+ let session = null;
106
+ const store = options.sessionStore ?? defaultSessionStore(options.home);
107
+ if (options.auth === "key") {
108
+ token = options.apiKey ?? (options.env ?? process.env).VENDO_API_KEY;
109
+ if (!token)
110
+ throw new CloudError("missing-api-key", "Pass --key or set VENDO_API_KEY", 0);
111
+ }
112
+ else if (options.auth === "user") {
113
+ if (options.accessToken) {
114
+ token = options.accessToken;
115
+ }
116
+ else {
117
+ session = await store.read();
118
+ if (!session)
119
+ throw new CloudError("not-logged-in", "Run `vendo cloud login` first", 401);
120
+ if (isSessionExpired(session))
121
+ session = await refreshUserSession(session, options, store);
122
+ token = session.access_token;
123
+ }
124
+ }
125
+ let result = await send(path, options, token);
126
+ if (options.auth === "user" && !options.accessToken && result.response.status === 401 && session) {
127
+ session = await refreshUserSession(session, options, store);
128
+ result = await send(path, options, session.access_token);
129
+ }
130
+ if (!result.response.ok)
131
+ throw errorFrom(result.response, result.body);
132
+ return result.body;
133
+ }
@@ -0,0 +1,22 @@
1
+ import { type CloudFetchOptions } from "./client.js";
2
+ import { type Output } from "../shared.js";
3
+ export type CloudFetcher = (path: string, options?: CloudFetchOptions) => Promise<unknown>;
4
+ export interface CloudCommandOptions {
5
+ output?: Output;
6
+ fetcher?: CloudFetcher;
7
+ home?: string;
8
+ env?: Record<string, string | undefined>;
9
+ now?: number | (() => number);
10
+ }
11
+ export interface CloudCommandContext {
12
+ output: Output;
13
+ fetcher: CloudFetcher;
14
+ home?: string;
15
+ env: Record<string, string | undefined>;
16
+ now?: number | (() => number);
17
+ }
18
+ export declare function commandContext(options: CloudCommandOptions): CloudCommandContext;
19
+ export declare function userOptions(args: string[], context: CloudCommandContext): CloudFetchOptions;
20
+ export declare function resolveOrgId(args: string[], context: CloudCommandContext): Promise<string>;
21
+ export declare function resolveProjectId(args: string[], context: CloudCommandContext): Promise<string>;
22
+ export declare function runCommand(options: CloudCommandOptions, operation: (context: CloudCommandContext) => Promise<unknown>): Promise<number>;
@@ -0,0 +1,73 @@
1
+ import { option } from "./args.js";
2
+ import { cloudFetch } from "./client.js";
3
+ import { errorMessage, printJson } from "./output.js";
4
+ import { consoleOutput } from "../shared.js";
5
+ export function commandContext(options) {
6
+ return {
7
+ output: options.output ?? consoleOutput,
8
+ fetcher: options.fetcher ?? cloudFetch,
9
+ home: options.home,
10
+ env: options.env ?? process.env,
11
+ now: options.now,
12
+ };
13
+ }
14
+ export function userOptions(args, context) {
15
+ return {
16
+ auth: "user",
17
+ apiUrl: option(args, "--api-url"),
18
+ accessToken: option(args, "--token"),
19
+ home: context.home,
20
+ env: context.env,
21
+ };
22
+ }
23
+ function orgRecords(value) {
24
+ if (Array.isArray(value))
25
+ return value;
26
+ if (typeof value === "object" && value !== null && Array.isArray(value.orgs)) {
27
+ return value.orgs;
28
+ }
29
+ return [];
30
+ }
31
+ export async function resolveOrgId(args, context) {
32
+ const explicit = option(args, "--org");
33
+ if (explicit)
34
+ return explicit;
35
+ const value = await context.fetcher("/api/v1/orgs", userOptions(args, context));
36
+ const orgs = orgRecords(value);
37
+ if (orgs.length === 1 && typeof orgs[0]?.id === "string")
38
+ return orgs[0].id;
39
+ throw new Error("Pass --org <id> (it can only be omitted when your account has exactly one organization)");
40
+ }
41
+ function projectRecords(value) {
42
+ if (Array.isArray(value))
43
+ return value;
44
+ if (typeof value === "object" && value !== null && Array.isArray(value.projects)) {
45
+ return value.projects;
46
+ }
47
+ return [];
48
+ }
49
+ // Keys and usage live under the project since the spine v2 realignment; a
50
+ // bare command walks org -> only project the same way resolveOrgId walks
51
+ // account -> only org.
52
+ export async function resolveProjectId(args, context) {
53
+ const explicit = option(args, "--project");
54
+ if (explicit)
55
+ return explicit;
56
+ const orgId = await resolveOrgId(args, context);
57
+ const value = await context.fetcher(`/api/v1/orgs/${encodeURIComponent(orgId)}/projects`, userOptions(args, context));
58
+ const projects = projectRecords(value);
59
+ if (projects.length === 1 && typeof projects[0]?.id === "string")
60
+ return projects[0].id;
61
+ throw new Error("Pass --project <id> (it can only be omitted when the organization has exactly one project)");
62
+ }
63
+ export async function runCommand(options, operation) {
64
+ const context = commandContext(options);
65
+ try {
66
+ printJson(context.output, await operation(context));
67
+ return 0;
68
+ }
69
+ catch (error) {
70
+ context.output.error(errorMessage(error));
71
+ return 1;
72
+ }
73
+ }
@@ -0,0 +1,26 @@
1
+ import type { AppDocument, PermissionGrant } from "@vendoai/core";
2
+ import { type VendoStore } from "@vendoai/store";
3
+ import { type CloudCommandOptions } from "./command.js";
4
+ export interface LocalDeployApp {
5
+ doc: AppDocument;
6
+ enabled: boolean;
7
+ }
8
+ export interface LocalDeployProject {
9
+ subject: string;
10
+ apps: LocalDeployApp[];
11
+ grants: PermissionGrant[];
12
+ }
13
+ export interface LocalProjectReadOptions {
14
+ subject?: string;
15
+ cwd?: string;
16
+ }
17
+ export interface ReadLocalProjectOptions extends LocalProjectReadOptions {
18
+ storeFactory?: () => VendoStore;
19
+ }
20
+ export type LocalProjectReader = (options: LocalProjectReadOptions) => Promise<LocalDeployProject>;
21
+ export interface CloudDeployOptions extends CloudCommandOptions {
22
+ cwd?: string;
23
+ localProjectReader?: LocalProjectReader;
24
+ }
25
+ export declare function readLocalProject(options?: ReadLocalProjectOptions): Promise<LocalDeployProject>;
26
+ export declare function runDeploy(args: string[], options?: CloudDeployOptions): Promise<number>;