create-instant-app 1.0.43 → 1.0.44
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/index.js +44 -19
- package/package.json +4 -4
- package/template/base/python-script/.env.example +2 -0
- package/template/base/python-script/.python-version +1 -0
- package/template/base/python-script/README.md +18 -0
- package/template/base/python-script/_gitignore +5 -0
- package/template/base/python-script/instant.perms.ts +13 -0
- package/template/base/python-script/instant.schema.ts +29 -0
- package/template/base/python-script/main.py +38 -0
- package/template/base/python-script/pyproject.toml +6 -0
- package/template/base/python-script/pyrightconfig.json +4 -0
- package/template/rules/AGENTS.md +6 -3
package/dist/index.js
CHANGED
|
@@ -1,34 +1,59 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import{Command as Fe,Option as l}from"commander";import*as
|
|
3
|
-
Usage: npx create-instant-app my-app --yes`);return{...C,appName:n,base:
|
|
2
|
+
import{Command as Fe,Option as l}from"commander";import*as V from"@clack/prompts";import{spawn as je}from"child_process";import{execa as $e}from"execa";async function D(){return new Promise(e=>{let t=process.env.SHELL||"/bin/bash",n=je(t,["-i","-c","which claude"],{stdio:["ignore","pipe","ignore"]}),r="";n.stdout.on("data",i=>{r+=i.toString()}),n.on("close",i=>{if(i===0){let a=r.trim();a.includes("aliased to ")?e(a.split("aliased to ")[1]||null):e(a)}else e(null)})})}var H=async(e,t)=>{let n=await D();if(!n)throw new Error("Claude not found in path");await $e(n,[e],{stdio:"inherit",cwd:t})};import{version as Me}from"@instantdb/version";import Y from"path";var q=e=>(e.length>1&&e.endsWith("/")&&(e=e.slice(0,-1)),e),Re=/^(?:@[a-z0-9-*~][a-z0-9-*._~]*\/)?[a-z0-9-~][a-z0-9-._~]*$/,X=e=>{let n=q(e).split("/"),r=n[n.length-1];if(r==="."){let o=Y.resolve(process.cwd());r=Y.basename(o)}let i=n.findIndex(o=>o.startsWith("@"));n.findIndex(o=>o.startsWith("@"))!==-1&&(r=n.slice(i).join("/"));let a=n.filter(o=>!o.startsWith("@")).join("/");return[r,a]},E=e=>e.trim(),U=e=>{let t=q(e),n=t.split("/"),r=n.findIndex(a=>a.startsWith("@")),i=n[n.length-1];if(n.findIndex(a=>a.startsWith("@"))!==-1&&(i=n.slice(r).join("/")),!(t==="."||Re.test(i??"")))return"App name must consist of only lowercase alphanumeric characters, '-', and '_'"};import{renderUnwrap as P,UI as g}from"instant-cli/ui";var C={base:"next-js-app-dir",appName:"Awesome Todos",ruleFiles:null,createRepo:!0,prompt:null,app:null,token:null,yes:!1,temporary:!1},Z=e=>e.base||e.viteReact&&"vite-react"||e.vanilla&&"vite-vanilla"||e.next&&"next-js-app-dir"||e.expo&&"expo"||e.sv&&"sveltekit"||e.vue&&"vue-vite"||e.python&&"python-script"||null,Q=e=>e.cursor&&"cursor"||e.claude&&"claude"||e.codex&&"codex"||e.gemini&&"gemini"||e.rules&&"codex"||null,ee=async()=>{let e=C,t=new Fe().name("Create Instant App").description("A CLI for creating web/mobile applications with InstantDB").argument("[dir]","The name of the application, as well as the name of the directory to create").addOption(new l("-b --base <template>","The base template to scaffold from").choices(["next-js-app-dir","vite-react","vite-vanilla","expo","bun-react","tanstack-start","tanstack-start-with-tanstack-query","solidjs-vite","sveltekit","vue-vite","vercel-ai-sdk","ai-chat","python-script"])).addOption(new l("-g --git","Create a git repo in the new project").default(!0)).addOption(new l("--expo","Use the Expo starter template").default(!1)).addOption(new l("--next","Use the NextJS starter template").default(!1)).addOption(new l("--vanilla","Use the vanilla JS starter template").default(!1)).addOption(new l("--vite-react","Use the Vite + React starter template").default(!1)).addOption(new l("--sv","Use the SvelteKit starter template").default(!1)).addOption(new l("--vue","Use the Vue + Vite starter template").default(!1)).addOption(new l("--python","Use the Python script starter template").default(!1)).addOption(new l("--no-git","Don't create a git repo in the new project")).addOption(new l("--cursor","Include a Cursor rules file in the scaffold")).addOption(new l("--claude","Include a CLAUDE.md file in the scaffold")).addOption(new l("--codex","Include an AGENTS.md file in the scaffold")).addOption(new l("--gemini","Include a GEMINI.md file in the scaffold")).addOption(new l("--rules","Include an AGENTS.md file in the scaffold")).addOption(new l("--ai","Create a new InstantDB app based off of a prompt. (requires Claude Code)")).addOption(new l("-a --app <app-id>","Link to an existing InstantDB app by ID (requires login or --token)")).addOption(new l("-t --token <token>","Auth token override (use with --app when not logged in)")).addOption(new l("-y --yes","Use all defaults (requires project name as first argument)").default(!1)).addOption(new l("--temp","Create a temporary app by default").default(!1)).version(Me).parse(process.argv),n=t.args[0]&&E(t.args[0]);if(n){let a=U(n);if(a)throw new Error("Invalid app name: "+a);e.appName=n}let r=t.opts();if(r.app&&r.temp)throw new Error("--app and --temp cannot be used together");if(r.yes){if(r.ai)throw new Error("--yes is not supported with --ai");if(!n)throw new Error(`When using --yes, you must specify a project name as the first argument.
|
|
3
|
+
Usage: npx create-instant-app my-app --yes`);return{...C,appName:n,base:Z(r)??C.base,ruleFiles:Q(r)??"claude",createRepo:r.git??C.createRepo,app:r.app??null,token:r.token??null,yes:!0,temporary:r.temp??!1}}if(r.ai&&!await D())throw new Error("--ai only works with Claude Code, but we couldn't find it in your machine. Install it first, and run it again : ). Alternatively you can scaffold out a project without --ai");return{...await V.group({appName:async()=>{if(n)return n.trim();let a=await P(new g.TextInput({prompt:"What will your project/folder be called?",placeholder:"awesome-todos",defaultValue:"awesome-todos",validate:s=>U(E(s)),modifyOutput:g.ciaModifier()}));return E(a)},prompt:async()=>r.ai?await P(new g.TextInput({prompt:"What would you like to create?",placeholder:"Create an app that...",modifyOutput:g.modifiers.piped([g.ciaModifier()])})):null,base:async({results:a})=>{let o=Z(r);return o||(a.prompt?P(new g.Select({promptText:"What framework would you like to use?",options:[{value:"next-js-app-dir",label:"Web: Next.js"},{value:"vite-react",label:"Web: Vite React"},{value:"expo",label:"Mobile: Expo"}],defaultValue:"next-js-app-dir",modifyOutput:g.modifiers.piped([g.ciaModifier()])})):P(new g.Select({promptText:"What framework would you like to use?",options:[{value:"next-js-app-dir",label:"Web: Next.js"},{value:"expo",label:"Mobile: Expo"},{value:"vite-react",label:"Vite: React",secondary:!0},{value:"vite-vanilla",label:"Vite: Vanilla TS",secondary:!0},{value:"tanstack-start",label:"Tanstack Start",secondary:!0},{value:"bun-react",label:"Bun + React",secondary:!0},{value:"solidjs-vite",label:"Vite: SolidJS",secondary:!0},{value:"sveltekit",label:"SvelteKit",secondary:!0},{value:"vue-vite",label:"Vite: Vue",secondary:!0},{value:"vercel-ai-sdk",label:"Vercel AI SDK App Builder + SSR",secondary:!0},{value:"ai-chat",label:"Vercel AI SDK Chat App",secondary:!0},{value:"python-script",label:"Python: Script",secondary:!0}],defaultValue:"next-js-app-dir",modifyOutput:g.modifiers.piped([g.ciaModifier()])})))},ruleFiles:async({results:a})=>{if(a.prompt)return"claude";let o=Q(r);return o||P(new g.Select({promptText:"Which AI tool would you like to add rule files for?",options:[{value:"claude",label:"Claude"},{value:"cursor",label:"Cursor"},{value:"codex",label:"Codex"},{value:"gemini",label:"Gemini"},{value:"zed",label:"Zed"},{value:"windsurf",label:"Windsurf"},{value:null,label:"None"}],defaultValue:"claude",modifyOutput:g.ciaModifier()}))},createRepo:async()=>r.git!==void 0?r.git:!0,temporary:async()=>r.temp??!1},{onCancel(){process.exit(1)}}),app:r.app??null,token:r.token??null,yes:!1}};import J from"path";import M from"fs-extra";import{log as Ee,outro as At}from"@clack/prompts";import{intro as De}from"@clack/prompts";import te from"chalk";var Ue=` _ _ _
|
|
4
4
|
\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588 (_) | | | |
|
|
5
5
|
\u2588 \u2588\u2588\u2588\u2588 _ _ __ ___| |_ __ _ _ _ _| |_
|
|
6
6
|
\u2588 \u2588\u2588\u2588\u2588 | | '_ \\/ __| __/ _\\\`| '_ \\| __|
|
|
7
7
|
\u2588 \u2588\u2588\u2588\u2588 | | | | \\__ \\ || (_| | | | | |_
|
|
8
|
-
\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588 |_|_| |_|___/\\__\\__,_|_| |_|\\__|`,
|
|
9
|
-
`+
|
|
10
|
-
`).map(e=>`${
|
|
11
|
-
`))};import
|
|
12
|
-
enable-pre-post-scripts=true`),e.base==="expo"){let
|
|
13
|
-
Using git to clone from main...`),
|
|
8
|
+
\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588 |_|_| |_|___/\\__\\__,_|_| |_|\\__|`,ne=()=>{De(`
|
|
9
|
+
`+Ue.split(`
|
|
10
|
+
`).map(e=>`${te.gray("\u2502")}${te.hex("#EA580D").bold(" "+e)}`).join(`
|
|
11
|
+
`))};import m from"path";import Le from"tiged";import d from"fs-extra";import re from"path";import{fileURLToPath as Ve}from"url";var Be=Ve(import.meta.url),Ge=re.dirname(Be),O=re.join(Ge,"../");import I from"chalk";var j=e=>{if(e==="bun-react")return"bun";let t=process.env.npm_config_user_agent;return t?t.startsWith("yarn")?"yarn":t.startsWith("pnpm")?"pnpm":t.startsWith("bun")?"bun":"npm":"npm"};import{renderUnwrap as oe,UI as w}from"instant-cli/ui";import We from"slugify";import ze from"ignore";var ae=async(e,t)=>{let n=m.resolve(process.cwd(),t);if(d.existsSync(n))if(d.readdirSync(n).length===0)w.log(`${I.cyan.bold(e.appName)} exists but is empty, continuing...`,w.ciaModifier(null));else{let a=await oe(new w.Select({promptText:I.redBright(`${I.bold("Warning:")} ${I.bold(e.appName)} already exists and isn't empty. How would you like to proceed?`),options:[{label:"Abort installation",value:"abort"},{label:"Clear the directory and continue installation",value:"clear"}],defaultValue:"abort",modifyOutput:w.ciaModifier()}));a==="abort"&&(w.log("Aborting installation..."),process.exit(1)),a==="clear"&&d.emptyDirSync(n)}let r=He({projectDir:n,baseTemplateName:e.base}),i=e.appName==="."?"App":I.hex("#EA570B").bold(e.appName);if(await oe(new w.Spinner({promise:r,workingText:"Scaffolding project files...",doneText:`Successfully scaffolded ${i}!`,errorText:"Error scaffolding project files",modifyOutput:w.ciaModifier(null)})),d.pathExistsSync(m.join(n,"pnpm-lock.yaml"))&&d.removeSync(m.join(n,"pnpm-lock.yaml")),d.pathExistsSync(m.join(n,"bun.lock"))&&d.removeSync(m.join(n,"bun.lock")),j(e.base)==="pnpm"&&e.base==="expo"&&await d.appendFile(m.join(n,".npmrc"),`node-linker=hoisted
|
|
12
|
+
enable-pre-post-scripts=true`),e.base==="expo"){let a=We.default(t);B(m.join(n,"app.json"),'"name": "expo-template"',`"name": "${t}"`),B(m.join(n,"app.json"),'"slug": "expo-template"',`"slug": "${a}"`),B(m.join(n,"app/_layout.tsx"),'"My Instant App"',`"${t}"`)}return n},B=(e,t,n)=>{let i=d.readFileSync(e,"utf8").replaceAll(t,n);d.writeFileSync(e,i)},Je=async({projectDir:e,baseTemplateName:t})=>{let n=`instantdb/instant/examples/${t}`;await Le(n,{mode:"tar",disableCache:!0}).clone(e)};async function Ke(e,t){let n=ze();n.add(".git");try{let r=m.join(e,".gitignore"),i=await d.readFile(r,"utf8");n.add(i)}catch{}await d.copy(e,t,{filter:r=>{let i=m.relative(e,r);return i===""?!0:!n.ignores(i)}})}var He=async({projectDir:e,baseTemplateName:t})=>{let n=m.join(O,`template/base/${t}`),r=!!process.env.INSTANT_CLI_DEV&&!!process.env.INSTANT_REPO_FOLDER,i=d.pathExistsSync(n),a=r?"dev":i?"bundled-template":"tiged";if(a==="bundled-template"){d.copySync(n,e);let o=m.join(e,"_gitignore"),s=m.join(e,".gitignore");d.pathExistsSync(o)&&d.renameSync(o,s);return}if(a==="dev"){let o=process.env.INSTANT_REPO_FOLDER;if(!o)throw new Error("INSTANT_REPO_FOLDER is required when using repo-examples scaffolding.");let s=m.join(o,"examples",t);await Ke(s,e);return}process.env.INSTANT_CLI_DEV&&!process.env.INSTANT_REPO_FOLDER&&w.log(I.bold.yellowBright(`WARNING: INSTANT_CLI_DEV is TRUE but no INSTANT_REPO_FOLDER is set.
|
|
13
|
+
Using git to clone from main...`),w.ciaModifier(null)),await Je({projectDir:e,baseTemplateName:t})};import{execa as Ye}from"execa";import{renderUnwrap as qe,UI as $}from"instant-cli/ui";var ie=async(e,t)=>{let n=Ye(e,["install"],{cwd:t}),r=await qe(new $.Spinner({promise:n,workingText:`Installing dependencies with ${e}...`,doneText:"Successfully installed dependencies!",modifyOutput:$.ciaModifier(null)}));r.exitCode!==0&&($.log(r.stderr,$.ciaModifier(null)),process.exit(1))};import Ce from"chalk";import Xe from"fs-extra";import se from"path";var Ze={claude:"CLAUDE.md",gemini:"GEMINI.md",cursor:"AGENTS.md",codex:"AGENTS.md",zed:"AGENTS.md",windsurf:"AGENTS.md"},pe=({projectDir:e,ruleFilesToAdd:t})=>{t!==null&&Xe.copyFileSync(se.join(O,"template/rules/AGENTS.md"),se.join(e,Ze[t]))};import{execSync as L}from"child_process";import G from"path";import*as ce from"@clack/prompts";import R from"chalk";import{execa as v}from"execa";import de from"fs-extra";import{renderUnwrap as le,UI as k}from"instant-cli/ui";var Qe=e=>{try{return L("git --version",{cwd:e}),!0}catch{return!1}},et=e=>de.existsSync(G.join(e,".git")),tt=async e=>{try{return await v("git",["rev-parse","--is-inside-work-tree"],{cwd:e,stdout:"ignore"}),!0}catch{return!1}},nt=()=>{let t=L("git --version").toString().trim().split(" ")[2],n=t?.split(".")[0],r=t?.split(".")[1];return{major:Number(n),minor:Number(r)}},rt=()=>L("git config --global init.defaultBranch || echo main").toString().trim(),ue=async e=>{if(!Qe(e)){ce.log.warn("Git is not installed. Skipping Git initialization.");return}let t=et(e),n=await tt(e),r=G.parse(e).name;if(n&&t){if(!await le(new k.Confirmation({promptText:`${R.redBright.bold("Warning:")} Git is already initialized in "${r}". Initializing a new git repository would delete the previous history. Would you like to continue anyways?`,defaultValue:!1,modifyOutput:k.ciaModifier()})))return;de.removeSync(G.join(e,".git"))}else if(n&&!t&&!await le(new k.Confirmation({promptText:`${R.redBright.bold("Warning:")} "${r}" is already in a git worktree.
|
|
14
14
|
Would you still like to initialize a new git repository in this directory?
|
|
15
|
-
`,defaultValue:!1,modifyOutput:k.ciaModifier()})))return;try{let i=
|
|
16
|
-
INSTANT_APP_ADMIN_TOKEN=${r}`;
|
|
17
|
-
`),
|
|
15
|
+
`,defaultValue:!1,modifyOutput:k.ciaModifier()})))return;try{let i=rt(),{major:a,minor:o}=nt();a<2||a==2&&o<28?(await v("git",["init"],{cwd:e}),await v("git",["symbolic-ref","HEAD",`refs/heads/${i}`],{cwd:e})):await v("git",["init",`--initial-branch=${i}`],{cwd:e}),await v("git",["add","."],{cwd:e}),await v("git",["commit","-m","Initial commit (create-instant-app)"],{cwd:e}),k.log(R.dim(`${R.green("\u2713")} Git repository initialized successfully.`),k.ciaModifier(null))}catch{}};import at from"env-paths";import{mkdir as it,readFile as st,writeFile as pt}from"node:fs/promises";import lt from"open";import{join as ct}from"node:path";import{randomUUID as ge}from"node:crypto";import{version as ot}from"@instantdb/version";var me=!!process.env.INSTANT_CLI_DEV,fe=me?"http://localhost:3000":"https://instantdb.com",W=process.env.INSTANT_CLI_API_URI||(me?"http://localhost:8888":"https://api.instantdb.com");async function y({path:e,body:t,method:n="GET",authToken:r,metadata:i}){let o={"Content-Type":"application/json","X-Instant-Source":"create-instant-app","X-Instant-Version":ot,"X-Instant-Command":"create"};r&&(o.Authorization=`Bearer ${r}`),i&&(o["X-Instant-Metadata"]=JSON.stringify(i));let s=await fetch(`${W}${e}`,{method:n??"GET",headers:o,body:t?JSON.stringify(t):void 0,signal:AbortSignal.timeout(3e5)}),p;try{p=await s.json()}catch{p=null}if(!s.ok){let c=p.message||p.hint?.errors?.[0]?.message||"There was an error";throw new Error(c)}return p}import{renderUnwrap as A,UI as u}from"instant-cli/ui";function T(e){return e.replace(/[-_]/g," ").replace(/\b\w/g,t=>t.toUpperCase())}var dt=!!process.env.INSTANT_CLI_DEV,ut=!!process.env.INSTANT_CLI_FORCE_EPHEMERAL;function we(){let e=`instantdb-${dt?"dev":"prod"}`,{config:t}=at(e);return{authConfigFilePath:ct(t,"a"),appConfigDirPath:t}}var he=async(e,t,n,r)=>{let i=ge(),a=ge(),o={id:i,title:e,admin_token:a,org_id:n};return r?.rules&&(o.rules=r.rules),r?.schema&&(o.schema=r.schema),await y({method:"POST",authToken:t,path:"/dash/apps",body:o,metadata:r}),{appId:i,adminToken:a,source:"created"}},z=(e,t,n)=>{y({method:"POST",path:`/dash/apps/${e}/track-import`,authToken:t,metadata:n}).catch(()=>{})},ye=async e=>await y({method:"GET",path:"/dash",authToken:e}),Te=async(e,t)=>await y({method:"GET",path:`/dash/orgs/${t}`,authToken:e});var mt=async(e,t)=>{try{let n=await ye(e),r=n.apps.find(i=>i.id===t);if(r)return{appId:r.id,adminToken:r.admin_token};for(let i of n.orgs){let{apps:a}=await Te(e,i.id),o=a.find(s=>s.id===t);if(o)return{appId:o.id,adminToken:o.admin_token}}return null}catch{return null}},ft=async(e,t)=>{try{return await y({method:"GET",path:`/dash/apps/${e}/schema/pull`,authToken:t}),!0}catch{return!1}},gt=async()=>ut?null:process.env.INSTANT_CLI_AUTH_TOKEN?process.env.INSTANT_CLI_AUTH_TOKEN:await st(we().authConfigFilePath,"utf-8").catch(()=>null),F=async(e,t)=>{let n=await y({authToken:null,method:"POST",path:"/dash/apps/ephemeral",body:{title:e,schema:t?.schema,rules:{$users:{view:"true"},$files:{allow:{view:"true",create:"true",delete:"true"}}}},metadata:t});return{appId:n.app.id,adminToken:n.app["admin-token"]}},be=async(e,t,n)=>{let r=await gt();if(t?.yes&&!t?.app)if(r&&!t.temporary){let{appId:s,adminToken:p}=await he(T(e),r,void 0,n);return{appId:s,adminToken:p,approach:"create"}}else{let{appId:s,adminToken:p}=await F(T(e),n);return{appId:s,adminToken:p,approach:"ephemeral"}}if(t?.temporary){let s=await A(new u.TextInput({defaultValue:T(e),prompt:"Enter a name for your temporary app:",placeholder:T(e),modifyOutput:u.ciaModifier()}));return{...await F(s,n),approach:"ephemeral"}}if(t?.app){if(t.token){if(!await ft(t.app,t.token))throw new Error("Invalid app ID and token combination. Please verify both the app ID and token are correct.");return u.log(`Linking to app: ${t.app}`,u.ciaModifier(null)),z(t.app,t.token,n),{appId:t.app,adminToken:t.token,approach:"import"}}if(r){let s=await mt(r,t.app);if(!s)throw new Error(`You don't have access to app "${t.app}". Please check the app ID or use --token to provide a token.`);return u.log(`Linking to app: ${t.app}`,u.ciaModifier(null)),z(s.appId,r,n),{appId:s.appId,adminToken:s.adminToken,approach:"import"}}throw new Error(`You must be logged in or provide --token when using --app. Either run 'npx instant-cli login' first, or use: --app ${t.app} --token <token>`)}if(!r){let s=await A(new u.Select({promptText:"You are not logged in.",options:[{label:"Login to your Instant account",value:"login"},{label:"Create a temporary app",value:"ephemeral"},{label:"Create an app later",value:"skip"}],modifyOutput:u.ciaModifier()}));if(s==="login"){let p=await y({authToken:null,method:"POST",path:"/dash/cli/auth/register"}).catch(Oe=>{throw new Error("Failed to register",{cause:Oe})}),{secret:c,ticket:h}=p;lt(`${fe}/dash?ticket=${h}`);let _=wt({secret:c}),K=await A(new u.Spinner({promise:_,workingText:"Waiting for login in browser",disappearWhenDone:!0,modifyOutput:u.ciaModifier(null)}));await yt(K.token),r=K.token}if(s==="skip")return u.log("Skipping app link step",u.ciaModifier(null)),null;if(s==="ephemeral"){let p=await A(new u.TextInput({defaultValue:T(e),prompt:"Enter a name for your temporary app:",placeholder:T(e),modifyOutput:u.ciaModifier()}));return{...await F(p,n),approach:"ephemeral"}}}if(!r)return null;let i=await ye(r).catch(s=>{throw new Error("Failed to fetch dashboard",{cause:s})}),a=i.orgs.filter(s=>s.role!=="app-member");i.orgs=a;let o=await A(new u.AppSelector({startingMenuIndex:0,defaultAppName:T(e),allowCreate:!0,allowEphemeral:!0,api:{getDash(){return i},createEphemeralApp(s){return F(s,n)},getAppsForOrg:async s=>{let{apps:p}=await Te(r,s);return{apps:p}},createApp:async(s,p)=>{let{appId:c,adminToken:h}=await he(s,r,p,n);return{appId:c,adminToken:h}}},modifyOutput:u.ciaModifier()}));return o?.approach==="import"&&z(o.appId,r,n),o};function ht(e){return new Promise(t=>setTimeout(t,e))}async function wt({secret:e}){for(let t=1;t<=120;t++){await ht(1e3);let n=await fetch(`${W}/dash/cli/auth/check`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({secret:e})});if(!(!n.ok&&(await n.json()).hint.errors?.[0]?.issue==="waiting-for-user")&&n.ok)return n.json()}throw new Error("Timed out waiting for login")}async function yt(e){let t=we();return await it(t.appConfigDirPath,{recursive:!0}),pt(t.authConfigFilePath,e,"utf-8")}import Tt from"fs-extra";import bt from"path";var It={"next-js-app-dir":"NEXT_PUBLIC_INSTANT_APP_ID","vite-react":"VITE_INSTANT_APP_ID","vite-vanilla":"VITE_INSTANT_APP_ID",expo:"EXPO_PUBLIC_INSTANT_APP_ID","tanstack-start":"VITE_INSTANT_APP_ID","bun-react":"BUN_PUBLIC_INSTANT_APP_ID","solidjs-vite":"VITE_INSTANT_APP_ID",sveltekit:"VITE_INSTANT_APP_ID","vue-vite":"VITE_INSTANT_APP_ID","tanstack-start-with-tanstack-query":"VITE_INSTANT_APP_ID","vercel-ai-sdk":"NEXT_PUBLIC_INSTANT_APP_ID","ai-chat":"NEXT_PUBLIC_INSTANT_APP_ID","python-script":"INSTANT_APP_ID"},Ie=(e,t,n,r)=>{let i=bt.join(t,".env"),o=`${It[e.base]}=${n}
|
|
16
|
+
INSTANT_APP_ADMIN_TOKEN=${r}`;Tt.writeFileSync(i,o)};import{stdin as b,stdout as ke}from"process";import{setRawModeWindowsFriendly as ve}from"instant-cli/ui";async function kt(){return new Promise(e=>{if(!ke.isTTY||!b.isTTY){e(null);return}let t=b.isRaw;t||ve(b,!0);let n=setTimeout(()=>{i(),e(null)},100),r="",i=()=>{clearTimeout(n),b.removeListener("data",a),!t&&b.isTTY&&ve(b,!1)},a=o=>{let s=o.toString();r+=s;let p=r.match(/\x1b\]11;rgb:([0-9a-fA-F]+)\/([0-9a-fA-F]+)\/([0-9a-fA-F]+)(?:\x1b\\|\x07)/);if(p){i();let[,c,h,_]=p;e(`rgb:${c}/${h}/${_}`)}};b.on("data",a),ke.write("\x1B]11;?\x1B\\")})}function vt(e){let t=e.match(/rgb:([0-9a-fA-F]+)\/([0-9a-fA-F]+)\/([0-9a-fA-F]+)/);if(!t)return null;let[,n,r,i]=t,a=Math.round(parseInt(n,16)/257),o=Math.round(parseInt(r,16)/257),s=Math.round(parseInt(i,16)/257);return{r:a,g:o,b:s}}function xt(e){return(.299*e.r+.587*e.g+.114*e.b)/255>.5}async function xe(){try{let e=await kt();if(!e)return"unknown";let t=vt(e);return t?xt(t)?"light":"dark":"unknown"}catch(e){return console.error("Error detecting terminal theme:",e),"unknown"}}import S from"chalk";var f=(e,t)=>e==="light"?S.bgYellowBright(t):S.bgBlackBright(t),_e=(e,t=console.log,n=!1)=>{let r=(process.stdout.columns||80)-4,i=e.split(`
|
|
17
|
+
`),a=!0;i.forEach(o=>{if(o.length===0){a?(t(""),a=!1):console.log(S.gray("\u2502"));return}for(let s=0;s<o.length;s+=r){let p=o.slice(s,s+r);s+r<o.length&&p.length===r&&(p=p+"-"),a?(t(p),a=!1):console.log(S.gray("\u2502 ")+p)}}),n&&console.log(S.gray("\u2502"))};var N="\x1B[?25h";import{execa as St}from"execa";import Pe from"fs-extra";import{permsTypescriptFileToCode as _t,schemaTypescriptFileToInstantSchema as Pt}from"@instantdb/platform";function Ae(e){for(let t of["src/instant.perms.ts","instant.perms.ts"])try{let n=Pe.readFileSync(`${e}/${t}`,"utf8");return{code:_t(n,t)}}catch{}return null}function Se(e){for(let t of["src/instant.schema.ts","instant.schema.ts"])try{let n=Pe.readFileSync(`${e}/${t}`,"utf8");return Pt(n,t)}catch{}return null}import{UI as x}from"instant-cli/ui";var Ne=e=>{if(e){if(e.approach==="import"){x.log(`Imported app with App ID: ${e.appId}`,x.ciaModifier());return}e.approach==="create"&&x.log(`Created new app with App ID: ${e.appId}`,x.ciaModifier()),e.approach==="ephemeral"&&x.log(`Created temporary app with App ID: ${e.appId}`,x.ciaModifier())}};var Nt=async()=>{process.argv.some(c=>["-h","--help","--version","-V"].includes(c))||ne();let e=await xe(),t=await ee(),[n,r]=X(t.appName),i=t.base==="python-script",a=j(t.base),o=await ae(t,r);pe({projectDir:o,ruleFilesToAdd:t.ruleFiles});let s={template:t.base,aiTool:t.ruleFiles??"none",usedAiPrompt:!!t.prompt,rules:Ae(o),schema:Se(o)},p=await be(r==="."?n:r,t,s);if(Ne(p),p&&Ie(t,o,p.appId,p.adminToken),i){let c=n.replace(/^@/,"").replace(/\//g,"-"),h=J.join(o,"pyproject.toml"),_=M.readFileSync(h,"utf-8");M.writeFileSync(h,_.replace(/^name = .*$/m,`name = "${c}"`))}else{let c=M.readJSONSync(J.join(o,"package.json"));if(c.name=n,a!=="bun"){let{stdout:h}=await St(a,["-v"],{cwd:o});c.packageManager=`${a}@${h.trim()}`}M.writeJSONSync(J.join(o,"package.json"),c,{spaces:2}),await ie(a,o)}if(t.createRepo&&await ue(o),t.prompt&&(await H(t.prompt,o),process.stdout.write(N)),At("Done!"),i)p?(console.log(`
|
|
18
18
|
\u{1F389} Success! Your project is ready to go!
|
|
19
19
|
|
|
20
20
|
To get started:
|
|
21
|
-
1. ${
|
|
22
|
-
2. ${
|
|
23
|
-
|
|
21
|
+
1. ${f(e,"cd "+r)}
|
|
22
|
+
2. ${f(e,"uv sync")}
|
|
23
|
+
3. ${f(e,"uv run python main.py")}
|
|
24
|
+
|
|
25
|
+
To push schema and permissions:
|
|
26
|
+
${f(e,"npx instant-cli push")}
|
|
27
|
+
`),p.approach==="ephemeral"&&console.log(`
|
|
28
|
+
An ephemeral app has been created and added to your .env file.
|
|
29
|
+
It will expire in two weeks. For a permanent app, sign in and use ${f(e,"npx instant-cli claim")}
|
|
30
|
+
`)):console.log(`
|
|
31
|
+
\u{1F389} Success! Your project is ready to go!
|
|
32
|
+
|
|
33
|
+
To get started:
|
|
34
|
+
1. ${f(e,"cd "+r)}
|
|
35
|
+
2. Create a new app on ${Ce.underline("www.instantdb.com")}
|
|
36
|
+
3. Add your APP_ID and admin token to the .env file
|
|
37
|
+
4. ${f(e,"uv sync")}
|
|
38
|
+
5. ${f(e,"uv run python main.py")}
|
|
39
|
+
|
|
40
|
+
To push schema and permissions:
|
|
41
|
+
${f(e,"npx instant-cli push")}
|
|
42
|
+
`);else{let c=t.base==="expo"?"start":"dev";p?(console.log(`
|
|
43
|
+
\u{1F389} Success! Your project is ready to go!
|
|
44
|
+
|
|
45
|
+
To get started:
|
|
46
|
+
1. ${f(e,"cd "+r)}
|
|
47
|
+
2. ${f(e,a+" run "+c)}
|
|
48
|
+
`),p.approach==="ephemeral"&&console.log(`
|
|
24
49
|
An ephemeral app has been created and added to your .env file.
|
|
25
|
-
It will expire in two weeks. For a permanent app, sign in and use ${
|
|
50
|
+
It will expire in two weeks. For a permanent app, sign in and use ${f(e,"npx instant-cli claim")}
|
|
26
51
|
`)):console.log(`
|
|
27
52
|
\u{1F389} Success! Your project is ready to go!
|
|
28
53
|
|
|
29
54
|
To get started:
|
|
30
|
-
1. ${
|
|
31
|
-
2. Create a new app on ${
|
|
55
|
+
1. ${f(e,"cd "+r)}
|
|
56
|
+
2. Create a new app on ${Ce.underline("www.instantdb.com")}
|
|
32
57
|
3. Add your APP_ID to the .env file
|
|
33
|
-
4. ${
|
|
34
|
-
`)
|
|
58
|
+
4. ${f(e,a+" run "+c)}
|
|
59
|
+
`)}process.stdout.write(N),process.exit(0)};Nt().catch(e=>{Ee.error("Aborting installation..."),_e(e.message,Ee.error),process.stdout.write(N),process.exit(1)});process.on("SIGINT",()=>{process.stdout.write(N),process.exit(0)});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "create-instant-app",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.44",
|
|
4
4
|
"description": "Scaffold a new web/mobile app with InstantDB",
|
|
5
5
|
"homepage": "https://github.com/instantdb/instant/tree/main/client/packages/create-instant-app",
|
|
6
6
|
"repository": {
|
|
@@ -35,9 +35,9 @@
|
|
|
35
35
|
"slugify": "^1.6.6",
|
|
36
36
|
"sort-package-json": "^2.10.0",
|
|
37
37
|
"tiged": "^2.12.7",
|
|
38
|
-
"@instantdb/platform": "1.0.
|
|
39
|
-
"@instantdb/version": "1.0.
|
|
40
|
-
"instant-cli": "1.0.
|
|
38
|
+
"@instantdb/platform": "1.0.44",
|
|
39
|
+
"@instantdb/version": "1.0.44",
|
|
40
|
+
"instant-cli": "1.0.44"
|
|
41
41
|
},
|
|
42
42
|
"devDependencies": {
|
|
43
43
|
"@anthropic-ai/sdk": "^0.60.0",
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
3.10
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# Welcome to your InstantDB Python script 👋
|
|
2
|
+
|
|
3
|
+
[InstantDB Docs](https://www.instantdb.com/docs/start-python)
|
|
4
|
+
|
|
5
|
+
This is a Python script scaffolded with create-instant-app.
|
|
6
|
+
|
|
7
|
+
To install dependencies:
|
|
8
|
+
`uv sync`
|
|
9
|
+
|
|
10
|
+
To run the script:
|
|
11
|
+
`uv run python main.py`
|
|
12
|
+
|
|
13
|
+
To push schema and permission changes (requires Node):
|
|
14
|
+
`npx instant-cli push`
|
|
15
|
+
|
|
16
|
+
`instant.schema.ts` and `instant.perms.ts` are included so you can manage your app with the Instant CLI.
|
|
17
|
+
|
|
18
|
+
Got any feedback or questions? Join our [Discord](https://discord.gg/hgVf9R6SBm)
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
// Docs: https://www.instantdb.com/docs/permissions
|
|
2
|
+
|
|
3
|
+
import type { InstantRules } from "@instantdb/core";
|
|
4
|
+
|
|
5
|
+
const rules = {
|
|
6
|
+
/**
|
|
7
|
+
* Welcome to Instant's permission system!
|
|
8
|
+
* Right now your rules are empty. To start filling them in, check out the docs:
|
|
9
|
+
* https://www.instantdb.com/docs/permissions
|
|
10
|
+
*/
|
|
11
|
+
} satisfies InstantRules;
|
|
12
|
+
|
|
13
|
+
export default rules;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
// Docs: https://www.instantdb.com/docs/modeling-data
|
|
2
|
+
|
|
3
|
+
import { i } from "@instantdb/core";
|
|
4
|
+
|
|
5
|
+
const _schema = i.schema({
|
|
6
|
+
entities: {
|
|
7
|
+
$files: i.entity({
|
|
8
|
+
path: i.string().unique().indexed(),
|
|
9
|
+
url: i.string(),
|
|
10
|
+
}),
|
|
11
|
+
$users: i.entity({
|
|
12
|
+
email: i.string().unique().indexed().optional(),
|
|
13
|
+
}),
|
|
14
|
+
todos: i.entity({
|
|
15
|
+
text: i.string(),
|
|
16
|
+
done: i.boolean(),
|
|
17
|
+
createdAt: i.number(),
|
|
18
|
+
}),
|
|
19
|
+
},
|
|
20
|
+
links: {},
|
|
21
|
+
rooms: {},
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
type _AppSchema = typeof _schema;
|
|
25
|
+
interface AppSchema extends _AppSchema {}
|
|
26
|
+
const schema: AppSchema = _schema;
|
|
27
|
+
|
|
28
|
+
export type { AppSchema };
|
|
29
|
+
export default schema;
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
"""Hello world InstantDB script.
|
|
2
|
+
|
|
3
|
+
Reads INSTANT_APP_ID and INSTANT_APP_ADMIN_TOKEN from the environment. Run:
|
|
4
|
+
|
|
5
|
+
uv run python main.py
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import time
|
|
9
|
+
|
|
10
|
+
from dotenv import load_dotenv
|
|
11
|
+
from instantdb import Instant, id
|
|
12
|
+
|
|
13
|
+
load_dotenv()
|
|
14
|
+
|
|
15
|
+
db = Instant()
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def main() -> None:
|
|
19
|
+
todo_id = id()
|
|
20
|
+
db.transact(
|
|
21
|
+
db.tx.todos[todo_id].update(
|
|
22
|
+
{
|
|
23
|
+
"text": "Hello from Python!",
|
|
24
|
+
"done": False,
|
|
25
|
+
"createdAt": int(time.time() * 1000),
|
|
26
|
+
}
|
|
27
|
+
)
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
result = db.query({"todos": {}})
|
|
31
|
+
print(f"Found {len(result['todos'])} todo(s):")
|
|
32
|
+
for todo in result["todos"]:
|
|
33
|
+
status = "x" if todo["done"] else " "
|
|
34
|
+
print(f" [{status}] {todo['text']}")
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
if __name__ == "__main__":
|
|
38
|
+
main()
|
package/template/rules/AGENTS.md
CHANGED
|
@@ -8,7 +8,7 @@ Instant is a client-side database (Modern Firebase) with built-in queries, trans
|
|
|
8
8
|
|
|
9
9
|
# Instant SDKs
|
|
10
10
|
|
|
11
|
-
Instant provides client-side
|
|
11
|
+
Instant provides client-side SDKs and server-side SDKs:
|
|
12
12
|
|
|
13
13
|
- `@instantdb/core` --- vanilla JS
|
|
14
14
|
- `@instantdb/react` --- React
|
|
@@ -16,11 +16,13 @@ Instant provides client-side JS SDKs and an admin SDK:
|
|
|
16
16
|
- `@instantdb/solidjs` --- SolidJS
|
|
17
17
|
- `@instantdb/svelte` --- Svelte
|
|
18
18
|
- `@instantdb/vue` --- Vue
|
|
19
|
-
- `@instantdb/admin` --- backend
|
|
19
|
+
- `@instantdb/admin` --- JS/TS backend SDK
|
|
20
|
+
- `instantdb` --- Python backend SDK
|
|
20
21
|
|
|
21
22
|
When installing, always check what package manager the project uses (npm, pnpm,
|
|
22
23
|
bun) first and then install the latest version of the Instant SDK. If working in
|
|
23
|
-
React use Next and Tailwind unless specified otherwise.
|
|
24
|
+
React use Next and Tailwind unless specified otherwise. If working in python be
|
|
25
|
+
sure to fetch the python documentation listed below.
|
|
24
26
|
|
|
25
27
|
# Managing Instant Apps
|
|
26
28
|
|
|
@@ -429,6 +431,7 @@ Fetch the URL for a topic to learn more about it.
|
|
|
429
431
|
- [Svelte](https://www.instantdb.com/docs/start-svelte.md): How to use Instant in Svelte apps.
|
|
430
432
|
- [Vue](https://www.instantdb.com/docs/start-vue.md): How to use Instant in Vue apps.
|
|
431
433
|
- [TanStack](https://www.instantdb.com/docs/start-tanstack.md): How to use Instant in TanStack apps.
|
|
434
|
+
- [Python](https://www.instantdb.com/docs/start-python.md): How to use Instant with Python.
|
|
432
435
|
|
|
433
436
|
# Final Note
|
|
434
437
|
|