@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,159 @@
1
+ import { createRequire } from "node:module";
2
+ import { join } from "node:path";
3
+ import { pathToFileURL } from "node:url";
4
+ import { resolveCloudBaseUrl } from "../cli/cloud/client.js";
5
+ import { describeDevCredential, resolveDevCredential, } from "./resolve.js";
6
+ const DEFAULT_MODELS = {
7
+ anthropic: {
8
+ module: "@ai-sdk/anthropic",
9
+ factory: "createAnthropic",
10
+ model: "claude-sonnet-4-6",
11
+ modelEnv: "VENDO_DEV_ANTHROPIC_MODEL",
12
+ install: "npm install ai@^6 @ai-sdk/anthropic@^3",
13
+ },
14
+ openai: {
15
+ module: "@ai-sdk/openai",
16
+ factory: "createOpenAI",
17
+ model: "gpt-5",
18
+ modelEnv: "VENDO_DEV_OPENAI_MODEL",
19
+ install: "npm install ai@^6 @ai-sdk/openai@^3",
20
+ },
21
+ google: {
22
+ module: "@ai-sdk/google",
23
+ factory: "createGoogleGenerativeAI",
24
+ model: "gemini-2.5-flash",
25
+ modelEnv: "VENDO_DEV_GOOGLE_MODEL",
26
+ install: "npm install ai@^6 @ai-sdk/google@^3",
27
+ },
28
+ };
29
+ /** The Cloud gateway serves curated model aliases, never raw provider ids:
30
+ * `vendo-default` (Sonnet), `vendo-fast` (Haiku), `vendo-strong` (Opus).
31
+ * VENDO_CLOUD_MODEL picks among them; the gateway remaps any other value to
32
+ * vendo-default (with an `x-vendo-model-remapped` warning header) during the
33
+ * grace window, and will hard-reject non-aliases after it. Same module/
34
+ * factory/install as anthropic — the gateway speaks the Anthropic Messages
35
+ * wire. */
36
+ const CLOUD_MODEL = {
37
+ module: "@ai-sdk/anthropic",
38
+ factory: "createAnthropic",
39
+ model: "vendo-default",
40
+ modelEnv: "VENDO_CLOUD_MODEL",
41
+ install: "npm install ai@^6 @ai-sdk/anthropic@^3",
42
+ };
43
+ export const NO_CREDENTIAL_MESSAGE = "Vendo found no model key. Set ANTHROPIC_API_KEY / OPENAI_API_KEY / GOOGLE_GENERATIVE_AI_API_KEY "
44
+ + "in .env.local (with the matching @ai-sdk provider installed), or run `vendo login` for a "
45
+ + "free dev key. Production always needs a real server-side key.";
46
+ /** Bundler-proof dynamic import: this module runs inside the host's dev server
47
+ * bundle (Next/webpack/turbopack), where a computed `import(...)` becomes a
48
+ * runtime stub throwing "expression is too dynamic". Native import first
49
+ * (plain Node, test VMs), Function-constructed import as the bundler-blind
50
+ * fallback. The Function body is a FIXED literal — the specifier is a
51
+ * parameter, never interpolated into code. */
52
+ async function dynamicImport(url) {
53
+ try {
54
+ return await import(url);
55
+ }
56
+ catch (nativeError) {
57
+ try {
58
+ const escaped = new Function("specifier", "return import(specifier)");
59
+ return await escaped(url);
60
+ }
61
+ catch {
62
+ throw nativeError;
63
+ }
64
+ }
65
+ }
66
+ async function importHostModule(root, specifier) {
67
+ const require = createRequire(join(root, "package.json"));
68
+ return await dynamicImport(pathToFileURL(require.resolve(specifier)).href);
69
+ }
70
+ export class DevModelController {
71
+ root;
72
+ env;
73
+ importModule;
74
+ resolution = null;
75
+ announced = false;
76
+ constructor(options = {}) {
77
+ this.root = options.root ?? process.cwd();
78
+ this.env = options.env ?? process.env;
79
+ this.importModule = options.importModule ?? importHostModule;
80
+ }
81
+ /** Resolve the credential once per process; state it on the server log once.
82
+ * An unavailable resolution logs its full instructions HERE — the wire
83
+ * deliberately shows clients only a generic error, so the operator's
84
+ * terminal is where the honest message must land. */
85
+ resolve() {
86
+ this.resolution ??= this.resolveOnce().then((resolution) => {
87
+ if (resolution.mode === "unavailable")
88
+ console.error(`[vendo] ${resolution.message}`);
89
+ return resolution;
90
+ });
91
+ return this.resolution;
92
+ }
93
+ announce(credential, suffix = "") {
94
+ if (this.announced)
95
+ return;
96
+ this.announced = true;
97
+ console.log(`[vendo] model: ${describeDevCredential(credential)}${suffix}`);
98
+ }
99
+ /** The shared delegate rung: load the provider module (an install failure
100
+ * resolves unavailable with the exact install command), pick the model id
101
+ * (env override or the spec default), and hand the factory-built model back. */
102
+ async delegate(credential, spec, keyName, config, announceSuffix) {
103
+ let loaded;
104
+ try {
105
+ loaded = await this.importModule(this.root, spec.module);
106
+ }
107
+ catch {
108
+ const message = `${keyName} is set but ${spec.module} is not installed in this app; install it (\`${spec.install}\`).`;
109
+ this.announce(credential, ` — but ${spec.module} is missing`);
110
+ return { mode: "unavailable", credential, message };
111
+ }
112
+ const factory = loaded[spec.factory];
113
+ const modelId = this.env[spec.modelEnv] ?? spec.model;
114
+ const model = factory(config)(modelId);
115
+ this.announce(credential, ` → ${modelId}${announceSuffix}`);
116
+ return { mode: "delegate", credential, model };
117
+ }
118
+ async resolveOnce() {
119
+ const options = { env: this.env };
120
+ const credential = await resolveDevCredential(options);
121
+ if (credential.rung === "env-key") {
122
+ return this.delegate(credential, DEFAULT_MODELS[credential.provider], credential.envVar, { apiKey: this.env[credential.envVar] }, "");
123
+ }
124
+ if (credential.rung === "vendo-cloud") {
125
+ // The gateway speaks the Anthropic Messages wire, so the anthropic
126
+ // provider serves it — pointed at the console instead of Anthropic.
127
+ const base = resolveCloudBaseUrl({ env: this.env });
128
+ const baseURL = base.endsWith("/api/v1") ? base : `${base}/api/v1`;
129
+ return this.delegate(credential, CLOUD_MODEL, "VENDO_API_KEY", { apiKey: this.env["VENDO_API_KEY"], baseURL }, " via the Cloud gateway");
130
+ }
131
+ this.announce(credential);
132
+ return { mode: "unavailable", credential, message: NO_CREDENTIAL_MESSAGE };
133
+ }
134
+ async doGenerate(callOptions) {
135
+ const resolution = await this.resolve();
136
+ if (resolution.mode === "delegate")
137
+ return resolution.model.doGenerate(callOptions);
138
+ throw new Error(resolution.message);
139
+ }
140
+ async doStream(callOptions) {
141
+ const resolution = await this.resolve();
142
+ if (resolution.mode === "delegate")
143
+ return resolution.model.doStream(callOptions);
144
+ throw new Error(resolution.message);
145
+ }
146
+ }
147
+ /** The env-resolving model (see module doc). */
148
+ export function devModel(options = {}) {
149
+ const controller = new DevModelController(options);
150
+ const model = {
151
+ specificationVersion: "v3",
152
+ provider: "vendo-dev",
153
+ modelId: "dev-env",
154
+ supportedUrls: {},
155
+ doGenerate: (callOptions) => controller.doGenerate(callOptions),
156
+ doStream: (callOptions) => controller.doStream(callOptions),
157
+ };
158
+ return model;
159
+ }
@@ -0,0 +1,36 @@
1
+ /**
2
+ * The dev-mode model-credential resolver (install-dx v1, re-derived 2026-07-18).
3
+ * Runtime model credentials are REAL KEYS ONLY — CLI-session rungs were removed
4
+ * (a coding-agent login helps at init time only, never serves product turns).
5
+ * Detection is PURE and read-only — no network, no writes, no key material in
6
+ * the result (consumers read the env variable themselves). Order (explicit
7
+ * beats implicit):
8
+ *
9
+ * 1. explicit env key (ANTHROPIC / OPENAI / GOOGLE)
10
+ * 2. VENDO_API_KEY (Vendo Cloud starter allowance / gateway)
11
+ * 3. none (honest failure with exact instructions)
12
+ */
13
+ export type EnvKeyProvider = "anthropic" | "openai" | "google";
14
+ export type DevCredential = {
15
+ rung: "env-key";
16
+ provider: EnvKeyProvider;
17
+ envVar: string;
18
+ } | {
19
+ rung: "vendo-cloud";
20
+ } | {
21
+ rung: "none";
22
+ };
23
+ export declare const ENV_KEY_VARS: ReadonlyArray<{
24
+ envVar: string;
25
+ provider: EnvKeyProvider;
26
+ }>;
27
+ export interface ResolveDevCredentialOptions {
28
+ env?: Record<string, string | undefined>;
29
+ }
30
+ /** Detect the best available model credential. `VENDO_DEV_CREDENTIAL`
31
+ * (env-key:anthropic | vendo-cloud | none) pins the rung explicitly — used by
32
+ * E2E rung matrices and escape hatches. Async for seam stability (callers and
33
+ * test seams predate the session-rung removal). */
34
+ export declare function resolveDevCredential(options?: ResolveDevCredentialOptions): Promise<DevCredential>;
35
+ /** One human line for the wizard / doctor / runtime log. */
36
+ export declare function describeDevCredential(credential: DevCredential): string;
@@ -0,0 +1,57 @@
1
+ /**
2
+ * The dev-mode model-credential resolver (install-dx v1, re-derived 2026-07-18).
3
+ * Runtime model credentials are REAL KEYS ONLY — CLI-session rungs were removed
4
+ * (a coding-agent login helps at init time only, never serves product turns).
5
+ * Detection is PURE and read-only — no network, no writes, no key material in
6
+ * the result (consumers read the env variable themselves). Order (explicit
7
+ * beats implicit):
8
+ *
9
+ * 1. explicit env key (ANTHROPIC / OPENAI / GOOGLE)
10
+ * 2. VENDO_API_KEY (Vendo Cloud starter allowance / gateway)
11
+ * 3. none (honest failure with exact instructions)
12
+ */
13
+ export const ENV_KEY_VARS = [
14
+ { envVar: "ANTHROPIC_API_KEY", provider: "anthropic" },
15
+ { envVar: "OPENAI_API_KEY", provider: "openai" },
16
+ { envVar: "GOOGLE_GENERATIVE_AI_API_KEY", provider: "google" },
17
+ ];
18
+ function present(env, name) {
19
+ const value = env[name];
20
+ return typeof value === "string" && value.trim().length > 0;
21
+ }
22
+ /** Detect the best available model credential. `VENDO_DEV_CREDENTIAL`
23
+ * (env-key:anthropic | vendo-cloud | none) pins the rung explicitly — used by
24
+ * E2E rung matrices and escape hatches. Async for seam stability (callers and
25
+ * test seams predate the session-rung removal). */
26
+ export async function resolveDevCredential(options = {}) {
27
+ const env = options.env ?? process.env;
28
+ const pinned = env["VENDO_DEV_CREDENTIAL"]?.trim();
29
+ if (pinned !== undefined && pinned.length > 0) {
30
+ if (pinned === "vendo-cloud" || pinned === "none")
31
+ return { rung: pinned };
32
+ const match = /^env-key:(anthropic|openai|google)$/.exec(pinned);
33
+ if (match !== null) {
34
+ const provider = match[1];
35
+ const envVar = ENV_KEY_VARS.find((entry) => entry.provider === provider).envVar;
36
+ return present(env, envVar) ? { rung: "env-key", provider, envVar } : { rung: "none" };
37
+ }
38
+ }
39
+ for (const { envVar, provider } of ENV_KEY_VARS) {
40
+ if (present(env, envVar))
41
+ return { rung: "env-key", provider, envVar };
42
+ }
43
+ if (present(env, "VENDO_API_KEY"))
44
+ return { rung: "vendo-cloud" };
45
+ return { rung: "none" };
46
+ }
47
+ /** One human line for the wizard / doctor / runtime log. */
48
+ export function describeDevCredential(credential) {
49
+ switch (credential.rung) {
50
+ case "env-key":
51
+ return `explicit ${credential.envVar} (${credential.provider})`;
52
+ case "vendo-cloud":
53
+ return "VENDO_API_KEY (Vendo Cloud)";
54
+ case "none":
55
+ return "no model credential found";
56
+ }
57
+ }
@@ -0,0 +1,52 @@
1
+ import { type EraseReport, type SubjectMergeReport, type VendoStore } from "@vendoai/store";
2
+ export interface HostedStoreOptions {
3
+ apiKey: string;
4
+ /** Defaults to the Vendo console; the composition seam passes VENDO_CLOUD_URL. */
5
+ baseUrl?: string;
6
+ /** Per-request abort timeout, in milliseconds. */
7
+ timeoutMs?: number;
8
+ fetch?: typeof fetch;
9
+ }
10
+ /** The hosted store handle: a plain StoreAdapter over the console wire, plus
11
+ * the erase door (02-store §5 — the console cascades exactly like eraseStore;
12
+ * the host-side TTL sweep is built on this call). `ensureSchema` is a client
13
+ * no-op (the service owns its migrations), `close` holds no local resources,
14
+ * and `raw()` fails loudly — there is no local database handle to hand out. */
15
+ export interface HostedStore extends VendoStore {
16
+ erase: {
17
+ bySubject(subject: string): Promise<EraseReport>;
18
+ byApp(appId: string): Promise<EraseReport>;
19
+ };
20
+ /** The ephemeral-session doors (02-store §4, hosted): registration == touch
21
+ * on every ephemeral request, adoption on sign-in, and the list/claim legs
22
+ * of the HOST-driven TTL sweep — the sweep claims a stale subject, then
23
+ * finishes through `erase.bySubject` (hosted-store one-pager). Millisecond
24
+ * clocks ride the wire so an injected session clock stays authoritative. */
25
+ sessions: {
26
+ register(subject: string, now?: number): Promise<void>;
27
+ adopt(from: string, to: string): Promise<SubjectMergeReport | null>;
28
+ stale(idleMs: number, now?: number): Promise<string[]>;
29
+ claim(subject: string, idleMs: number, now?: number): Promise<boolean>;
30
+ };
31
+ }
32
+ /** vendo-web@7cd0a02 (2026-07-19) deleted the console's ephemeral-session op
33
+ * family (/api/v1/store/sessions/*) per spec — the removed routes answer
34
+ * Next.js's BARE 404 page, no error envelope. Typed so the composition layer
35
+ * (hostedSessionOps in server.ts) can disable the session doors gracefully
36
+ * instead of failing anonymous traffic; an ENVELOPED 404 is a live console
37
+ * answering "not-found" and keeps the loud path, same for every other
38
+ * failure. */
39
+ export declare class HostedSessionDoorsMissingError extends Error {
40
+ constructor();
41
+ }
42
+ /** The Cloud hosted-store adapter — the OSS side of the hosted-store seam
43
+ * (docs/superpowers/specs/2026-07-18-hosted-store-onepager.md): a plain
44
+ * StoreAdapter speaking RPC-over-HTTP to the console's /api/v1/store routes,
45
+ * method for method. Tenant = the key's org, resolved server-side on every
46
+ * call; reserved-collection semantics are enforced server-side by the same
47
+ * engine rules as packages/store's routing. Secrets are excluded by
48
+ * construction: the wire has no secrets surface, and storeSecrets/secretStore
49
+ * keep requiring the local store handle. Cloned from cloudSandbox's shape:
50
+ * behavior comes ONLY from constructor arguments (adapter rule — see
51
+ * selectStore in server.ts); the adapter never reads the environment. */
52
+ export declare function hostedStore(options: HostedStoreOptions): HostedStore;
@@ -0,0 +1,322 @@
1
+ import { vendoRecordSchema, } from "@vendoai/core";
2
+ import { DEDICATED_RECORD_COLLECTIONS, RESERVED_COLLECTIONS, } from "@vendoai/store";
3
+ import { consoleSender, raiseCloudError, toArrayBuffer } from "./cloud-console.js";
4
+ import { deploymentIdentityHeaders } from "./deployment-identity.js";
5
+ /** The console mounts the hosted-store surface here
6
+ * (apps/console/app/api/v1/store/*). */
7
+ const CONSOLE_STORE_PATH = "/api/v1/store";
8
+ /** Store calls are row/blob CRUD, not machine boots: generous enough for a
9
+ * large blob transfer on a slow link, small enough that a hung console
10
+ * request can't wedge a chat turn the way cloudSandbox's 300s budget would. */
11
+ const DEFAULT_TIMEOUT_MS = 30_000;
12
+ /** Console garbage on a 2xx is the SERVICE misbehaving, never the caller's
13
+ * fault — same posture as cloudSandbox's malformed-200 defenses. No VendoError
14
+ * code fits "your storage backend answered nonsense", so this stays a plain
15
+ * Error: the wire layer logs it server-side instead of blaming the client. */
16
+ const invalidResponse = (what) => {
17
+ throw new Error(`Vendo Cloud store returned an ${what} response`);
18
+ };
19
+ /** The console's "unauthorized"/"quota-exhausted" have no VendoError twin;
20
+ * both ride the shared 402/401 → cloud-required mapping. Anything else
21
+ * (unknown codes, 5xx, non-JSON bodies) is carried on a plain Error with the
22
+ * server's code attached — the packages/apps cloud client's posture. */
23
+ const raiseStoreError = (response) => raiseCloudError(response, "store", (code, message) => {
24
+ throw Object.assign(new Error(message), { code: code ?? "unavailable" });
25
+ });
26
+ /** vendo-web@7cd0a02 (2026-07-19) deleted the console's ephemeral-session op
27
+ * family (/api/v1/store/sessions/*) per spec — the removed routes answer
28
+ * Next.js's BARE 404 page, no error envelope. Typed so the composition layer
29
+ * (hostedSessionOps in server.ts) can disable the session doors gracefully
30
+ * instead of failing anonymous traffic; an ENVELOPED 404 is a live console
31
+ * answering "not-found" and keeps the loud path, same for every other
32
+ * failure. */
33
+ export class HostedSessionDoorsMissingError extends Error {
34
+ constructor() {
35
+ super("Vendo Cloud console does not serve /api/v1/store/sessions/* (removed in vendo-web@7cd0a02)");
36
+ this.name = "HostedSessionDoorsMissingError";
37
+ }
38
+ }
39
+ /** The session doors' raise: a bare 404 (no envelope) is the one
40
+ * removed-surface signal; everything else defers to the store mapping. */
41
+ const raiseSessionsError = async (response) => {
42
+ if (response.status === 404) {
43
+ let payload;
44
+ try {
45
+ payload = JSON.parse(await response.clone().text());
46
+ }
47
+ catch {
48
+ payload = undefined;
49
+ }
50
+ const enveloped = typeof payload === "object" && payload !== null && "error" in payload;
51
+ if (!enveloped)
52
+ throw new HostedSessionDoorsMissingError();
53
+ }
54
+ return raiseStoreError(response);
55
+ };
56
+ function parseRecord(value) {
57
+ const parsed = vendoRecordSchema.safeParse(value);
58
+ if (!parsed.success)
59
+ invalidResponse("invalid record");
60
+ return parsed.data;
61
+ }
62
+ function parseNullableRecord(value) {
63
+ if (value === null)
64
+ return null;
65
+ return parseRecord(value);
66
+ }
67
+ /** The Cloud hosted-store adapter — the OSS side of the hosted-store seam
68
+ * (docs/superpowers/specs/2026-07-18-hosted-store-onepager.md): a plain
69
+ * StoreAdapter speaking RPC-over-HTTP to the console's /api/v1/store routes,
70
+ * method for method. Tenant = the key's org, resolved server-side on every
71
+ * call; reserved-collection semantics are enforced server-side by the same
72
+ * engine rules as packages/store's routing. Secrets are excluded by
73
+ * construction: the wire has no secrets surface, and storeSecrets/secretStore
74
+ * keep requiring the local store handle. Cloned from cloudSandbox's shape:
75
+ * behavior comes ONLY from constructor arguments (adapter rule — see
76
+ * selectStore in server.ts); the adapter never reads the environment. */
77
+ export function hostedStore(options) {
78
+ const base = (options.baseUrl ?? "https://console.vendo.run").replace(/\/$/, "");
79
+ const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
80
+ const fetchImpl = options.fetch ?? globalThis.fetch;
81
+ const send = consoleSender({
82
+ base,
83
+ mountPath: CONSOLE_STORE_PATH,
84
+ apiKey: options.apiKey,
85
+ timeoutMs,
86
+ fetchImpl,
87
+ raise: raiseStoreError,
88
+ });
89
+ // Same wire, sessions-only raise — the doors are the one surface the prod
90
+ // console may legitimately not serve (vendo-web@7cd0a02).
91
+ const sendSessions = consoleSender({
92
+ base,
93
+ mountPath: CONSOLE_STORE_PATH,
94
+ apiKey: options.apiKey,
95
+ timeoutMs,
96
+ fetchImpl,
97
+ raise: raiseSessionsError,
98
+ });
99
+ const postJson = (sender) => async (path, body) => {
100
+ const response = await sender(path, {
101
+ method: "POST",
102
+ headers: { "content-type": "application/json" },
103
+ body: JSON.stringify(body),
104
+ });
105
+ try {
106
+ return await response.json();
107
+ }
108
+ catch {
109
+ return {};
110
+ }
111
+ };
112
+ const sendJson = postJson(send);
113
+ const sendSessionsJson = postJson(sendSessions);
114
+ const records = (collection) => {
115
+ const prefix = `/records/${encodeURIComponent(collection)}`;
116
+ const store = {
117
+ async get(id) {
118
+ const payload = await sendJson(`${prefix}/get`, { id });
119
+ if (payload.record === undefined)
120
+ invalidResponse("invalid record");
121
+ return parseNullableRecord(payload.record);
122
+ },
123
+ async put(record) {
124
+ const payload = await sendJson(`${prefix}/put`, { record });
125
+ if (payload.record === undefined || payload.record === null)
126
+ invalidResponse("invalid record");
127
+ return parseRecord(payload.record);
128
+ },
129
+ async delete(id) {
130
+ await sendJson(`${prefix}/delete`, { id });
131
+ },
132
+ async list(query) {
133
+ const payload = await sendJson(`${prefix}/list`, { query: query ?? {} });
134
+ if (!Array.isArray(payload.records))
135
+ invalidResponse("invalid list");
136
+ return {
137
+ records: payload.records.map(parseRecord),
138
+ ...(typeof payload.cursor === "string" ? { cursor: payload.cursor } : {}),
139
+ };
140
+ },
141
+ };
142
+ // Capability mirror of the store engine's routing (02-store §2): routed
143
+ // reserved collections expose no claim; atomic rides generic collections
144
+ // and vendo_threads' revision counter only. Mirroring the shape here keeps
145
+ // feature detection (`records.atomic !== undefined`) identical on both
146
+ // sides of the wire.
147
+ const reserved = RESERVED_COLLECTIONS.includes(collection);
148
+ const dedicated = DEDICATED_RECORD_COLLECTIONS.includes(collection);
149
+ if (!reserved) {
150
+ store.claim = async (expected, replacement) => {
151
+ const payload = await sendJson(`${prefix}/claim`, {
152
+ expected,
153
+ ...(replacement === undefined ? {} : { replacement }),
154
+ });
155
+ if (typeof payload.claimed !== "boolean")
156
+ invalidResponse("invalid claim");
157
+ return payload.claimed;
158
+ };
159
+ }
160
+ if ((!reserved && !dedicated) || collection === "vendo_threads") {
161
+ store.atomic = {
162
+ async insertIfAbsent(record) {
163
+ const payload = await sendJson(`${prefix}/atomic/insert-if-absent`, { record });
164
+ if (payload.record === undefined)
165
+ invalidResponse("invalid record");
166
+ return parseNullableRecord(payload.record);
167
+ },
168
+ async compareAndSwap(record, expectedRevision) {
169
+ const payload = await sendJson(`${prefix}/atomic/compare-and-swap`, {
170
+ record,
171
+ expectedRevision,
172
+ });
173
+ if (payload.record === undefined)
174
+ invalidResponse("invalid record");
175
+ return parseNullableRecord(payload.record);
176
+ },
177
+ };
178
+ }
179
+ return store;
180
+ };
181
+ const blobs = (namespace) => {
182
+ const prefix = `/blobs/${encodeURIComponent(namespace)}`;
183
+ // Blob keys are paths ("images/a.png"); encode per segment so the key's
184
+ // own separators survive as URL structure while each segment stays safe.
185
+ const keyPath = (key) => `${prefix}/${key.split("/").map(encodeURIComponent).join("/")}`;
186
+ return {
187
+ async put(key, bytes, meta) {
188
+ await send(keyPath(key), {
189
+ method: "PUT",
190
+ ...(meta?.contentType === undefined ? {} : { headers: { "content-type": meta.contentType } }),
191
+ body: toArrayBuffer(bytes),
192
+ });
193
+ },
194
+ async get(key) {
195
+ const response = await fetchImpl(`${base}${CONSOLE_STORE_PATH}${keyPath(key)}`, {
196
+ headers: {
197
+ authorization: `Bearer ${options.apiKey}`,
198
+ ...(await deploymentIdentityHeaders()),
199
+ },
200
+ signal: AbortSignal.timeout(timeoutMs),
201
+ });
202
+ // A missing blob is null at the seam (01-core §12) — but ONLY the
203
+ // console's enveloped not-found says "missing blob". A bare 404 (no
204
+ // envelope) is some other server answering — a misdeployed base URL
205
+ // must fail loudly, not read as an empty blob store forever.
206
+ if (response.status === 404) {
207
+ let payload;
208
+ try {
209
+ payload = JSON.parse(await response.text());
210
+ }
211
+ catch {
212
+ payload = undefined;
213
+ }
214
+ const code = typeof payload === "object" && payload !== null && "error" in payload
215
+ ? payload.error?.code
216
+ : undefined;
217
+ if (code === "not-found")
218
+ return null;
219
+ throw new Error("Vendo Cloud store request failed with a bare 404 (no error envelope) — is the base URL a Vendo console?");
220
+ }
221
+ if (!response.ok)
222
+ await raiseStoreError(response);
223
+ const contentType = response.headers.get("content-type");
224
+ return {
225
+ bytes: new Uint8Array(await response.arrayBuffer()),
226
+ ...(contentType === null ? {} : { contentType }),
227
+ };
228
+ },
229
+ async delete(key) {
230
+ await send(keyPath(key), { method: "DELETE" });
231
+ },
232
+ async list(prefixFilter) {
233
+ const query = prefixFilter === undefined || prefixFilter === ""
234
+ ? ""
235
+ : `?prefix=${encodeURIComponent(prefixFilter)}`;
236
+ const response = await send(`${prefix}${query}`);
237
+ let payload;
238
+ try {
239
+ payload = await response.json();
240
+ }
241
+ catch {
242
+ payload = {};
243
+ }
244
+ if (!Array.isArray(payload.keys) || !payload.keys.every((key) => typeof key === "string")) {
245
+ invalidResponse("invalid blob list");
246
+ }
247
+ return payload.keys;
248
+ },
249
+ };
250
+ };
251
+ const parseReport = (payload) => {
252
+ const report = typeof payload === "object" && payload !== null && "report" in payload
253
+ ? payload.report
254
+ : undefined;
255
+ if (typeof report !== "object" || report === null || Array.isArray(report)
256
+ || !Object.values(report).every((count) => typeof count === "number")) {
257
+ invalidResponse("invalid erase");
258
+ }
259
+ return report;
260
+ };
261
+ const parseMergeReport = (payload) => {
262
+ const report = typeof payload === "object" && payload !== null && "report" in payload
263
+ ? payload.report
264
+ : undefined;
265
+ if (report === null)
266
+ return null;
267
+ if (typeof report !== "object" || report === undefined || Array.isArray(report)
268
+ || !Object.values(report).every((count) => typeof count === "number")) {
269
+ invalidResponse("invalid adopt");
270
+ }
271
+ return report;
272
+ };
273
+ return {
274
+ records,
275
+ blobs,
276
+ sessions: {
277
+ async register(subject, now) {
278
+ await sendSessionsJson("/sessions/register", { subject, ...(now === undefined ? {} : { now }) });
279
+ },
280
+ async adopt(from, to) {
281
+ return parseMergeReport(await sendSessionsJson("/sessions/adopt", { from, to }));
282
+ },
283
+ async stale(idleMs, now) {
284
+ const payload = await sendSessionsJson("/sessions/stale", {
285
+ idleMs,
286
+ ...(now === undefined ? {} : { now }),
287
+ });
288
+ if (!Array.isArray(payload.subjects) || !payload.subjects.every((subject) => typeof subject === "string")) {
289
+ invalidResponse("invalid stale");
290
+ }
291
+ return payload.subjects;
292
+ },
293
+ async claim(subject, idleMs, now) {
294
+ const payload = await sendSessionsJson("/sessions/claim", {
295
+ subject,
296
+ idleMs,
297
+ ...(now === undefined ? {} : { now }),
298
+ });
299
+ if (typeof payload.claimed !== "boolean")
300
+ invalidResponse("invalid claim");
301
+ return payload.claimed;
302
+ },
303
+ },
304
+ // 02-store §5 — the subject/app cascade runs server-side with eraseStore
305
+ // parity; the host-side ephemeral TTL sweep is built on bySubject.
306
+ erase: {
307
+ async bySubject(subject) {
308
+ return parseReport(await sendJson("/erase", { subject }));
309
+ },
310
+ async byApp(appId) {
311
+ return parseReport(await sendJson("/erase", { appId }));
312
+ },
313
+ },
314
+ // The service owns its migrations; there is nothing to migrate from here.
315
+ async ensureSchema() { },
316
+ // No local pool, no PGlite handle — nothing to release.
317
+ async close() { },
318
+ raw() {
319
+ throw new Error("[vendo] hostedStore has no local database handle — raw() requires a local createStore store");
320
+ },
321
+ };
322
+ }
@@ -0,0 +1,13 @@
1
+ /** @vendoai/vendo — root contract types (09-vendo §1). */
2
+ export type * from "@vendoai/core";
3
+ export type { VendoStore } from "@vendoai/store";
4
+ export type { Thread, ThreadSummary, VendoAgent } from "@vendoai/agent";
5
+ export type { ActionsRegistry, Connector, ConnectorAccount, ConnectorConnections, ExtractedTool, SyncReport, } from "@vendoai/actions";
6
+ export type { ConnectionsService, InitiatedConnection, InitiateOptions } from "./connections.js";
7
+ export type { Judge, PolicyConfig, PolicyFile, PolicyFn, PolicyRule, VendoGuard, } from "@vendoai/guard";
8
+ export type { AppsRuntime, EditResult, InClientApproval, InClientVerdict, OpenSurface, PinDrift, PinRebaseResult, SandboxAdapter, SandboxMachine, ShipDiff, VersionEntry, } from "@vendoai/apps";
9
+ export type { AutomationsEngine, RunPlan, RunRecord, RunStatus, } from "@vendoai/automations";
10
+ export type { VendoClient, VendoClientConfig } from "@vendoai/ui";
11
+ export type { HostOAuthAdapter } from "@vendoai/mcp";
12
+ export { remixable, type RemixableRegistration, type RemixableReportOptions } from "./remixable.js";
13
+ export type { ByoApprovalResolution } from "./byo-approvals.js";
package/dist/index.js ADDED
@@ -0,0 +1 @@
1
+ export { remixable } from "./remixable.js";