@01.software/init 0.9.1 → 0.10.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.
- package/dist/ai-docs.d.ts +13 -0
- package/dist/ai-docs.js +1 -1
- package/dist/browser-auth-CJDrpp5T.d.ts +11 -0
- package/dist/{chunk-UA7WNT2F.js → chunk-4LHYICUL.js} +1 -1
- package/dist/chunk-4LHYICUL.js.map +1 -0
- package/dist/{chunk-TBGKXE3Q.js → chunk-NJ4X7VNK.js} +5 -5
- package/dist/chunk-NJ4X7VNK.js.map +1 -0
- package/dist/{chunk-5K2CB2Y5.js → chunk-Q6MSORYN.js} +14 -37
- package/dist/chunk-Q6MSORYN.js.map +1 -0
- package/dist/chunk-STM4DKVZ.js +183 -0
- package/dist/chunk-STM4DKVZ.js.map +1 -0
- package/dist/{chunk-2IGKOSK7.js → chunk-WDWJ73KP.js} +41 -215
- package/dist/chunk-WDWJ73KP.js.map +1 -0
- package/dist/create-app-templates/ecommerce/AGENTS.md +88 -0
- package/dist/create-app-templates/ecommerce/CHANGELOG.md +30 -0
- package/dist/create-app-templates/ecommerce/CLAUDE.md +1 -0
- package/dist/create-app-templates/ecommerce/README.md +139 -0
- package/dist/create-app-templates/ecommerce/app/api/auth/login/route.ts +30 -0
- package/dist/create-app-templates/ecommerce/app/api/auth/logout/route.ts +18 -0
- package/dist/create-app-templates/ecommerce/app/api/auth/register/route.ts +41 -0
- package/dist/create-app-templates/ecommerce/app/api/cart/clear/route.ts +12 -0
- package/dist/create-app-templates/ecommerce/app/api/cart/items/route.ts +45 -0
- package/dist/create-app-templates/ecommerce/app/api/cart/route.ts +14 -0
- package/dist/create-app-templates/ecommerce/app/api/checkout/payment-return/route.ts +86 -0
- package/dist/create-app-templates/ecommerce/app/api/checkout/reconcile/route.ts +50 -0
- package/dist/create-app-templates/ecommerce/app/api/checkout/route.ts +41 -0
- package/dist/create-app-templates/ecommerce/app/cart/page.tsx +10 -0
- package/dist/create-app-templates/ecommerce/app/checkout/page.tsx +10 -0
- package/dist/create-app-templates/ecommerce/app/checkout/success/page.tsx +34 -0
- package/dist/create-app-templates/ecommerce/app/favicon.ico +0 -0
- package/dist/create-app-templates/ecommerce/app/globals.css +67 -0
- package/dist/create-app-templates/ecommerce/app/layout.tsx +23 -0
- package/dist/create-app-templates/ecommerce/app/login/page.tsx +11 -0
- package/dist/create-app-templates/ecommerce/app/page.tsx +5 -0
- package/dist/create-app-templates/ecommerce/app/products/[slug]/page.tsx +46 -0
- package/dist/create-app-templates/ecommerce/app/products/page.tsx +45 -0
- package/dist/create-app-templates/ecommerce/app/register/page.tsx +11 -0
- package/dist/create-app-templates/ecommerce/app/webhook/payment/route.ts +20 -0
- package/dist/create-app-templates/ecommerce/app-config.ts +54 -0
- package/dist/create-app-templates/ecommerce/components/auth/auth-form.tsx +109 -0
- package/dist/create-app-templates/ecommerce/components/cart/cart-content.tsx +119 -0
- package/dist/create-app-templates/ecommerce/components/checkout/checkout-form.tsx +267 -0
- package/dist/create-app-templates/ecommerce/components/checkout/checkout-reconcile.tsx +78 -0
- package/dist/create-app-templates/ecommerce/components/layout/account-nav.tsx +48 -0
- package/dist/create-app-templates/ecommerce/components/layout/account-slot.tsx +12 -0
- package/dist/create-app-templates/ecommerce/components/layout/cart-link.tsx +13 -0
- package/dist/create-app-templates/ecommerce/components/layout/page-shell.tsx +11 -0
- package/dist/create-app-templates/ecommerce/components/layout/site-header.tsx +22 -0
- package/dist/create-app-templates/ecommerce/components/product/add-to-cart.tsx +116 -0
- package/dist/create-app-templates/ecommerce/components/product/product-card.tsx +50 -0
- package/dist/create-app-templates/ecommerce/components/product/product-gallery.tsx +39 -0
- package/dist/create-app-templates/ecommerce/data/mock-catalog.json +173 -0
- package/dist/create-app-templates/ecommerce/eslint.config.mjs +18 -0
- package/dist/create-app-templates/ecommerce/lib/cart/cookie.ts +40 -0
- package/dist/create-app-templates/ecommerce/lib/cart/normalize.ts +32 -0
- package/dist/create-app-templates/ecommerce/lib/cart/parse-cart-request.ts +56 -0
- package/dist/create-app-templates/ecommerce/lib/cart/route-helpers.ts +17 -0
- package/dist/create-app-templates/ecommerce/lib/cart/select-provider.ts +44 -0
- package/dist/create-app-templates/ecommerce/lib/cart/server-cart.ts +96 -0
- package/dist/create-app-templates/ecommerce/lib/cart/sync-on-login.server.ts +34 -0
- package/dist/create-app-templates/ecommerce/lib/cart/use-cart.tsx +151 -0
- package/dist/create-app-templates/ecommerce/lib/checkout/checkout-errors.ts +22 -0
- package/dist/create-app-templates/ecommerce/lib/checkout/checkout-provider.ts +28 -0
- package/dist/create-app-templates/ecommerce/lib/checkout/parse-checkout-payload.ts +76 -0
- package/dist/create-app-templates/ecommerce/lib/checkout/start-checkout.ts +63 -0
- package/dist/create-app-templates/ecommerce/lib/checkout/types.ts +3 -0
- package/dist/create-app-templates/ecommerce/lib/commerce/adapters/mock.ts +336 -0
- package/dist/create-app-templates/ecommerce/lib/commerce/adapters/software-mappers.ts +312 -0
- package/dist/create-app-templates/ecommerce/lib/commerce/adapters/software.ts +913 -0
- package/dist/create-app-templates/ecommerce/lib/commerce/product-summary.ts +37 -0
- package/dist/create-app-templates/ecommerce/lib/commerce/provider.server.ts +60 -0
- package/dist/create-app-templates/ecommerce/lib/commerce/provider.ts +96 -0
- package/dist/create-app-templates/ecommerce/lib/commerce/stock.ts +37 -0
- package/dist/create-app-templates/ecommerce/lib/commerce/types.ts +206 -0
- package/dist/create-app-templates/ecommerce/lib/commerce/variant-selection.ts +23 -0
- package/dist/create-app-templates/ecommerce/lib/customer/auth-actions.ts +131 -0
- package/dist/create-app-templates/ecommerce/lib/customer/cart-sync.ts +44 -0
- package/dist/create-app-templates/ecommerce/lib/customer/client.server.ts +109 -0
- package/dist/create-app-templates/ecommerce/lib/customer/current-customer.ts +15 -0
- package/dist/create-app-templates/ecommerce/lib/customer/route-guard.ts +58 -0
- package/dist/create-app-templates/ecommerce/lib/customer/route-helpers.ts +75 -0
- package/dist/create-app-templates/ecommerce/lib/customer/session.ts +108 -0
- package/dist/create-app-templates/ecommerce/lib/format.ts +7 -0
- package/dist/create-app-templates/ecommerce/lib/payment/adapters/mock.ts +84 -0
- package/dist/create-app-templates/ecommerce/lib/payment/adapters/portone.ts +254 -0
- package/dist/create-app-templates/ecommerce/lib/payment/adapters/tosspayments.ts +287 -0
- package/dist/create-app-templates/ecommerce/lib/payment/amount-gate.ts +86 -0
- package/dist/create-app-templates/ecommerce/lib/payment/provider.server.ts +51 -0
- package/dist/create-app-templates/ecommerce/lib/payment/provider.ts +18 -0
- package/dist/create-app-templates/ecommerce/lib/payment/sync-order-payment.ts +96 -0
- package/dist/create-app-templates/ecommerce/lib/payment/types.ts +71 -0
- package/dist/create-app-templates/ecommerce/lib/server-only-guard.ts +20 -0
- package/dist/create-app-templates/ecommerce/next-env.d.ts +6 -0
- package/dist/create-app-templates/ecommerce/next.config.ts +16 -0
- package/dist/create-app-templates/ecommerce/package.json +33 -0
- package/dist/create-app-templates/ecommerce/postcss.config.mjs +7 -0
- package/dist/create-app-templates/ecommerce/tests/customer-auth.test.ts +263 -0
- package/dist/create-app-templates/ecommerce/tests/customer-cart.test.ts +392 -0
- package/dist/create-app-templates/ecommerce/tests/domain.test.ts +1537 -0
- package/dist/create-app-templates/ecommerce/tsconfig.json +35 -0
- package/dist/create-app-templates/registry.json +66 -0
- package/dist/create-app.d.ts +40 -0
- package/dist/create-app.js +652 -0
- package/dist/create-app.js.map +1 -0
- package/dist/detect-Bjxp9wcS.d.ts +13 -0
- package/dist/file-ops.d.ts +21 -0
- package/dist/file-ops.js +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.js +6 -5
- package/dist/index.js.map +1 -1
- package/dist/init.d.ts +40 -0
- package/dist/init.js +5 -4
- package/dist/templates.d.ts +27 -0
- package/dist/templates.js +1 -1
- package/package.json +31 -15
- package/dist/chunk-2IGKOSK7.js.map +0 -1
- package/dist/chunk-5K2CB2Y5.js.map +0 -1
- package/dist/chunk-TBGKXE3Q.js.map +0 -1
- package/dist/chunk-UA7WNT2F.js.map +0 -1
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
interface TenantContext {
|
|
2
|
+
tenantName: string;
|
|
3
|
+
features?: string[];
|
|
4
|
+
collections?: string[];
|
|
5
|
+
}
|
|
6
|
+
declare function generateClaudeMd(ctx: TenantContext): string;
|
|
7
|
+
declare function getSkillFiles(): Array<{
|
|
8
|
+
dirName: string;
|
|
9
|
+
content: string;
|
|
10
|
+
}>;
|
|
11
|
+
declare function fetchTenantContext(publishableKey: string, secretKey: string): Promise<TenantContext | null>;
|
|
12
|
+
|
|
13
|
+
export { type TenantContext, fetchTenantContext, generateClaudeMd, getSkillFiles };
|
package/dist/ai-docs.js
CHANGED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/file-ops.ts"],"sourcesContent":["// Helpers for `.env*` / Codex TOML / secret-bearing global config writes.\n// The pure string utilities (`readEnvValue`, `setEnvValue`,\n// `replaceTomlMcpSection`) are unit-tested without filesystem access; the\n// secret-write helpers (`writeEnvFile`, `chmodSecretFile`,\n// `writeSecretGlobalConfig`) are tested against an isolated tmpdir.\n\nimport fs from 'node:fs'\nimport path from 'node:path'\nimport { randomBytes } from 'node:crypto'\n\n// ── Secret-bearing writes ────────────────────────────────────────────\n\n/** POSIX file mode for secret-bearing files (`.env*`, global auth config). */\nexport const SECRET_FILE_MODE = 0o600\n/** POSIX directory mode for parents of global auth-bearing config. */\nexport const SECRET_DIR_MODE = 0o700\n\nconst isWin32 = process.platform === 'win32'\n\nfunction writeFileWithSecretMode(target: string, content: string): void {\n fs.writeFileSync(target, content, isWin32 ? undefined : { mode: SECRET_FILE_MODE })\n}\n\nfunction mkdirWithSecretMode(dir: string): void {\n fs.mkdirSync(\n dir,\n isWin32 ? { recursive: true } : { recursive: true, mode: SECRET_DIR_MODE },\n )\n}\n\n/** Write a `.env*` file with mode `0o600` on POSIX. Skips mode on Windows\n * (secret-handling caveats in `packages/init/AGENTS.md`). */\nexport function writeEnvFile(target: string, content: string): void {\n writeFileWithSecretMode(target, content)\n}\n\n/** Apply secret-file mode to an existing file (no-op on Windows). Useful\n * after merging an `.env*` file that already existed. */\nexport function chmodSecretFile(target: string): void {\n if (isWin32) return\n try {\n fs.chmodSync(target, SECRET_FILE_MODE)\n } catch {\n // Mode tightening is best-effort; the spec waives the requirement on\n // platforms that reject the chmod.\n }\n}\n\n/** Write a global auth-bearing config (e.g. `~/.codex/config.toml`) using\n * symlink-safe atomic rename. Throws when the existing target is a symlink,\n * has multiple hard links, or is not a regular file. */\nexport function writeSecretGlobalConfig(target: string, content: string): void {\n mkdirWithSecretMode(path.dirname(target))\n\n // Reject symlinks / non-regular files / multi-hardlink targets before any write.\n if (fs.existsSync(target)) {\n const stat = fs.lstatSync(target)\n if (stat.isSymbolicLink()) {\n throw new Error(\n `Refusing to write secret-bearing config through a symlink: ${target}. ` +\n `Remove the symlink and re-run init.`,\n )\n }\n if (!stat.isFile()) {\n throw new Error(\n `Refusing to write secret-bearing config to non-regular file: ${target}.`,\n )\n }\n if (!isWin32 && stat.nlink > 1) {\n throw new Error(\n `Refusing to write secret-bearing config to file with multiple hard links: ${target}.`,\n )\n }\n }\n\n // Atomic temp-file replacement: write tmp with 0o600, then rename to target.\n const tmp = `${target}.${randomBytes(6).toString('hex')}.tmp`\n writeFileWithSecretMode(tmp, content)\n try {\n fs.renameSync(tmp, target)\n } catch (err) {\n // Best-effort cleanup of the tmp file if rename failed (e.g. EXDEV).\n try {\n fs.unlinkSync(tmp)\n } catch {\n // ignore\n }\n throw err\n }\n}\n\n// ── .env merge ───────────────────────────────────────────────────────\n\nconst envLineRegexCache = new Map<string, RegExp>()\n\nfunction envLineRegex(name: string): RegExp {\n let re = envLineRegexCache.get(name)\n if (!re) {\n const escaped = name.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&')\n re = new RegExp(`^${escaped}=(.*)$`, 'm')\n envLineRegexCache.set(name, re)\n }\n return re\n}\n\nexport function readEnvValue(content: string, name: string): string | null {\n const m = content.match(envLineRegex(name))\n return m ? m[1] : null\n}\n\nexport function setEnvValue(content: string, name: string, value: string): string {\n const re = envLineRegex(name)\n if (re.test(content)) return content.replace(re, `${name}=${value}`)\n const sep = content.length === 0 || content.endsWith('\\n') ? '' : '\\n'\n return content + sep + `${name}=${value}\\n`\n}\n\n// ── Codex TOML manipulation ──────────────────────────────────────────\n\nconst OWNED_MCP_TOML_SECTION = 'mcp_servers.01software'\n\nfunction tomlSectionName(line: string): string | null {\n const match = line.trim().match(/^\\[([^\\]]+)\\]$/)\n return match ? match[1] : null\n}\n\nfunction isOwnedMcpTomlSection(sectionName: string): boolean {\n return (\n sectionName === OWNED_MCP_TOML_SECTION ||\n sectionName.startsWith(`${OWNED_MCP_TOML_SECTION}.`)\n )\n}\n\n/** Removes the existing `[mcp_servers.01software]` block and sub-blocks from a\n * TOML document, then appends the provided section. */\nexport function replaceTomlMcpSection(content: string, newSection: string): string {\n const lines = content.split('\\n')\n const kept: string[] = []\n let inOurSection = false\n for (const line of lines) {\n const sectionName = tomlSectionName(line)\n if (sectionName) {\n inOurSection = isOwnedMcpTomlSection(sectionName)\n if (inOurSection) continue\n }\n if (!inOurSection) kept.push(line)\n }\n let result = kept.join('\\n').replace(/\\n+$/, '')\n if (result.length > 0) result += '\\n'\n // newSection already starts with a blank line; keep it that way\n result += newSection.startsWith('\\n') ? newSection : '\\n' + newSection\n return result\n}\n"],"mappings":"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","names":[]}
|
|
@@ -56,11 +56,11 @@ export const analytics = createAnalytics({
|
|
|
56
56
|
function getQueryProviderTemplate(env) {
|
|
57
57
|
const useClientDirective = env === "nextjs" ? "'use client'\n\n" : "";
|
|
58
58
|
return `${useClientDirective}import { QueryClientProvider } from '@tanstack/react-query'
|
|
59
|
-
import {
|
|
59
|
+
import { getQueryClient } from '@01.software/sdk/query'
|
|
60
60
|
|
|
61
61
|
export function QueryProvider({ children }: { children: React.ReactNode }) {
|
|
62
62
|
return (
|
|
63
|
-
<QueryClientProvider client={
|
|
63
|
+
<QueryClientProvider client={getQueryClient()}>
|
|
64
64
|
{children}
|
|
65
65
|
</QueryClientProvider>
|
|
66
66
|
)
|
|
@@ -69,7 +69,7 @@ export function QueryProvider({ children }: { children: React.ReactNode }) {
|
|
|
69
69
|
}
|
|
70
70
|
function getServerTemplate(env, publishableKeyEnvVar, secretKeyEnvVar) {
|
|
71
71
|
if (env === "edge") {
|
|
72
|
-
return `import { createServerClient } from '@01.software/sdk'
|
|
72
|
+
return `import { createServerClient } from '@01.software/sdk/server'
|
|
73
73
|
|
|
74
74
|
// Edge runtime: pass your env bindings here
|
|
75
75
|
// e.g. Cloudflare Workers: use env.SOFTWARE_PUBLISHABLE_KEY from the handler context
|
|
@@ -79,7 +79,7 @@ export function createEdgeClient(publishableKey: string, secretKey: string) {
|
|
|
79
79
|
}
|
|
80
80
|
`;
|
|
81
81
|
}
|
|
82
|
-
return `import { createServerClient } from '@01.software/sdk'
|
|
82
|
+
return `import { createServerClient } from '@01.software/sdk/server'
|
|
83
83
|
|
|
84
84
|
/**
|
|
85
85
|
* Runtime guard: this module contains secret keys and must never be
|
|
@@ -180,4 +180,4 @@ export {
|
|
|
180
180
|
getCodexMcpTomlSection,
|
|
181
181
|
CODEX_MCP_SECTION_MARKER
|
|
182
182
|
};
|
|
183
|
-
//# sourceMappingURL=chunk-
|
|
183
|
+
//# sourceMappingURL=chunk-NJ4X7VNK.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/templates.ts"],"sourcesContent":["import type { ProjectEnv } from './detect'\n\nconst MCP_RESOURCE_AUDIENCE = 'https://mcp.01.software/mcp'\n\n// ── Client template (browser) ────────────────────────────────────────\n\nexport function getClientTemplate(env: ProjectEnv, publishableKeyEnvVar: string): string {\n if (env === 'nextjs') {\n return `import { createClient } from '@01.software/sdk'\n\nexport const client = createClient({\n publishableKey: process.env.${publishableKeyEnvVar}!,\n})\n`\n }\n\n if (env === 'react-cra') {\n return `import { createClient } from '@01.software/sdk'\n\nexport const client = createClient({\n publishableKey: process.env.${publishableKeyEnvVar}!,\n})\n`\n }\n\n if (env === 'vanilla') {\n return `import { createClient } from '@01.software/sdk'\n\n// Replace 'YOUR_PUBLISHABLE_KEY' with your actual publishable key from the 01.software console\nexport const client = createClient({\n publishableKey: 'YOUR_PUBLISHABLE_KEY',\n})\n`\n }\n\n // react-vite (import.meta.env)\n return `import { createClient } from '@01.software/sdk'\n\nexport const client = createClient({\n publishableKey: import.meta.env.${publishableKeyEnvVar},\n})\n`\n}\n\n// ── Analytics template (browser) ─────────────────────────────────────\n\nexport function getAnalyticsTemplate(\n env: ProjectEnv,\n publishableKeyEnvVar: string,\n): string {\n if (env === 'vanilla') {\n return `import { createAnalytics } from '@01.software/sdk/analytics'\n\n// Replace 'YOUR_PUBLISHABLE_KEY' with your actual publishable key from the 01.software console\nexport const analytics = createAnalytics({\n publishableKey: 'YOUR_PUBLISHABLE_KEY',\n})\n`\n }\n\n const publishableKeyExpression =\n env === 'react-vite'\n ? `import.meta.env.${publishableKeyEnvVar}`\n : `process.env.${publishableKeyEnvVar}!`\n\n return `import { createAnalytics } from '@01.software/sdk/analytics'\n\nexport const analytics = createAnalytics({\n publishableKey: ${publishableKeyExpression},\n})\n`\n}\n\n// ── Query Provider template ──────────────────────────────────────────\n\nexport function getQueryProviderTemplate(env: ProjectEnv): string {\n const useClientDirective = env === 'nextjs' ? \"'use client'\\n\\n\" : ''\n\n return `${useClientDirective}import { QueryClientProvider } from '@tanstack/react-query'\nimport { getQueryClient } from '@01.software/sdk/query'\n\nexport function QueryProvider({ children }: { children: React.ReactNode }) {\n return (\n <QueryClientProvider client={getQueryClient()}>\n {children}\n </QueryClientProvider>\n )\n}\n`\n}\n\n// ── Server template ──────────────────────────────────────────────────\n\nexport function getServerTemplate(\n env: ProjectEnv,\n publishableKeyEnvVar: string,\n secretKeyEnvVar: string,\n): string {\n if (env === 'edge') {\n return `import { createServerClient } from '@01.software/sdk/server'\n\n// Edge runtime: pass your env bindings here\n// e.g. Cloudflare Workers: use env.SOFTWARE_PUBLISHABLE_KEY from the handler context\n// e.g. Vercel Edge: use process.env.${publishableKeyEnvVar}\nexport function createEdgeClient(publishableKey: string, secretKey: string) {\n return createServerClient({ publishableKey, secretKey })\n}\n`\n }\n\n return `import { createServerClient } from '@01.software/sdk/server'\n\n/**\n * Runtime guard: this module contains secret keys and must never be\n * imported from client-side (browser) code. If the bundler accidentally\n * includes it in a client bundle, this throws at module-evaluation time.\n */\nif (typeof window !== 'undefined') {\n throw new Error(\n 'lib/software/server.ts must not be imported in client-side code — it contains secret keys.',\n )\n}\n\ntype ServerClient = ReturnType<typeof createServerClient>\n\nlet cachedClient: ServerClient | null = null\n\nfunction createConfiguredClient(): ServerClient {\n const publishableKey = process.env.${publishableKeyEnvVar}\n const secretKey = process.env.${secretKeyEnvVar}\n\n if (!publishableKey || !secretKey) {\n throw new Error(\n 'Server client requires ${publishableKeyEnvVar} and ${secretKeyEnvVar}.',\n )\n }\n\n return createServerClient({ publishableKey, secretKey })\n}\n\nexport function getServerClient(): ServerClient {\n cachedClient ??= createConfiguredClient()\n return cachedClient\n}\n\n/**\n * Lazy Proxy: the underlying client is created only on first property access,\n * so importing this file has no side effects until you actually call it.\n */\nexport const serverClient = new Proxy({} as ServerClient, {\n get(_target, prop, receiver) {\n const target = getServerClient()\n const value = Reflect.get(target as object, prop, receiver)\n return typeof value === 'function' ? value.bind(target) : value\n },\n})\n`\n}\n\n// ── Env file content ─────────────────────────────────────────────────\n\nexport function getEnvContent(\n publishableKey: string,\n secretKey: string,\n publishableKeyEnvVar: string,\n secretKeyEnvVar: string | null,\n): string {\n let content = `\\n# 01.software\\n${publishableKeyEnvVar}=${publishableKey}\\n`\n if (secretKeyEnvVar) {\n content += `${secretKeyEnvVar}=${secretKey}\\n`\n }\n return content\n}\n\n// ── MCP config (JSON) ────────────────────────────────────────────────\n\nexport type McpJsonClient = 'generic' | 'windsurf' | 'vscode'\n\n/** VS Code's MCP config root key is `servers`; all other JSON clients use\n * `mcpServers` (MCP config caveats in `packages/init/AGENTS.md`). */\nexport function getMcpRootKey(client: McpJsonClient = 'generic'): 'servers' | 'mcpServers' {\n return client === 'vscode' ? 'servers' : 'mcpServers'\n}\n\nexport function getMcpServerEntry(client: McpJsonClient = 'generic') {\n if (client === 'windsurf') {\n return {\n serverUrl: MCP_RESOURCE_AUDIENCE,\n }\n }\n\n return {\n type: 'http' as const,\n url: MCP_RESOURCE_AUDIENCE,\n }\n}\n\nexport function getMcpConfigTemplate(client: McpJsonClient = 'generic'): string {\n const rootKey = getMcpRootKey(client)\n return (\n JSON.stringify(\n { [rootKey]: { '01software': getMcpServerEntry(client) } },\n null,\n 2,\n ) + '\\n'\n )\n}\n\n// ── MCP config (TOML — Codex CLI) ────────────────────────────────────\n\n/** Codex CLI `[mcp_servers.01software]` block. Idempotent marker: the header\n * line. Intended to be appended to `~/.codex/config.toml`. */\nexport function getCodexMcpTomlSection(): string {\n return `\n[mcp_servers.01software]\nurl = \"${MCP_RESOURCE_AUDIENCE}\"\n`\n}\n\nexport const CODEX_MCP_SECTION_MARKER = '[mcp_servers.01software]'\n"],"mappings":";;;AAEA,IAAM,wBAAwB;AAIvB,SAAS,kBAAkB,KAAiB,sBAAsC;AACvF,MAAI,QAAQ,UAAU;AACpB,WAAO;AAAA;AAAA;AAAA,gCAGqB,oBAAoB;AAAA;AAAA;AAAA,EAGlD;AAEA,MAAI,QAAQ,aAAa;AACvB,WAAO;AAAA;AAAA;AAAA,gCAGqB,oBAAoB;AAAA;AAAA;AAAA,EAGlD;AAEA,MAAI,QAAQ,WAAW;AACrB,WAAO;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOT;AAGA,SAAO;AAAA;AAAA;AAAA,oCAG2B,oBAAoB;AAAA;AAAA;AAGxD;AAIO,SAAS,qBACd,KACA,sBACQ;AACR,MAAI,QAAQ,WAAW;AACrB,WAAO;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOT;AAEA,QAAM,2BACJ,QAAQ,eACJ,mBAAmB,oBAAoB,KACvC,eAAe,oBAAoB;AAEzC,SAAO;AAAA;AAAA;AAAA,oBAGW,wBAAwB;AAAA;AAAA;AAG5C;AAIO,SAAS,yBAAyB,KAAyB;AAChE,QAAM,qBAAqB,QAAQ,WAAW,qBAAqB;AAEnE,SAAO,GAAG,kBAAkB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAW9B;AAIO,SAAS,kBACd,KACA,sBACA,iBACQ;AACR,MAAI,QAAQ,QAAQ;AAClB,WAAO;AAAA;AAAA;AAAA;AAAA,8CAImC,oBAAoB;AAAA;AAAA;AAAA;AAAA;AAAA,EAKhE;AAEA,SAAO;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,uCAkB8B,oBAAoB;AAAA,kCACzB,eAAe;AAAA;AAAA;AAAA;AAAA,gCAIjB,oBAAoB,QAAQ,eAAe;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAwB3E;AAIO,SAAS,cACd,gBACA,WACA,sBACA,iBACQ;AACR,MAAI,UAAU;AAAA;AAAA,EAAoB,oBAAoB,IAAI,cAAc;AAAA;AACxE,MAAI,iBAAiB;AACnB,eAAW,GAAG,eAAe,IAAI,SAAS;AAAA;AAAA,EAC5C;AACA,SAAO;AACT;AAQO,SAAS,cAAc,SAAwB,WAAqC;AACzF,SAAO,WAAW,WAAW,YAAY;AAC3C;AAEO,SAAS,kBAAkB,SAAwB,WAAW;AACnE,MAAI,WAAW,YAAY;AACzB,WAAO;AAAA,MACL,WAAW;AAAA,IACb;AAAA,EACF;AAEA,SAAO;AAAA,IACL,MAAM;AAAA,IACN,KAAK;AAAA,EACP;AACF;AAEO,SAAS,qBAAqB,SAAwB,WAAmB;AAC9E,QAAM,UAAU,cAAc,MAAM;AACpC,SACE,KAAK;AAAA,IACH,EAAE,CAAC,OAAO,GAAG,EAAE,cAAc,kBAAkB,MAAM,EAAE,EAAE;AAAA,IACzD;AAAA,IACA;AAAA,EACF,IAAI;AAER;AAMO,SAAS,yBAAiC;AAC/C,SAAO;AAAA;AAAA,SAEA,qBAAqB;AAAA;AAE9B;AAEO,IAAM,2BAA2B;","names":[]}
|
|
@@ -8,12 +8,7 @@ function normalizeActiveCollections(collections) {
|
|
|
8
8
|
var HTTP_MCP_TOOLS = [
|
|
9
9
|
"get-collection-schema",
|
|
10
10
|
"get-tenant-context",
|
|
11
|
-
"
|
|
12
|
-
"update-field-config",
|
|
13
|
-
"sdk-get-recipe",
|
|
14
|
-
"sdk-search-docs",
|
|
15
|
-
"sdk-get-auth-setup",
|
|
16
|
-
"sdk-get-collection-pattern"
|
|
11
|
+
"check-feature-progress"
|
|
17
12
|
];
|
|
18
13
|
function generateClaudeMd(ctx) {
|
|
19
14
|
const featuresSection = ctx.features && ctx.features.length > 0 ? ctx.features.map((f) => `- ${f}`).join("\n") : "- See console";
|
|
@@ -35,47 +30,28 @@ ${featuresSection}
|
|
|
35
30
|
${collectionsSection}
|
|
36
31
|
|
|
37
32
|
## Hosted HTTP MCP Quick Reference
|
|
38
|
-
The hosted OAuth MCP server exposes these
|
|
33
|
+
The hosted OAuth MCP server exposes these ${HTTP_MCP_TOOLS.length} tools:
|
|
39
34
|
|
|
40
35
|
${HTTP_MCP_TOOLS.map((tool) => `- \`${tool}\``).join("\n")}
|
|
41
36
|
|
|
42
37
|
Use \`get-collection-schema\` for live field introspection before generating code.
|
|
43
|
-
Use SDK/server
|
|
38
|
+
Use the SDK/server client, API, or the \`01\` CLI for collection reads/writes,
|
|
39
|
+
cart workflows, and order/fulfillment/transaction operations that are not
|
|
40
|
+
exposed on hosted MCP.
|
|
44
41
|
|
|
45
42
|
## CLI
|
|
46
43
|
- \`01 query <collection>\` \u2014 query data
|
|
47
44
|
- \`01 schema show <collection>\` \u2014 inspect fields
|
|
48
45
|
- \`01 schema list\` \u2014 list all collections
|
|
49
|
-
- \`
|
|
46
|
+
- \`01 order\`, \`01 cart\`, \`01 transaction\`, \`01 product\` \u2014 commerce workflows
|
|
50
47
|
|
|
51
48
|
## Initial Setup
|
|
52
|
-
|
|
49
|
+
Configure field visibility (hide unused collections/fields) from the Console admin panel.
|
|
53
50
|
Use \`get-collection-schema\` or \`01 schema show <collection>\` for live field introspection instead of relying on this document as a schema snapshot.
|
|
54
51
|
`;
|
|
55
52
|
}
|
|
56
53
|
function getSkillFiles() {
|
|
57
54
|
return [
|
|
58
|
-
{
|
|
59
|
-
dirName: "01software-field-config",
|
|
60
|
-
content: `---
|
|
61
|
-
name: 01software-field-config
|
|
62
|
-
description: Configure field visibility for this tenant \u2014 hide unused collections and fields via MCP
|
|
63
|
-
disable-model-invocation: true
|
|
64
|
-
---
|
|
65
|
-
|
|
66
|
-
Steps:
|
|
67
|
-
1. Use \`list-configurable-fields\` to see current visibility settings
|
|
68
|
-
2. Identify fields/collections not needed for your use case
|
|
69
|
-
3. Use \`update-field-config\` to hide them
|
|
70
|
-
|
|
71
|
-
Common setups:
|
|
72
|
-
- Blog only: hide \`ecommerce\`, \`customers\`, \`videos\` collections
|
|
73
|
-
- Store: hide \`articles\`, \`documents\`, \`galleries\`, \`canvas\` collections
|
|
74
|
-
- Minimal: hide all except the collections you actively use
|
|
75
|
-
|
|
76
|
-
Ask me: "Show current field config" or "Hide ecommerce fields"
|
|
77
|
-
`
|
|
78
|
-
},
|
|
79
55
|
{
|
|
80
56
|
dirName: "01software-query",
|
|
81
57
|
content: `---
|
|
@@ -83,11 +59,12 @@ name: 01software-query
|
|
|
83
59
|
description: Query 01.software collections via SDK or CLI with filter, sort, and pagination examples
|
|
84
60
|
---
|
|
85
61
|
|
|
86
|
-
Hosted HTTP MCP
|
|
62
|
+
Hosted HTTP MCP exposes live schema and tenant context tools. Use the
|
|
63
|
+
SDK/server client or CLI for collection reads, filtering, and pagination.
|
|
87
64
|
|
|
88
65
|
HTTP MCP:
|
|
89
66
|
- Use \`get-collection-schema\` before assuming fields.
|
|
90
|
-
- Use \`
|
|
67
|
+
- Use \`get-tenant-context\` to see active collections and hidden fields.
|
|
91
68
|
|
|
92
69
|
CLI examples:
|
|
93
70
|
- \`01 query products --limit 10\`
|
|
@@ -122,9 +99,9 @@ States: pending \u2192 paid \u2192 preparing \u2192 shipped \u2192 delivered \u2
|
|
|
122
99
|
|
|
123
100
|
Free orders: omit paymentId, totalAmount=0 \u2192 auto-transitions to paid
|
|
124
101
|
|
|
125
|
-
Hosted HTTP MCP
|
|
126
|
-
|
|
127
|
-
CLI
|
|
102
|
+
Hosted HTTP MCP does not expose SDK-backed order mutations. Checkout, returns,
|
|
103
|
+
and granular order/fulfillment/transaction steps run through the SDK/server
|
|
104
|
+
client, API, or the \`01\` CLI (\`01 order create --help\` for full options).
|
|
128
105
|
`
|
|
129
106
|
},
|
|
130
107
|
{
|
|
@@ -174,4 +151,4 @@ export {
|
|
|
174
151
|
getSkillFiles,
|
|
175
152
|
fetchTenantContext
|
|
176
153
|
};
|
|
177
|
-
//# sourceMappingURL=chunk-
|
|
154
|
+
//# sourceMappingURL=chunk-Q6MSORYN.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/ai-docs.ts"],"sourcesContent":["export interface TenantContext {\n tenantName: string\n features?: string[]\n collections?: string[]\n}\n\ninterface TenantContextApiResponse {\n tenant?: { name?: string }\n features?: string[]\n collections?: { active?: string[]; inactive?: string[] }\n}\n\nfunction normalizeActiveCollections(\n collections: TenantContextApiResponse['collections'],\n): string[] {\n if (Array.isArray(collections?.active)) return collections.active\n return []\n}\n\n// Hosted HTTP MCP is the single thin surface for shell-less AI clients\n// (ADR 0009 + ADR 0031): only Console-service tools that execute under OAuth\n// request context are advertised. SDK-backed collection/commerce workflows,\n// SDK guidance, and field-config admin tooling live in the 01 CLI, API/SDK, and\n// Console, not on hosted MCP.\n// Keep this list in sync with MCP_TOOL_CONTRACT in @01.software/contracts.\nconst HTTP_MCP_TOOLS = [\n 'get-collection-schema',\n 'get-tenant-context',\n 'check-feature-progress',\n] as const\n\n// ── CLAUDE.md ────────────────────────────────────────────────────────\n\nexport function generateClaudeMd(ctx: TenantContext): string {\n const featuresSection =\n ctx.features && ctx.features.length > 0\n ? ctx.features.map((f) => `- ${f}`).join('\\n')\n : '- See console'\n\n const collectionsSection =\n ctx.collections && ctx.collections.length > 0\n ? ctx.collections.join(', ')\n : 'Run `01 schema list`'\n\n return `# 01.software SDK — ${ctx.tenantName}\n\n## Connection\n- Publishable Key: \\`NEXT_PUBLIC_SOFTWARE_PUBLISHABLE_KEY\\` (env)\n- Secret Key: \\`SOFTWARE_SECRET_KEY\\` (env)\n- MCP: \\`.mcp.json\\`\n- Agent discovery: \\`https://01.software/llms.txt\\`\n- Agent skill: \\`https://docs.01.software/skill.md\\`\n- OpenAPI: \\`https://docs.01.software/api/openapi\\`\n\n## Active Features\n${featuresSection}\n\n## Active Collections\n${collectionsSection}\n\n## Hosted HTTP MCP Quick Reference\nThe hosted OAuth MCP server exposes these ${HTTP_MCP_TOOLS.length} tools:\n\n${HTTP_MCP_TOOLS.map((tool) => `- \\`${tool}\\``).join('\\n')}\n\nUse \\`get-collection-schema\\` for live field introspection before generating code.\nUse the SDK/server client, API, or the \\`01\\` CLI for collection reads/writes,\ncart workflows, and order/fulfillment/transaction operations that are not\nexposed on hosted MCP.\n\n## CLI\n- \\`01 query <collection>\\` — query data\n- \\`01 schema show <collection>\\` — inspect fields\n- \\`01 schema list\\` — list all collections\n- \\`01 order\\`, \\`01 cart\\`, \\`01 transaction\\`, \\`01 product\\` — commerce workflows\n\n## Initial Setup\nConfigure field visibility (hide unused collections/fields) from the Console admin panel.\nUse \\`get-collection-schema\\` or \\`01 schema show <collection>\\` for live field introspection instead of relying on this document as a schema snapshot.\n`\n}\n\n// ── Skill files ──────────────────────────────────────────────────────\n\nexport function getSkillFiles(): Array<{ dirName: string; content: string }> {\n return [\n {\n dirName: '01software-query',\n content: `---\nname: 01software-query\ndescription: Query 01.software collections via SDK or CLI with filter, sort, and pagination examples\n---\n\nHosted HTTP MCP exposes live schema and tenant context tools. Use the\nSDK/server client or CLI for collection reads, filtering, and pagination.\n\nHTTP MCP:\n- Use \\`get-collection-schema\\` before assuming fields.\n- Use \\`get-tenant-context\\` to see active collections and hidden fields.\n\nCLI examples:\n- \\`01 query products --limit 10\\`\n- \\`01 query orders --where '{\"status\":{\"equals\":\"paid\"}}'\\`\n- \\`01 schema show products\\` — inspect available fields\n\nSDK (server):\n\\`\\`\\`typescript\nconst { docs } = await serverClient.collections.from('products').find({\n where: { status: { equals: 'published' } },\n sort: '-createdAt',\n limit: 10,\n})\n\\`\\`\\`\n`,\n },\n {\n dirName: '01software-order-flow',\n content: `---\nname: 01software-order-flow\ndescription: Order lifecycle reference — create, pay, fulfill, and return flows for 01.software\n---\n\nComplete order flow from creation to fulfillment.\n\nStates: pending → paid → preparing → shipped → delivered → confirmed\n\n1. Create the order from server code with the SDK/server client or Order API.\n2. Mark paid only after the payment gateway confirms.\n3. Create fulfillment with items and carrier/trackingNumber from a trusted server path.\n4. Handle returns and refunds from a trusted server path.\n\nFree orders: omit paymentId, totalAmount=0 → auto-transitions to paid\n\nHosted HTTP MCP does not expose SDK-backed order mutations. Checkout, returns,\nand granular order/fulfillment/transaction steps run through the SDK/server\nclient, API, or the \\`01\\` CLI (\\`01 order create --help\\` for full options).\n`,\n },\n {\n dirName: '01software-schema',\n content: `---\nname: 01software-schema\ndescription: Inspect 01.software collection schemas and available fields via MCP or CLI\n---\n\nInspect collection schemas to understand available fields.\n\nMCP: use \\`get-collection-schema\\` with collection\n\nCLI:\n- \\`01 schema list\\` — all available collections\n- \\`01 schema show <collection>\\` — field names, types, required status\n\nCommon collections: products, orders, customers, articles, documents, images\nUse \\`get-tenant-context\\` to see which collections are active for this tenant.\n`,\n },\n ]\n}\n\n// ── Tenant context fetch ─────────────────────────────────────────────\n\nexport async function fetchTenantContext(\n publishableKey: string,\n secretKey: string,\n): Promise<TenantContext | null> {\n try {\n const apiUrl = process.env.SOFTWARE_API_URL || 'https://api.01.software'\n // secretKey is now an opaque sk01_/pat01_ bearer token — send it directly.\n const res = await fetch(`${apiUrl}/api/tenants/context`, {\n headers: {\n 'X-Publishable-Key': publishableKey,\n Authorization: `Bearer ${secretKey}`,\n },\n })\n if (!res.ok) return null\n const data = (await res.json()) as TenantContextApiResponse\n return {\n tenantName: data.tenant?.name || '',\n features: data.features || [],\n collections: normalizeActiveCollections(data.collections),\n }\n } catch {\n return null\n }\n}\n"],"mappings":";;;AAYA,SAAS,2BACP,aACU;AACV,MAAI,MAAM,QAAQ,aAAa,MAAM,EAAG,QAAO,YAAY;AAC3D,SAAO,CAAC;AACV;AAQA,IAAM,iBAAiB;AAAA,EACrB;AAAA,EACA;AAAA,EACA;AACF;AAIO,SAAS,iBAAiB,KAA4B;AAC3D,QAAM,kBACJ,IAAI,YAAY,IAAI,SAAS,SAAS,IAClC,IAAI,SAAS,IAAI,CAAC,MAAM,KAAK,CAAC,EAAE,EAAE,KAAK,IAAI,IAC3C;AAEN,QAAM,qBACJ,IAAI,eAAe,IAAI,YAAY,SAAS,IACxC,IAAI,YAAY,KAAK,IAAI,IACzB;AAEN,SAAO,4BAAuB,IAAI,UAAU;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAW5C,eAAe;AAAA;AAAA;AAAA,EAGf,kBAAkB;AAAA;AAAA;AAAA,4CAGwB,eAAe,MAAM;AAAA;AAAA,EAE/D,eAAe,IAAI,CAAC,SAAS,OAAO,IAAI,IAAI,EAAE,KAAK,IAAI,CAAC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAiB1D;AAIO,SAAS,gBAA6D;AAC3E,SAAO;AAAA,IACL;AAAA,MACE,SAAS;AAAA,MACT,SAAS;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IA0BX;AAAA,IACA;AAAA,MACE,SAAS;AAAA,MACT,SAAS;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAoBX;AAAA,IACA;AAAA,MACE,SAAS;AAAA,MACT,SAAS;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAgBX;AAAA,EACF;AACF;AAIA,eAAsB,mBACpB,gBACA,WAC+B;AAC/B,MAAI;AACF,UAAM,SAAS,QAAQ,IAAI,oBAAoB;AAE/C,UAAM,MAAM,MAAM,MAAM,GAAG,MAAM,wBAAwB;AAAA,MACvD,SAAS;AAAA,QACP,qBAAqB;AAAA,QACrB,eAAe,UAAU,SAAS;AAAA,MACpC;AAAA,IACF,CAAC;AACD,QAAI,CAAC,IAAI,GAAI,QAAO;AACpB,UAAM,OAAQ,MAAM,IAAI,KAAK;AAC7B,WAAO;AAAA,MACL,YAAY,KAAK,QAAQ,QAAQ;AAAA,MACjC,UAAU,KAAK,YAAY,CAAC;AAAA,MAC5B,aAAa,2BAA2B,KAAK,WAAW;AAAA,IAC1D;AAAA,EACF,QAAQ;AACN,WAAO;AAAA,EACT;AACF;","names":[]}
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/browser-auth.ts
|
|
4
|
+
import { randomBytes } from "crypto";
|
|
5
|
+
import { createServer } from "http";
|
|
6
|
+
import { execFile, exec } from "child_process";
|
|
7
|
+
import { platform } from "os";
|
|
8
|
+
import { URL } from "url";
|
|
9
|
+
import pc from "picocolors";
|
|
10
|
+
var DEFAULT_WEB_URL = process.env.SOFTWARE_WEB_URL || "https://01.software";
|
|
11
|
+
var TIMEOUT_MS = 5 * 60 * 1e3;
|
|
12
|
+
function escapeHtml(s) {
|
|
13
|
+
return s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
14
|
+
}
|
|
15
|
+
function openBrowser(url) {
|
|
16
|
+
const os = platform();
|
|
17
|
+
const onError = () => {
|
|
18
|
+
console.log(
|
|
19
|
+
pc.yellow(
|
|
20
|
+
`Could not open browser automatically. Open this URL manually:
|
|
21
|
+
${url}`
|
|
22
|
+
)
|
|
23
|
+
);
|
|
24
|
+
};
|
|
25
|
+
if (os === "win32") {
|
|
26
|
+
exec(`start "" "${url}"`, (err) => {
|
|
27
|
+
if (err) onError();
|
|
28
|
+
});
|
|
29
|
+
} else {
|
|
30
|
+
const cmd = os === "darwin" ? "open" : "xdg-open";
|
|
31
|
+
execFile(cmd, [url], (err) => {
|
|
32
|
+
if (err) onError();
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
var PAGE_STYLE = `*{margin:0;box-sizing:border-box}
|
|
37
|
+
body{font-family:system-ui,-apple-system,sans-serif;display:flex;justify-content:center;align-items:center;min-height:100vh;background:#fff;color:#252525}
|
|
38
|
+
@media(prefers-color-scheme:dark){body{background:#252525;color:#f5f5f5}}
|
|
39
|
+
.card{text-align:center;padding:2rem 2.5rem;border-radius:10px;max-width:380px;width:100%}
|
|
40
|
+
.icon{width:40px;height:40px;margin:0 auto 1rem;border-radius:50%;display:flex;align-items:center;justify-content:center;font-size:1.25rem}
|
|
41
|
+
.icon.ok{background:rgba(0,0,0,.05);color:#252525}
|
|
42
|
+
.icon.err{background:rgba(220,38,38,.08);color:#dc2626}
|
|
43
|
+
@media(prefers-color-scheme:dark){.icon.ok{background:rgba(255,255,255,.08);color:#f5f5f5}}
|
|
44
|
+
h1{font-size:.875rem;font-weight:600;margin-bottom:.375rem}
|
|
45
|
+
p{font-size:.75rem;color:#737373;line-height:1.5}`;
|
|
46
|
+
var SUCCESS_HTML = `<!DOCTYPE html>
|
|
47
|
+
<html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width"><title>Login</title>
|
|
48
|
+
<style>${PAGE_STYLE}</style>
|
|
49
|
+
</head><body><div class="card"><div class="icon ok">\u2713</div><h1>Authenticated</h1><p>You can close this tab and return to the terminal.</p></div></body></html>`;
|
|
50
|
+
var ERROR_HTML = (msg) => `<!DOCTYPE html>
|
|
51
|
+
<html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width"><title>Login Error</title>
|
|
52
|
+
<style>${PAGE_STYLE}</style>
|
|
53
|
+
</head><body><div class="card"><div class="icon err">!</div><h1>Authentication failed</h1><p>${escapeHtml(msg)}</p></div></body></html>`;
|
|
54
|
+
async function exchangeCode(webUrl, code) {
|
|
55
|
+
const url = `${webUrl}/api/cli/exchange`;
|
|
56
|
+
try {
|
|
57
|
+
const res = await fetch(url, {
|
|
58
|
+
method: "POST",
|
|
59
|
+
headers: { "Content-Type": "application/json" },
|
|
60
|
+
body: JSON.stringify({ code })
|
|
61
|
+
});
|
|
62
|
+
if (!res.ok) {
|
|
63
|
+
const body = await res.text().catch(() => "");
|
|
64
|
+
console.error(
|
|
65
|
+
pc.red(
|
|
66
|
+
`Exchange failed: HTTP ${res.status} from ${url}${body ? ` \u2014 ${body.slice(0, 200)}` : ""}`
|
|
67
|
+
)
|
|
68
|
+
);
|
|
69
|
+
return null;
|
|
70
|
+
}
|
|
71
|
+
const data = await res.json();
|
|
72
|
+
if (typeof data.publishableKey !== "string" || typeof data.secretKey !== "string" || typeof data.tenantName !== "string" || typeof data.tenantId !== "string") {
|
|
73
|
+
console.error(pc.red(`Exchange failed: malformed response from ${url}`));
|
|
74
|
+
return null;
|
|
75
|
+
}
|
|
76
|
+
return {
|
|
77
|
+
publishableKey: data.publishableKey,
|
|
78
|
+
secretKey: data.secretKey,
|
|
79
|
+
tenantName: data.tenantName,
|
|
80
|
+
tenantId: data.tenantId
|
|
81
|
+
};
|
|
82
|
+
} catch (err) {
|
|
83
|
+
console.error(
|
|
84
|
+
pc.red(
|
|
85
|
+
`Exchange request to ${url} failed: ${err instanceof Error ? err.message : String(err)}`
|
|
86
|
+
)
|
|
87
|
+
);
|
|
88
|
+
return null;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
async function startBrowserAuth(options) {
|
|
92
|
+
const state = randomBytes(32).toString("hex");
|
|
93
|
+
const webUrl = options?.webUrl ?? DEFAULT_WEB_URL;
|
|
94
|
+
return new Promise((resolve, reject) => {
|
|
95
|
+
const server = createServer((req, res) => {
|
|
96
|
+
if (!req.url) {
|
|
97
|
+
res.writeHead(400).end();
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
const url = new URL(req.url, `http://localhost`);
|
|
101
|
+
if (url.pathname !== "/callback" || req.method !== "GET") {
|
|
102
|
+
res.writeHead(404).end();
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
const error = url.searchParams.get("error");
|
|
106
|
+
if (error) {
|
|
107
|
+
res.writeHead(200, { "Content-Type": "text/html; charset=utf-8", Connection: "close" }).end(ERROR_HTML(error));
|
|
108
|
+
console.error(pc.red(`Login failed: ${error}`));
|
|
109
|
+
cleanup(new Error(`Login failed: ${error}`));
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
const code = url.searchParams.get("code");
|
|
113
|
+
const receivedState = url.searchParams.get("state");
|
|
114
|
+
if (!code || !receivedState) {
|
|
115
|
+
res.writeHead(400, { "Content-Type": "text/html; charset=utf-8", Connection: "close" }).end(ERROR_HTML("Missing code or state."));
|
|
116
|
+
cleanup(new Error("Login failed: missing code or state."));
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
if (receivedState !== state) {
|
|
120
|
+
res.writeHead(403, { "Content-Type": "text/html; charset=utf-8", Connection: "close" }).end(ERROR_HTML("State mismatch."));
|
|
121
|
+
console.error(pc.red("Login failed: state mismatch."));
|
|
122
|
+
cleanup(new Error("Login failed: state mismatch."));
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
exchangeCode(webUrl, code).then((creds) => {
|
|
126
|
+
if (!creds) {
|
|
127
|
+
res.writeHead(400, { "Content-Type": "text/html; charset=utf-8", Connection: "close" }).end(ERROR_HTML("Invalid or expired code."));
|
|
128
|
+
cleanup(new Error("Login failed: code exchange failed."));
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
res.writeHead(200, { "Content-Type": "text/html; charset=utf-8", Connection: "close" }).end(SUCCESS_HTML);
|
|
132
|
+
console.log(pc.green(`
|
|
133
|
+
Logged in successfully!`));
|
|
134
|
+
console.log(pc.dim(`Tenant: ${creds.tenantName}`));
|
|
135
|
+
cleanup(null, creds);
|
|
136
|
+
});
|
|
137
|
+
});
|
|
138
|
+
let timeout;
|
|
139
|
+
let completed = false;
|
|
140
|
+
function cleanup(err, result) {
|
|
141
|
+
if (completed) return;
|
|
142
|
+
completed = true;
|
|
143
|
+
clearTimeout(timeout);
|
|
144
|
+
server.closeAllConnections?.();
|
|
145
|
+
server.close(() => {
|
|
146
|
+
if (err) {
|
|
147
|
+
reject(err);
|
|
148
|
+
} else {
|
|
149
|
+
resolve(result);
|
|
150
|
+
}
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
server.listen(0, "127.0.0.1", () => {
|
|
154
|
+
const addr = server.address();
|
|
155
|
+
if (!addr || typeof addr === "string") {
|
|
156
|
+
reject(new Error("Failed to start local server."));
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
const port = addr.port;
|
|
160
|
+
timeout = setTimeout(() => {
|
|
161
|
+
console.error(pc.red("\nLogin timed out (5 minutes). Please try again."));
|
|
162
|
+
cleanup(new Error("Login timed out"));
|
|
163
|
+
}, TIMEOUT_MS);
|
|
164
|
+
const params = new URLSearchParams({ port: String(port), state });
|
|
165
|
+
if (options?.tenantId) {
|
|
166
|
+
params.set("tenantId", options.tenantId);
|
|
167
|
+
}
|
|
168
|
+
const loginUrl = `${webUrl}/cli-auth?${params.toString()}`;
|
|
169
|
+
console.log(pc.dim("Opening browser for login..."));
|
|
170
|
+
console.log(pc.dim(`If the browser does not open, visit:
|
|
171
|
+
${loginUrl}`));
|
|
172
|
+
openBrowser(loginUrl);
|
|
173
|
+
});
|
|
174
|
+
server.on("error", (err) => {
|
|
175
|
+
reject(err);
|
|
176
|
+
});
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
export {
|
|
181
|
+
startBrowserAuth
|
|
182
|
+
};
|
|
183
|
+
//# sourceMappingURL=chunk-STM4DKVZ.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/browser-auth.ts"],"sourcesContent":["import { randomBytes } from 'node:crypto'\nimport { createServer } from 'node:http'\nimport { execFile, exec } from 'node:child_process'\nimport { platform } from 'node:os'\nimport { URL } from 'node:url'\nimport pc from 'picocolors'\n\nconst DEFAULT_WEB_URL = process.env.SOFTWARE_WEB_URL || 'https://01.software'\nconst TIMEOUT_MS = 5 * 60 * 1000 // 5 minutes\n\nfunction escapeHtml(s: string): string {\n return s\n .replace(/&/g, '&')\n .replace(/</g, '<')\n .replace(/>/g, '>')\n .replace(/\"/g, '"')\n}\n\nfunction openBrowser(url: string): void {\n const os = platform()\n\n const onError = () => {\n console.log(\n pc.yellow(\n `Could not open browser automatically. Open this URL manually:\\n${url}`,\n ),\n )\n }\n\n if (os === 'win32') {\n exec(`start \"\" \"${url}\"`, (err) => {\n if (err) onError()\n })\n } else {\n const cmd = os === 'darwin' ? 'open' : 'xdg-open'\n execFile(cmd, [url], (err) => {\n if (err) onError()\n })\n }\n}\n\nconst PAGE_STYLE = `*{margin:0;box-sizing:border-box}\nbody{font-family:system-ui,-apple-system,sans-serif;display:flex;justify-content:center;align-items:center;min-height:100vh;background:#fff;color:#252525}\n@media(prefers-color-scheme:dark){body{background:#252525;color:#f5f5f5}}\n.card{text-align:center;padding:2rem 2.5rem;border-radius:10px;max-width:380px;width:100%}\n.icon{width:40px;height:40px;margin:0 auto 1rem;border-radius:50%;display:flex;align-items:center;justify-content:center;font-size:1.25rem}\n.icon.ok{background:rgba(0,0,0,.05);color:#252525}\n.icon.err{background:rgba(220,38,38,.08);color:#dc2626}\n@media(prefers-color-scheme:dark){.icon.ok{background:rgba(255,255,255,.08);color:#f5f5f5}}\nh1{font-size:.875rem;font-weight:600;margin-bottom:.375rem}\np{font-size:.75rem;color:#737373;line-height:1.5}`\n\nconst SUCCESS_HTML = `<!DOCTYPE html>\n<html lang=\"en\"><head><meta charset=\"utf-8\"><meta name=\"viewport\" content=\"width=device-width\"><title>Login</title>\n<style>${PAGE_STYLE}</style>\n</head><body><div class=\"card\"><div class=\"icon ok\">\\u2713</div><h1>Authenticated</h1><p>You can close this tab and return to the terminal.</p></div></body></html>`\n\nconst ERROR_HTML = (msg: string) => `<!DOCTYPE html>\n<html lang=\"en\"><head><meta charset=\"utf-8\"><meta name=\"viewport\" content=\"width=device-width\"><title>Login Error</title>\n<style>${PAGE_STYLE}</style>\n</head><body><div class=\"card\"><div class=\"icon err\">!</div><h1>Authentication failed</h1><p>${escapeHtml(msg)}</p></div></body></html>`\n\ninterface ExchangeResponse {\n publishableKey: string\n secretKey: string\n tenantName: string\n tenantId: string\n}\n\nasync function exchangeCode(\n webUrl: string,\n code: string,\n): Promise<ExchangeResponse | null> {\n const url = `${webUrl}/api/cli/exchange`\n try {\n const res = await fetch(url, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ code }),\n })\n if (!res.ok) {\n const body = await res.text().catch(() => '')\n console.error(\n pc.red(\n `Exchange failed: HTTP ${res.status} from ${url}${body ? ` — ${body.slice(0, 200)}` : ''}`,\n ),\n )\n return null\n }\n const data = (await res.json()) as Partial<ExchangeResponse>\n if (\n typeof data.publishableKey !== 'string' ||\n typeof data.secretKey !== 'string' ||\n typeof data.tenantName !== 'string' ||\n typeof data.tenantId !== 'string'\n ) {\n console.error(pc.red(`Exchange failed: malformed response from ${url}`))\n return null\n }\n return {\n publishableKey: data.publishableKey,\n secretKey: data.secretKey,\n tenantName: data.tenantName,\n tenantId: data.tenantId,\n }\n } catch (err) {\n console.error(\n pc.red(\n `Exchange request to ${url} failed: ${err instanceof Error ? err.message : String(err)}`,\n ),\n )\n return null\n }\n}\n\nexport async function startBrowserAuth(options?: {\n webUrl?: string\n tenantId?: string\n}): Promise<{\n publishableKey: string\n secretKey: string\n tenantName: string\n tenantId?: string\n}> {\n const state = randomBytes(32).toString('hex')\n const webUrl = options?.webUrl ?? DEFAULT_WEB_URL\n\n return new Promise((resolve, reject) => {\n const server = createServer((req, res) => {\n if (!req.url) {\n res.writeHead(400).end()\n return\n }\n\n const url = new URL(req.url, `http://localhost`)\n\n if (url.pathname !== '/callback' || req.method !== 'GET') {\n res.writeHead(404).end()\n return\n }\n\n const error = url.searchParams.get('error')\n if (error) {\n res\n .writeHead(200, { 'Content-Type': 'text/html; charset=utf-8', Connection: 'close' })\n .end(ERROR_HTML(error))\n console.error(pc.red(`Login failed: ${error}`))\n cleanup(new Error(`Login failed: ${error}`))\n return\n }\n\n const code = url.searchParams.get('code')\n const receivedState = url.searchParams.get('state')\n\n if (!code || !receivedState) {\n res\n .writeHead(400, { 'Content-Type': 'text/html; charset=utf-8', Connection: 'close' })\n .end(ERROR_HTML('Missing code or state.'))\n cleanup(new Error('Login failed: missing code or state.'))\n return\n }\n\n if (receivedState !== state) {\n res\n .writeHead(403, { 'Content-Type': 'text/html; charset=utf-8', Connection: 'close' })\n .end(ERROR_HTML('State mismatch.'))\n console.error(pc.red('Login failed: state mismatch.'))\n cleanup(new Error('Login failed: state mismatch.'))\n return\n }\n\n exchangeCode(webUrl, code).then((creds) => {\n if (!creds) {\n res\n .writeHead(400, { 'Content-Type': 'text/html; charset=utf-8', Connection: 'close' })\n .end(ERROR_HTML('Invalid or expired code.'))\n cleanup(new Error('Login failed: code exchange failed.'))\n return\n }\n\n res\n .writeHead(200, { 'Content-Type': 'text/html; charset=utf-8', Connection: 'close' })\n .end(SUCCESS_HTML)\n\n console.log(pc.green(`\\nLogged in successfully!`))\n console.log(pc.dim(`Tenant: ${creds.tenantName}`))\n\n cleanup(null, creds)\n })\n })\n\n let timeout: ReturnType<typeof setTimeout>\n let completed = false\n\n function cleanup(err: Error | null, result?: ExchangeResponse) {\n if (completed) return\n completed = true\n clearTimeout(timeout)\n server.closeAllConnections?.()\n server.close(() => {\n if (err) {\n reject(err)\n } else {\n resolve(result!)\n }\n })\n }\n\n server.listen(0, '127.0.0.1', () => {\n const addr = server.address()\n if (!addr || typeof addr === 'string') {\n reject(new Error('Failed to start local server.'))\n return\n }\n\n const port = addr.port\n\n timeout = setTimeout(() => {\n console.error(pc.red('\\nLogin timed out (5 minutes). Please try again.'))\n cleanup(new Error('Login timed out'))\n }, TIMEOUT_MS)\n\n const params = new URLSearchParams({ port: String(port), state })\n if (options?.tenantId) {\n params.set('tenantId', options.tenantId)\n }\n const loginUrl = `${webUrl}/cli-auth?${params.toString()}`\n\n console.log(pc.dim('Opening browser for login...'))\n console.log(pc.dim(`If the browser does not open, visit:\\n${loginUrl}`))\n openBrowser(loginUrl)\n })\n\n server.on('error', (err) => {\n reject(err)\n })\n })\n}\n"],"mappings":"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","names":[]}
|