@tenderprompt/accounts 0.2.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 (90) hide show
  1. package/README.md +84 -0
  2. package/dist/api.d.ts +27 -0
  3. package/dist/api.d.ts.map +1 -0
  4. package/dist/api.js +73 -0
  5. package/dist/api.js.map +1 -0
  6. package/dist/artifact.d.ts +8 -0
  7. package/dist/artifact.d.ts.map +1 -0
  8. package/dist/artifact.js +205 -0
  9. package/dist/artifact.js.map +1 -0
  10. package/dist/cli.d.ts +3 -0
  11. package/dist/cli.d.ts.map +1 -0
  12. package/dist/cli.js +4 -0
  13. package/dist/cli.js.map +1 -0
  14. package/dist/contracts.d.ts +96 -0
  15. package/dist/contracts.d.ts.map +1 -0
  16. package/dist/contracts.js +5 -0
  17. package/dist/contracts.js.map +1 -0
  18. package/dist/errors.d.ts +10 -0
  19. package/dist/errors.d.ts.map +1 -0
  20. package/dist/errors.js +19 -0
  21. package/dist/errors.js.map +1 -0
  22. package/dist/files.d.ts +21 -0
  23. package/dist/files.d.ts.map +1 -0
  24. package/dist/files.js +263 -0
  25. package/dist/files.js.map +1 -0
  26. package/dist/index.d.ts +4 -0
  27. package/dist/index.d.ts.map +1 -0
  28. package/dist/index.js +4 -0
  29. package/dist/index.js.map +1 -0
  30. package/dist/main.d.ts +15 -0
  31. package/dist/main.d.ts.map +1 -0
  32. package/dist/main.js +565 -0
  33. package/dist/main.js.map +1 -0
  34. package/dist/processes.d.ts +12 -0
  35. package/dist/processes.d.ts.map +1 -0
  36. package/dist/processes.js +44 -0
  37. package/dist/processes.js.map +1 -0
  38. package/dist/starter.d.ts +20 -0
  39. package/dist/starter.d.ts.map +1 -0
  40. package/dist/starter.js +159 -0
  41. package/dist/starter.js.map +1 -0
  42. package/package.json +32 -0
  43. package/skill/tender-accounts/SKILL.md +111 -0
  44. package/templates/shopify-customer-account/AGENTS.md +14 -0
  45. package/templates/shopify-customer-account/README.md +44 -0
  46. package/templates/shopify-customer-account/apps/gateway/AGENTS.md +9 -0
  47. package/templates/shopify-customer-account/apps/gateway/README.md +9 -0
  48. package/templates/shopify-customer-account/apps/gateway/database-schema.json +49 -0
  49. package/templates/shopify-customer-account/apps/gateway/dot.dev.vars.example +11 -0
  50. package/templates/shopify-customer-account/apps/gateway/migrations/0001_customer_auth.sql +76 -0
  51. package/templates/shopify-customer-account/apps/gateway/package.json +25 -0
  52. package/templates/shopify-customer-account/apps/gateway/scripts/build-worker.mjs +15 -0
  53. package/templates/shopify-customer-account/apps/gateway/scripts/pack-tender-artifact.mjs +84 -0
  54. package/templates/shopify-customer-account/apps/gateway/src/platform/customer-auth/hostname-policy.ts +66 -0
  55. package/templates/shopify-customer-account/apps/gateway/src/platform/customer-auth/schema.ts +90 -0
  56. package/templates/shopify-customer-account/apps/gateway/src/platform/customer-auth/session-store.ts +864 -0
  57. package/templates/shopify-customer-account/apps/gateway/src/platform/customer-auth/shopify-route-adapter.ts +1157 -0
  58. package/templates/shopify-customer-account/apps/gateway/src/platform/runtime-hostname.ts +46 -0
  59. package/templates/shopify-customer-account/apps/gateway/src/portal-service.test.ts +18 -0
  60. package/templates/shopify-customer-account/apps/gateway/src/portal-service.ts +51 -0
  61. package/templates/shopify-customer-account/apps/gateway/src/runtime-config.test.ts +30 -0
  62. package/templates/shopify-customer-account/apps/gateway/src/runtime-config.ts +56 -0
  63. package/templates/shopify-customer-account/apps/gateway/src/worker.ts +85 -0
  64. package/templates/shopify-customer-account/apps/gateway/tender-accounts.json +10 -0
  65. package/templates/shopify-customer-account/apps/gateway/tender-app.config.json +40 -0
  66. package/templates/shopify-customer-account/apps/gateway/tsconfig.json +13 -0
  67. package/templates/shopify-customer-account/apps/gateway/vitest.config.ts +8 -0
  68. package/templates/shopify-customer-account/apps/gateway/worker-configuration.d.ts +26 -0
  69. package/templates/shopify-customer-account/apps/gateway/wrangler.jsonc +42 -0
  70. package/templates/shopify-customer-account/apps/portal/AGENTS.md +9 -0
  71. package/templates/shopify-customer-account/apps/portal/README.md +5 -0
  72. package/templates/shopify-customer-account/apps/portal/index.html +13 -0
  73. package/templates/shopify-customer-account/apps/portal/package.json +33 -0
  74. package/templates/shopify-customer-account/apps/portal/scripts/build-worker.mjs +11 -0
  75. package/templates/shopify-customer-account/apps/portal/scripts/pack-tender-artifact.mjs +63 -0
  76. package/templates/shopify-customer-account/apps/portal/scripts/portal-worker.mjs +84 -0
  77. package/templates/shopify-customer-account/apps/portal/scripts/portal-worker.test.mjs +14 -0
  78. package/templates/shopify-customer-account/apps/portal/src/App.tsx +111 -0
  79. package/templates/shopify-customer-account/apps/portal/src/main.tsx +10 -0
  80. package/templates/shopify-customer-account/apps/portal/src/styles.css +54 -0
  81. package/templates/shopify-customer-account/apps/portal/tender-accounts.json +10 -0
  82. package/templates/shopify-customer-account/apps/portal/tender-app.config.json +19 -0
  83. package/templates/shopify-customer-account/apps/portal/tsconfig.json +14 -0
  84. package/templates/shopify-customer-account/apps/portal/vite.config.ts +7 -0
  85. package/templates/shopify-customer-account/apps/portal/worker-configuration.d.ts +11 -0
  86. package/templates/shopify-customer-account/apps/portal/wrangler.jsonc +22 -0
  87. package/templates/shopify-customer-account/dot.dev.vars.example +3 -0
  88. package/templates/shopify-customer-account/dot.gitignore +9 -0
  89. package/templates/shopify-customer-account/package.json +22 -0
  90. package/templates/shopify-customer-account/scripts/artifact-contract.mjs +109 -0
@@ -0,0 +1,44 @@
1
+ import { spawn } from "node:child_process";
2
+ import { CliError } from "./errors.js";
3
+ export async function runProjectCommand(input) {
4
+ await new Promise((resolvePromise, reject) => {
5
+ const child = spawn(input.command, {
6
+ cwd: input.cwd,
7
+ env: input.environment,
8
+ shell: true,
9
+ stdio: input.machineOutput ? ["inherit", "pipe", "pipe"] : "inherit",
10
+ });
11
+ if (input.machineOutput) {
12
+ child.stdout?.on("data", (chunk) => process.stderr.write(chunk));
13
+ child.stderr?.on("data", (chunk) => process.stderr.write(chunk));
14
+ }
15
+ child.on("error", reject);
16
+ child.on("exit", (code, signal) => {
17
+ if (code === 0)
18
+ resolvePromise();
19
+ else
20
+ reject(new CliError("project_command_failed", `Project command failed${signal ? ` with signal ${signal}` : ` with exit code ${code ?? "unknown"}`}: ${input.command}`));
21
+ });
22
+ });
23
+ }
24
+ export async function captureCommand(command, args, cwd) {
25
+ return await new Promise((resolvePromise, reject) => {
26
+ const child = spawn(command, args, { cwd, stdio: ["ignore", "pipe", "pipe"] });
27
+ const stdout = [];
28
+ const stderr = [];
29
+ child.stdout.on("data", (chunk) => stdout.push(Buffer.from(chunk)));
30
+ child.stderr.on("data", (chunk) => stderr.push(Buffer.from(chunk)));
31
+ child.on("error", reject);
32
+ child.on("exit", (code) => {
33
+ const result = {
34
+ stdout: Buffer.concat(stdout).toString("utf8").trim(),
35
+ stderr: Buffer.concat(stderr).toString("utf8").trim(),
36
+ };
37
+ if (code === 0)
38
+ resolvePromise(result);
39
+ else
40
+ reject(new CliError("project_git_unavailable", result.stderr || `${command} exited with code ${code ?? "unknown"}.`));
41
+ });
42
+ });
43
+ }
44
+ //# sourceMappingURL=processes.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"processes.js","sourceRoot":"","sources":["../src/processes.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,20 @@
1
+ export interface StarterInput {
2
+ cwd: string;
3
+ directory?: string;
4
+ name: string;
5
+ slug?: string;
6
+ template?: string;
7
+ dryRun: boolean;
8
+ }
9
+ export interface StarterResult {
10
+ status: "planned" | "created";
11
+ template: string;
12
+ name: string;
13
+ slug: string;
14
+ directory: string;
15
+ files: string[];
16
+ wroteFiles: boolean;
17
+ nextSteps: string[];
18
+ }
19
+ export declare function initializeStarter(input: StarterInput): Promise<StarterResult>;
20
+ //# sourceMappingURL=starter.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"starter.d.ts","sourceRoot":"","sources":["../src/starter.ts"],"names":[],"mappings":"AAUA,MAAM,WAAW,YAAY;IAC3B,GAAG,EAAE,MAAM,CAAC;IACZ,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,OAAO,CAAC;CACjB;AAED,MAAM,WAAW,aAAa;IAC5B,MAAM,EAAE,SAAS,GAAG,SAAS,CAAC;IAC9B,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,UAAU,EAAE,OAAO,CAAC;IACpB,SAAS,EAAE,MAAM,EAAE,CAAC;CACrB;AAED,wBAAsB,iBAAiB,CAAC,KAAK,EAAE,YAAY,GAAG,OAAO,CAAC,aAAa,CAAC,CAwDnF"}
@@ -0,0 +1,159 @@
1
+ import { fileURLToPath } from "node:url";
2
+ import { lstat, mkdir, readFile, readdir, writeFile } from "node:fs/promises";
3
+ import { dirname, isAbsolute, relative, resolve, sep } from "node:path";
4
+ import { CliError } from "./errors.js";
5
+ const DEFAULT_TEMPLATE = "shopify-customer-account";
6
+ const TEMPLATE_FILE_MAX_BYTES = 2 * 1024 * 1024;
7
+ const STARTER_NAME = /^[A-Za-z0-9][A-Za-z0-9 _-]{1,63}$/u;
8
+ const STARTER_SLUG = /^[a-z0-9][a-z0-9-]{1,47}$/u;
9
+ export async function initializeStarter(input) {
10
+ const template = input.template ?? DEFAULT_TEMPLATE;
11
+ if (template !== DEFAULT_TEMPLATE) {
12
+ throw new CliError("starter_template_unknown", `Unknown starter template: ${template}.`, `tender-accounts init --name "My account" --template ${DEFAULT_TEMPLATE}`);
13
+ }
14
+ const name = validStarterName(input.name);
15
+ const slug = validStarterSlug(input.slug ?? slugify(name));
16
+ const target = await safeStarterTarget(input.cwd, input.directory ?? ".");
17
+ await assertEmptyStarterTarget(target);
18
+ const templateRoot = fileURLToPath(new URL(`../templates/${template}/`, import.meta.url));
19
+ const templateFiles = await walkTemplate(templateRoot);
20
+ const files = templateFiles.map((path) => renderedRelativePath(relative(templateRoot, path)))
21
+ .concat(".agents/skills/tender-accounts/SKILL.md")
22
+ .sort((left, right) => left.localeCompare(right));
23
+ if (!input.dryRun) {
24
+ await mkdir(target, { recursive: true });
25
+ for (const source of templateFiles) {
26
+ const relativeSource = relative(templateRoot, source);
27
+ const destination = resolve(target, renderedRelativePath(relativeSource));
28
+ assertWithin(target, destination);
29
+ const entry = await lstat(source);
30
+ if (!entry.isFile() || entry.isSymbolicLink() || entry.size > TEMPLATE_FILE_MAX_BYTES) {
31
+ throw new CliError("starter_template_unsafe", `Starter template contains an unsafe file: ${relativeSource}.`);
32
+ }
33
+ const contents = new TextDecoder("utf-8", { fatal: true }).decode(await readFile(source));
34
+ await mkdir(dirname(destination), { recursive: true });
35
+ await writeFile(destination, renderTemplate(contents, { name, slug }), { encoding: "utf8", flag: "wx" });
36
+ }
37
+ const skillDestination = resolve(target, ".agents/skills/tender-accounts/SKILL.md");
38
+ await mkdir(dirname(skillDestination), { recursive: true });
39
+ await writeFile(skillDestination, await readFile(skillPath(), "utf8"), { encoding: "utf8", flag: "wx" });
40
+ }
41
+ const displayDirectory = relative(resolve(input.cwd), target) || ".";
42
+ const prefix = displayDirectory === "." ? "" : `cd ${shellSafePath(displayDirectory)} && `;
43
+ return {
44
+ status: input.dryRun ? "planned" : "created",
45
+ template,
46
+ name,
47
+ slug,
48
+ directory: target,
49
+ files,
50
+ wroteFiles: !input.dryRun,
51
+ nextSteps: [
52
+ `${prefix}npm install`,
53
+ `${prefix}git init && git add . && git commit -m "Initialize ${name}"`,
54
+ `${prefix}npm run dev`,
55
+ `${prefix}npx tender-accounts doctor --cwd apps/portal --json`,
56
+ ],
57
+ };
58
+ }
59
+ function validStarterName(value) {
60
+ const normalized = value.trim().replace(/\s+/gu, " ");
61
+ if (!STARTER_NAME.test(normalized)) {
62
+ throw new CliError("starter_name_invalid", "--name must be 2-64 letters, numbers, spaces, hyphens, or underscores.", "tender-accounts init --name \"My customer account\" --directory ./my-account");
63
+ }
64
+ return normalized;
65
+ }
66
+ function validStarterSlug(value) {
67
+ const normalized = value.trim().toLowerCase();
68
+ if (!STARTER_SLUG.test(normalized) || normalized.endsWith("-")) {
69
+ throw new CliError("starter_slug_invalid", "--slug must be 2-48 lowercase letters, numbers, or hyphens and cannot end with a hyphen.", "tender-accounts init --name \"My account\" --slug my-account");
70
+ }
71
+ return normalized;
72
+ }
73
+ function slugify(value) {
74
+ return value.toLowerCase().replace(/[^a-z0-9]+/gu, "-").replace(/^-+|-+$/gu, "").slice(0, 48).replace(/-+$/u, "");
75
+ }
76
+ async function safeStarterTarget(cwd, value) {
77
+ if (isAbsolute(value) || value.includes("\\") || value.includes("%") || /[\r\n\0]/u.test(value)) {
78
+ throw new CliError("starter_directory_unsafe", "--directory must be a safe path relative to the current directory.");
79
+ }
80
+ const segments = value === "." ? [] : value.split("/");
81
+ if (segments.some((segment) => !segment || segment === "." || segment === ".." || !/^[A-Za-z0-9._-]+$/u.test(segment))) {
82
+ throw new CliError("starter_directory_unsafe", "--directory must be a safe path relative to the current directory.");
83
+ }
84
+ const root = resolve(cwd);
85
+ const target = resolve(root, ...segments);
86
+ if (target !== root)
87
+ assertWithin(root, target);
88
+ let cursor = root;
89
+ for (const segment of segments) {
90
+ cursor = resolve(cursor, segment);
91
+ const entry = await optionalLstat(cursor);
92
+ if (entry?.isSymbolicLink()) {
93
+ throw new CliError("starter_directory_unsafe", `Starter path cannot traverse a symbolic link: ${cursor}.`);
94
+ }
95
+ if (entry && !entry.isDirectory()) {
96
+ throw new CliError("starter_directory_unsafe", `Starter path is not a directory: ${cursor}.`);
97
+ }
98
+ }
99
+ return target;
100
+ }
101
+ async function assertEmptyStarterTarget(target) {
102
+ const entry = await optionalLstat(target);
103
+ if (!entry)
104
+ return;
105
+ if (!entry.isDirectory() || entry.isSymbolicLink()) {
106
+ throw new CliError("starter_target_unsafe", "The starter target must be a regular directory.");
107
+ }
108
+ const existing = (await readdir(target)).filter((name) => name !== ".git");
109
+ if (existing.length > 0) {
110
+ throw new CliError("starter_target_not_empty", `The starter target contains existing files: ${existing.slice(0, 5).join(", ")}.`, "Choose a new empty --directory. Existing applications should adopt tender-accounts.json instead of using init.");
111
+ }
112
+ }
113
+ async function walkTemplate(root) {
114
+ const entries = await readdir(root, { withFileTypes: true });
115
+ const files = [];
116
+ for (const entry of entries.sort((left, right) => left.name.localeCompare(right.name))) {
117
+ const path = resolve(root, entry.name);
118
+ if (entry.isSymbolicLink())
119
+ throw new CliError("starter_template_unsafe", `Starter template contains a symbolic link: ${path}.`);
120
+ if (entry.isDirectory())
121
+ files.push(...await walkTemplate(path));
122
+ else if (entry.isFile())
123
+ files.push(path);
124
+ else
125
+ throw new CliError("starter_template_unsafe", `Starter template contains an unsupported entry: ${path}.`);
126
+ }
127
+ return files;
128
+ }
129
+ function renderedRelativePath(value) {
130
+ return value.split(sep).map((segment) => segment.startsWith("dot.") ? `.${segment.slice(4)}` : segment).join("/");
131
+ }
132
+ function renderTemplate(value, input) {
133
+ return value
134
+ .replaceAll("__APP_NAME__", input.name)
135
+ .replaceAll("__APP_SLUG__", input.slug);
136
+ }
137
+ function assertWithin(root, target) {
138
+ const suffix = relative(resolve(root), resolve(target));
139
+ if (!suffix || suffix === ".." || suffix.startsWith(`..${sep}`) || isAbsolute(suffix)) {
140
+ throw new CliError("starter_directory_unsafe", "Starter files must stay inside the selected directory.");
141
+ }
142
+ }
143
+ async function optionalLstat(path) {
144
+ try {
145
+ return await lstat(path);
146
+ }
147
+ catch (error) {
148
+ if (error instanceof Error && "code" in error && error.code === "ENOENT")
149
+ return null;
150
+ throw error;
151
+ }
152
+ }
153
+ function skillPath() {
154
+ return fileURLToPath(new URL("../skill/tender-accounts/SKILL.md", import.meta.url));
155
+ }
156
+ function shellSafePath(value) {
157
+ return /^[A-Za-z0-9._/-]+$/u.test(value) ? value : JSON.stringify(value);
158
+ }
159
+ //# sourceMappingURL=starter.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"starter.js","sourceRoot":"","sources":["../src/starter.ts"],"names":[],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,32 @@
1
+ {
2
+ "name": "@tenderprompt/accounts",
3
+ "version": "0.2.0",
4
+ "description": "Project-scoped developer CLI for Tender Accounts applications",
5
+ "type": "module",
6
+ "license": "UNLICENSED",
7
+ "bin": {
8
+ "tender-accounts": "./dist/cli.js"
9
+ },
10
+ "files": [
11
+ "dist",
12
+ "skill",
13
+ "templates",
14
+ "README.md"
15
+ ],
16
+ "engines": {
17
+ "node": ">=22.0.0"
18
+ },
19
+ "scripts": {
20
+ "build": "tsc -p tsconfig.build.json",
21
+ "check": "npm run typecheck && npm run test && npm run build",
22
+ "sync:starter": "node ./scripts/sync-starter-template.mjs",
23
+ "test": "vitest run",
24
+ "typecheck": "tsc --noEmit -p tsconfig.json",
25
+ "prepack": "npm run sync:starter && npm run check"
26
+ },
27
+ "devDependencies": {
28
+ "@types/node": "^26.2.0",
29
+ "typescript": "^7.0.2",
30
+ "vitest": "^4.1.11"
31
+ }
32
+ }
@@ -0,0 +1,111 @@
1
+ ---
2
+ name: tender-accounts
3
+ description: Create, build, validate, and preview merchant-owned Tender Accounts applications with the project-scoped CLI. Use when starting a new customer account, changing a repository that contains tender-accounts.json, implementing a customer-account feature, diagnosing a Tender Accounts preview, or preparing an exact preview for review.
4
+ ---
5
+
6
+ # Tender Accounts application delivery
7
+
8
+ Use the repository-installed `tender-accounts` CLI as the only platform interface. Let the merchant repository own its framework, build commands, Worker code, and commerce integrations.
9
+
10
+ ## Start a new application only when requested
11
+
12
+ When the user asked for a new Shopify customer-account repository and the target is empty, inspect the exact plan and then create the optional starter:
13
+
14
+ ```sh
15
+ npx tender-accounts init --name "Merchant customer account" --directory ./merchant-account --dry-run --json
16
+ npx tender-accounts init --name "Merchant customer account" --directory ./merchant-account --json
17
+ ```
18
+
19
+ Never run `init` inside an existing application or use it to replace a merchant's architecture. It creates a stable gateway and independently editable `apps/portal`. UI-only tasks stay in the portal; authentication, sessions, D1, confidential bindings, and protected APIs stay in the gateway. The command deliberately does not install dependencies, initialize Git, link projects, or deploy.
20
+
21
+ ## Start every task with scope discovery
22
+
23
+ 1. Read the closest `AGENTS.md` files and `tender-accounts.json`. In a generated two-service repository, identify whether the request belongs to `apps/portal` or `apps/gateway` before editing.
24
+ 2. Run:
25
+
26
+ ```sh
27
+ npx tender-accounts doctor --json
28
+ ```
29
+
30
+ 3. Stop if configuration, authentication, linking, or project access fails. Report the exact error code and remediation. Never bypass the CLI with direct API calls.
31
+ 4. Confirm the linked project is the application the user requested. Use `--cwd apps/portal` or `--cwd apps/gateway` from a generated repository. Do not edit a gateway, service, or portal outside the current repository and credential scope.
32
+
33
+ If the app has not been linked, ask the merchant administrator for a short-lived credential scoped to this project. Put it only in ignored `.dev.vars`, then run:
34
+
35
+ ```sh
36
+ npx tender-accounts link --json
37
+ ```
38
+
39
+ When one credential grants multiple projects, select the intended project once with `--project`.
40
+
41
+ ## Implement and validate
42
+
43
+ 1. Inspect the existing application and tests before changing code.
44
+ 2. Use the repository's own development workflow. `npx tender-accounts dev` delegates to the committed `commands.dev` and never performs a Cloudflare deployment.
45
+ 3. Make the smallest source change that satisfies the request.
46
+ 4. Run the deterministic repository checks:
47
+
48
+ ```sh
49
+ npx tender-accounts check --json
50
+ ```
51
+
52
+ 5. Validate the complete portable artifact without uploading it:
53
+
54
+ ```sh
55
+ npx tender-accounts preview --dry-run --json
56
+ ```
57
+
58
+ Do not weaken checks, remove declared bindings, or place runtime values in the artifact to make validation pass.
59
+
60
+ ## Create an exact preview
61
+
62
+ After local validation succeeds, run:
63
+
64
+ ```sh
65
+ npx tender-accounts preview --json
66
+ ```
67
+
68
+ Treat stdout as one machine-readable JSON object. Preserve and report:
69
+
70
+ - `previewUrl`;
71
+ - `deliveryId`;
72
+ - `releaseId`;
73
+ - `bundleDigest`;
74
+ - validation performed before delivery.
75
+
76
+ The CLI may label an uncommitted local preview with a `local-...` source revision. The immutable bundle digest remains the content identity. Do not claim the change is committed or pushed unless separately verified.
77
+
78
+ ## Diagnose a delivery
79
+
80
+ Read exact workflow state:
81
+
82
+ ```sh
83
+ npx tender-accounts delivery status --delivery <delivery-id> --json
84
+ ```
85
+
86
+ Retry only a requested or failed preview workflow:
87
+
88
+ ```sh
89
+ npx tender-accounts delivery retry --delivery <delivery-id> --json
90
+ ```
91
+
92
+ Mint a new browser session for an already-succeeded delivery without rebuilding:
93
+
94
+ ```sh
95
+ npx tender-accounts delivery preview --delivery <delivery-id> --json
96
+ ```
97
+
98
+ Use the error code, request ID, and suggested command from the CLI. Do not retry with altered IDs, another project, or raw HTTP requests.
99
+
100
+ ## Hard boundaries
101
+
102
+ - Never run `wrangler deploy` or use Cloudflare deployment credentials.
103
+ - Never call Tender Accounts internal APIs with `curl` or custom scripts.
104
+ - Never print, read back, commit, or copy `.dev.vars` values.
105
+ - Never put Shopify, subscription-provider, or other merchant credentials in source, build output, logs, or screenshots.
106
+ - Never change `.tender/link.json` by hand; use `tender-accounts link`.
107
+ - Never publish production from an agent credential. The CLI intentionally has no publish command.
108
+ - Never expand project scope. Ask a merchant administrator for a different credential when the requested app is outside the current grant.
109
+ - Never treat a successful preview as a production release, merge, commit, or push.
110
+
111
+ Production moves only through the merchant's protected default-branch policy or an authenticated merchant administrator in the Tender Accounts console.
@@ -0,0 +1,14 @@
1
+ # __APP_NAME__ application boundary
2
+
3
+ This is a merchant-owned Tender Accounts application. The gateway and portal are independently deployable projects.
4
+
5
+ ## Working rules
6
+
7
+ - For portal-only requests, change `apps/portal` and do not edit `apps/gateway`.
8
+ - Keep Shopify authentication, confidential bindings, sessions, and protected API routes in `apps/gateway`.
9
+ - Keep `apps/portal` free of Shopify customer tokens, session storage, D1 access, and provider secrets.
10
+ - Use `npx tender-accounts` for project linking, validation, and previews. Never call Tender internal APIs directly.
11
+ - Never run `wrangler deploy`; production and preview delivery are controlled by Tender Accounts.
12
+ - Never commit `.dev.vars`, `.tender/`, credentials, customer data, or provider responses.
13
+ - Shopify and selected commerce providers remain systems of record. Do not duplicate their durable business state without an explicit design.
14
+ - Run the closest app checks and `npx tender-accounts preview --dry-run --json` before creating a preview.
@@ -0,0 +1,44 @@
1
+ # __APP_NAME__
2
+
3
+ Merchant-owned Shopify customer account built as two independently deployable Tender Accounts applications:
4
+
5
+ ```text
6
+ customer hostname
7
+ |
8
+ v
9
+ apps/gateway -- PORTAL_UI service binding --> apps/portal
10
+ Shopify auth React UI
11
+ HttpOnly sessions no customer tokens
12
+ application D1 no confidential bindings
13
+ /auth/* and /api/*
14
+ ```
15
+
16
+ The gateway owns Shopify Customer Account authentication and application sessions. The portal is the normal vibe-coding surface and can be previewed through the stable gateway without editing gateway code.
17
+
18
+ ## Start locally
19
+
20
+ 1. Install dependencies with `npm install`.
21
+ 2. Initialize Git and make the first commit.
22
+ 3. Copy `apps/gateway/.dev.vars.example` to `apps/gateway/.dev.vars` and replace only the public configuration values.
23
+ 4. Run `npm run dev`.
24
+
25
+ Local development intentionally stays signed out because Shopify requires registered HTTPS callback URLs. Use an immutable Tender preview to test real sign-in.
26
+
27
+ ## Create a preview
28
+
29
+ Each app is linked and delivered independently. A merchant administrator provides a short-lived developer credential scoped to the correct Tender project. Put it in the ignored root `.dev.vars` file; the CLI searches up to the repository root when `--cwd` selects an app.
30
+
31
+ ```sh
32
+ npx tender-accounts link --cwd apps/portal --json
33
+ npx tender-accounts doctor --cwd apps/portal --json
34
+ npx tender-accounts preview --cwd apps/portal --dry-run --json
35
+ npx tender-accounts preview --cwd apps/portal --json
36
+ ```
37
+
38
+ Use `apps/gateway` instead only when gateway code or bindings changed. The CLI intentionally cannot publish production.
39
+
40
+ ## Configure Shopify
41
+
42
+ Register the canonical gateway hostname as the JavaScript origin and use its `/auth/callback` and `/auth/logout/callback` URLs in Shopify Customer Account settings. Put the public client ID and Shopify domains in the gateway's local `.dev.vars`; Tender injects the environment values for previews and production.
43
+
44
+ If the platform connects a merchant vanity domain, add that exact origin plus its `/auth/callback` and `/auth/logout/callback` URLs in Shopify, then set `ACCOUNT_VANITY_HOSTNAMES` in the gateway environment. Production sign-in and renewal will stay on the vanity hostname. Unregistered preview hosts continue to use the canonical callback and a one-time exact-host handoff. This value is public hostname policy, not a credential. The React portal needs no vanity-domain configuration.
@@ -0,0 +1,9 @@
1
+ # Gateway boundary
2
+
3
+ This app owns authentication, HttpOnly sessions, application D1 state, `/auth/*`, and protected `/api/*` routes.
4
+
5
+ - Do not put presentation-only changes here; make those in `../portal`.
6
+ - Preserve exact-host preview session isolation and Shopify PKCE protections.
7
+ - Never expose customer access tokens, ID tokens, D1 data, or confidential provider credentials to the portal.
8
+ - Add new resources and services to both `wrangler.jsonc` and `tender-app.config.json`; packaging must fail if they disagree.
9
+ - Run `npm run check` from this directory after gateway changes.
@@ -0,0 +1,9 @@
1
+ # Gateway
2
+
3
+ Stable backend for __APP_NAME__. It owns Shopify Customer Account PKCE authentication, shopper sessions in D1, application API routes, and the `PORTAL_UI` service binding.
4
+
5
+ The customer-auth implementation is normal merchant source code copied by the starter. The merchant can inspect, edit, or replace it. Shopify remains the customer system of record; D1 stores only transient auth state and application sessions.
6
+
7
+ Local HTTP development returns an explicit signed-out state. Real Shopify sign-in must use an HTTPS Tender preview whose canonical callback and logout URLs are registered in Shopify.
8
+
9
+ The generated `.dev` hostname stays the Shopify callback anchor. When a merchant vanity domain is activated, add it to the public comma-separated `ACCOUNT_VANITY_HOSTNAMES` environment value so the exact-host session policy can safely return the shopper there. Do not move Shopify callback configuration to the vanity hostname.
@@ -0,0 +1,49 @@
1
+ {
2
+ "schema": "tender.d1-schema/v1",
3
+ "statements": [
4
+ "CREATE TABLE IF NOT EXISTS public_customer_auth_flows (\n project_id TEXT NOT NULL,\n state_digest TEXT NOT NULL,\n browser_binding_digest TEXT NOT NULL,\n client_key_digest TEXT NOT NULL,\n interaction_mode TEXT NOT NULL,\n pkce_verifier TEXT NOT NULL,\n return_hostname TEXT NOT NULL,\n created_at INTEGER NOT NULL,\n expires_at INTEGER NOT NULL,\n consumed_at INTEGER,\n PRIMARY KEY (project_id, state_digest),\n CHECK (length(project_id) BETWEEN 2 AND 128),\n CHECK (length(state_digest) = 64),\n CHECK (state_digest NOT GLOB '*[^0-9a-f]*'),\n CHECK (length(browser_binding_digest) = 64),\n CHECK (browser_binding_digest NOT GLOB '*[^0-9a-f]*'),\n CHECK (length(client_key_digest) = 64),\n CHECK (client_key_digest NOT GLOB '*[^0-9a-f]*'),\n CHECK (interaction_mode IN ('interactive', 'silent')),\n CHECK (length(pkce_verifier) BETWEEN 43 AND 128),\n CHECK (length(return_hostname) BETWEEN 1 AND 253),\n CHECK (return_hostname = lower(return_hostname)),\n CHECK (expires_at > created_at),\n CHECK (consumed_at IS NULL OR consumed_at BETWEEN created_at AND expires_at)\n )",
5
+ "CREATE INDEX IF NOT EXISTS public_customer_auth_flows_by_expiry\n ON public_customer_auth_flows (expires_at)",
6
+ "CREATE TABLE IF NOT EXISTS public_customer_auth_sessions (\n project_id TEXT NOT NULL,\n session_digest TEXT NOT NULL,\n audience_hostname TEXT NOT NULL,\n session_data TEXT NOT NULL,\n created_at INTEGER NOT NULL,\n expires_at INTEGER NOT NULL,\n PRIMARY KEY (project_id, session_digest),\n CHECK (length(project_id) BETWEEN 2 AND 128),\n CHECK (length(session_digest) = 64),\n CHECK (session_digest NOT GLOB '*[^0-9a-f]*'),\n CHECK (length(audience_hostname) BETWEEN 1 AND 253),\n CHECK (audience_hostname = lower(audience_hostname)),\n CHECK (length(session_data) BETWEEN 1 AND 65536),\n CHECK (expires_at > created_at)\n )",
7
+ "CREATE INDEX IF NOT EXISTS public_customer_auth_sessions_by_expiry\n ON public_customer_auth_sessions (expires_at)",
8
+ "CREATE TABLE IF NOT EXISTS public_customer_auth_handoffs (\n project_id TEXT NOT NULL,\n handoff_digest TEXT NOT NULL,\n return_hostname TEXT NOT NULL,\n session_digest TEXT NOT NULL,\n session_seal TEXT NOT NULL,\n created_at INTEGER NOT NULL,\n expires_at INTEGER NOT NULL,\n claimed_at INTEGER,\n PRIMARY KEY (project_id, handoff_digest),\n CHECK (length(project_id) BETWEEN 2 AND 128),\n CHECK (length(handoff_digest) = 64),\n CHECK (handoff_digest NOT GLOB '*[^0-9a-f]*'),\n CHECK (length(return_hostname) BETWEEN 1 AND 253),\n CHECK (return_hostname = lower(return_hostname)),\n CHECK (length(session_digest) = 64),\n CHECK (session_digest NOT GLOB '*[^0-9a-f]*'),\n CHECK (length(session_seal) BETWEEN 80 AND 100000),\n CHECK (expires_at > created_at),\n CHECK (claimed_at IS NULL OR claimed_at BETWEEN created_at AND expires_at)\n )",
9
+ "CREATE INDEX IF NOT EXISTS public_customer_auth_handoffs_by_expiry\n ON public_customer_auth_handoffs (expires_at)"
10
+ ],
11
+ "objects": [
12
+ {
13
+ "type": "table",
14
+ "name": "public_customer_auth_flows",
15
+ "tableName": "public_customer_auth_flows",
16
+ "statementIndex": 0
17
+ },
18
+ {
19
+ "type": "index",
20
+ "name": "public_customer_auth_flows_by_expiry",
21
+ "tableName": "public_customer_auth_flows",
22
+ "statementIndex": 1
23
+ },
24
+ {
25
+ "type": "table",
26
+ "name": "public_customer_auth_sessions",
27
+ "tableName": "public_customer_auth_sessions",
28
+ "statementIndex": 2
29
+ },
30
+ {
31
+ "type": "index",
32
+ "name": "public_customer_auth_sessions_by_expiry",
33
+ "tableName": "public_customer_auth_sessions",
34
+ "statementIndex": 3
35
+ },
36
+ {
37
+ "type": "table",
38
+ "name": "public_customer_auth_handoffs",
39
+ "tableName": "public_customer_auth_handoffs",
40
+ "statementIndex": 4
41
+ },
42
+ {
43
+ "type": "index",
44
+ "name": "public_customer_auth_handoffs_by_expiry",
45
+ "tableName": "public_customer_auth_handoffs",
46
+ "statementIndex": 5
47
+ }
48
+ ]
49
+ }
@@ -0,0 +1,11 @@
1
+ # Public Shopify and Tender runtime configuration. Replace these locally.
2
+ ACCOUNT_TENANT_ID=ten_replace_me
3
+ ACCOUNT_PROJECT_ID=prj_replace_me
4
+ ACCOUNT_RUNTIME_BASE_DOMAIN=tenderprompt.dev
5
+ ACCOUNT_VANITY_HOSTNAMES=
6
+ SHOPIFY_STOREFRONT_DOMAIN=example.myshopify.com
7
+ SHOPIFY_CUSTOMER_ACCOUNT_CLIENT_ID=replace_me
8
+ SHOPIFY_CUSTOMER_ACCOUNT_DOMAIN=shopify.com
9
+
10
+ # Keep the short-lived developer credential only in ignored .dev.vars when using the CLI.
11
+ # TENDER_ACCOUNTS_TOKEN=ta_agent_...
@@ -0,0 +1,76 @@
1
+ -- Application-owned auth/session state. Shopify remains the customer system of record.
2
+
3
+ CREATE TABLE IF NOT EXISTS public_customer_auth_flows (
4
+ project_id TEXT NOT NULL,
5
+ state_digest TEXT NOT NULL,
6
+ browser_binding_digest TEXT NOT NULL,
7
+ client_key_digest TEXT NOT NULL,
8
+ interaction_mode TEXT NOT NULL,
9
+ pkce_verifier TEXT NOT NULL,
10
+ return_hostname TEXT NOT NULL,
11
+ created_at INTEGER NOT NULL,
12
+ expires_at INTEGER NOT NULL,
13
+ consumed_at INTEGER,
14
+ PRIMARY KEY (project_id, state_digest),
15
+ CHECK (length(project_id) BETWEEN 2 AND 128),
16
+ CHECK (length(state_digest) = 64),
17
+ CHECK (state_digest NOT GLOB '*[^0-9a-f]*'),
18
+ CHECK (length(browser_binding_digest) = 64),
19
+ CHECK (browser_binding_digest NOT GLOB '*[^0-9a-f]*'),
20
+ CHECK (length(client_key_digest) = 64),
21
+ CHECK (client_key_digest NOT GLOB '*[^0-9a-f]*'),
22
+ CHECK (interaction_mode IN ('interactive', 'silent')),
23
+ CHECK (length(pkce_verifier) BETWEEN 43 AND 128),
24
+ CHECK (length(return_hostname) BETWEEN 1 AND 253),
25
+ CHECK (return_hostname = lower(return_hostname)),
26
+ CHECK (expires_at > created_at),
27
+ CHECK (consumed_at IS NULL OR consumed_at BETWEEN created_at AND expires_at)
28
+ );
29
+
30
+ CREATE INDEX IF NOT EXISTS public_customer_auth_flows_by_expiry
31
+ ON public_customer_auth_flows (expires_at);
32
+
33
+ CREATE TABLE IF NOT EXISTS public_customer_auth_sessions (
34
+ project_id TEXT NOT NULL,
35
+ session_digest TEXT NOT NULL,
36
+ audience_hostname TEXT NOT NULL,
37
+ session_data TEXT NOT NULL,
38
+ created_at INTEGER NOT NULL,
39
+ expires_at INTEGER NOT NULL,
40
+ PRIMARY KEY (project_id, session_digest),
41
+ CHECK (length(project_id) BETWEEN 2 AND 128),
42
+ CHECK (length(session_digest) = 64),
43
+ CHECK (session_digest NOT GLOB '*[^0-9a-f]*'),
44
+ CHECK (length(audience_hostname) BETWEEN 1 AND 253),
45
+ CHECK (audience_hostname = lower(audience_hostname)),
46
+ CHECK (length(session_data) BETWEEN 1 AND 65536),
47
+ CHECK (expires_at > created_at)
48
+ );
49
+
50
+ CREATE INDEX IF NOT EXISTS public_customer_auth_sessions_by_expiry
51
+ ON public_customer_auth_sessions (expires_at);
52
+
53
+ CREATE TABLE IF NOT EXISTS public_customer_auth_handoffs (
54
+ project_id TEXT NOT NULL,
55
+ handoff_digest TEXT NOT NULL,
56
+ return_hostname TEXT NOT NULL,
57
+ session_digest TEXT NOT NULL,
58
+ session_seal TEXT NOT NULL,
59
+ created_at INTEGER NOT NULL,
60
+ expires_at INTEGER NOT NULL,
61
+ claimed_at INTEGER,
62
+ PRIMARY KEY (project_id, handoff_digest),
63
+ CHECK (length(project_id) BETWEEN 2 AND 128),
64
+ CHECK (length(handoff_digest) = 64),
65
+ CHECK (handoff_digest NOT GLOB '*[^0-9a-f]*'),
66
+ CHECK (length(return_hostname) BETWEEN 1 AND 253),
67
+ CHECK (return_hostname = lower(return_hostname)),
68
+ CHECK (length(session_digest) = 64),
69
+ CHECK (session_digest NOT GLOB '*[^0-9a-f]*'),
70
+ CHECK (length(session_seal) BETWEEN 80 AND 100000),
71
+ CHECK (expires_at > created_at),
72
+ CHECK (claimed_at IS NULL OR claimed_at BETWEEN created_at AND expires_at)
73
+ );
74
+
75
+ CREATE INDEX IF NOT EXISTS public_customer_auth_handoffs_by_expiry
76
+ ON public_customer_auth_handoffs (expires_at);
@@ -0,0 +1,25 @@
1
+ {
2
+ "name": "__APP_SLUG__-gateway",
3
+ "version": "0.1.0",
4
+ "private": true,
5
+ "type": "module",
6
+ "scripts": {
7
+ "build": "node ./scripts/build-worker.mjs",
8
+ "cf:types": "wrangler types --include-runtime false",
9
+ "check": "npm run typecheck && npm run test && npm run build && npm run pack:check",
10
+ "db:migrate:local": "wrangler d1 migrations apply __APP_SLUG__-sessions --local",
11
+ "dev": "wrangler dev",
12
+ "pack:tender": "node ./scripts/pack-tender-artifact.mjs",
13
+ "pack:check": "node ./scripts/pack-tender-artifact.mjs --vars .dev.vars.example --source-revision ffffffffffffffffffffffffffffffffffffffff",
14
+ "test": "vitest run",
15
+ "typecheck": "tsc --noEmit"
16
+ },
17
+ "devDependencies": {
18
+ "@cloudflare/workers-types": "^5.20260825.1",
19
+ "@types/node": "^26.2.0",
20
+ "esbuild": "^0.25.9",
21
+ "typescript": "^7.0.2",
22
+ "vitest": "^4.1.11",
23
+ "wrangler": "^4.125.0"
24
+ }
25
+ }