Giter VIP home page Giter VIP logo

sindri-js's People

Contributors

katiemckeon avatar kpreisner avatar marc-aurele-besner avatar sangaline avatar sindri-bot[bot] avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar

sindri-js's Issues

Add ESM/CJS import tests

We should add explicit tests that the ESM and CJS build outputs can be imported. It's easy for a CJS-only library to sneak in and break the ESM build.

Add the Sindri-Client header to aid internal Sindri metrics

Add the Sindri-Client header to aid internal Sindri metrics

Every request to the Sindri API should include the following header:

  • Sindri-Client: sindri-js-sdk/<version> (<platform>) <comment>
  • Sindri-Client: sindri-cli-sdk/<version> (<platform>) <comment>
  • version above must come from the release version. This version is set during the CI build pipeline. Research setting this version so it is available during

Refer to the Sindri internal API Headers for Tracking Epic for more information.

Add new smart contract and calldata methods

The API will soon support retrieving the circuit's generated smart contract verifier code and its proofs' proof+public formatted as calldata for their respective smart contracts.

Create Sindri CLI tool

This is a good starting point to get the skeleton of the project together before implementing the actual SDK.

Update CLI to use SDK where possible

There's a fair bit of duplicate code that can be eliminated between the two. The most complicated part of this is moving more logging into the client and utility functions.

Add optional flag to sindri lint to disable circomspect

Add optional flag to sindri lint to disable circomspect

  • add an optional way to turn off circomspect for the sindri lint CLI command. maybe use an environment variable or an additional command line argument such as --disable circomspect

Why

This feature will be for Sindri internal usage only. Some circuits in our testing infrastructure contain intentional errors that cause circomspect to fail. We want to disable circomspect checking for those circuits so internal CI checks pass.

Login Forbidden Error

Description:

While attempting to log in using sindri, I encountered a "Forbidden" error with a 403 status code. This issue prevents successful authentication despite providing correct credentials.

node@docker-desktop:/sindri$ sindri -d login -u http://host.docker.internal login
[05:49:58.931] DEBUG: Set log level to "trace".
[05:49:58.952] DEBUG: Config file "/home/node/.config/sindri/sindri.conf.json" does not exist, initializing default config.
? Username: test
? Password: ****
? New API Key Name: docker-desktop-sdk
[05:51:12.565] FATAL: An irrecoverable error occurred.
[05:51:12.565] ERROR: Forbidden
    err: {
      "type": "ApiError",
      "message": "Forbidden",
      "stack":
          ApiError: Forbidden
              at catchErrorCodes (/sindri/dist/cli/index.js:453:11)
              at /sindri/dist/cli/index.js:502:9
              at process.processTicksAndRejections (node:internal/process/task_queues:95:5)
      "url": "http://host.docker.internal/api/token/pair",
      "status": 403,
      "statusText": "connecting to host.docker.internal:80: connecting to 127.0.0.1:80: dial tcp 127.0.0.1:80: connectex: No connection could be made because the target machine actively refused it.",
      "body": "connecting to host.docker.internal:80: connecting to 127.0.0.1:80: dial tcp 127.0.0.1:80: connectex: No connection could be made because the target machine actively refused it.",
      "request": {
        "method": "POST",
        "url": "/api/token/pair",
        "body": {
          "username": "test",
          "password": "||||"
        },
        "mediaType": "application/json"
      },
      "name": "ApiError"
    }

Steps to Reproduce:

docker compose up
docker compose exec sindri-js bash
sindri -d login -u http://host.docker.internal login

Request:
Kindly assist in troubleshooting this issue to enable successful authentication via sindri using docker.
@sangaline

Thank you

Modularize SDK Client

Authentication and logging are currently global, need to modularize these so there can be multiple client instances with different configurations.

cli clone error

following instructions on "Create your first ZK project" I get an error at step 2, npx sindri@latest clone sindri/multiplier2. The console shows:

> npx sindri@latest clone sindri/multiplier2

[11:08:58.082] INFO: Cloning the circuit "sindri/multiplier2" into "/Users/billy/GitHub/trifle-labs/multiplier2".
/Users/billy/.npm/_npx/2dfc35fe94c077da/node_modules/sindri/dist/cli/index.js:4
    rm ${e}`),r?.debug(o)}}return r?.debug(`Config file "${e}" does not exist, initializing default config.`),M.default.cloneDeep(ft)},q=class{_config;logger;constructor(e){this.logger=e,this.reload()}get auth(){return M.default.cloneDeep(this._config.auth)}get config(){return M.default.cloneDeep(this._config)}reload(){this._config=gt(this.logger)}update(e){this.logger?.debug("Merging in config update:"),this.logger?.debug(e);let o=M.default.cloneDeep(this._config);M.default.merge(o,e),this._config=Oe.parse(o);let n=Qe(),i=Ae.default.dirname(n);V.default.existsSync(i)||V.default.mkdirSync(i,{recursive:!0}),this.logger?.debug(`Writing merged config to "${n}":`,this._config),V.default.writeFileSync(n,JSON.stringify(this._config,null,2),{encoding:"utf-8"})}};var Xe=y(require("pino")),er=y(require("pino-pretty")),ke=r=>{let e=(0,Xe.default)(process.env.BROWSER_BUILD?{browser:{asObject:!0}}:(0,er.default)({colorize:!0,destination:2,ignore:"hostname,pid",levelFirst:!1,sync:!0}));return e.level=r??!0?"silent":"info",e},Ao=ke(),j=console.log;var ge=class r{_client;_clientConfig;_config;logger;pollingInterval=1e3;retryOptions={minTimeout:1e3,retries:6};constructor(e={},{retryOptions:o}={}){this._client=new ie,this._clientConfig=this._client.request.config;let n="v0.0.1-alpha.53";this._clientConfig.HEADERS={...this._clientConfig.HEADERS,"Sindri-Client":`sindri-js-sdk/${n}`},this.logger=ke(),process.env.BROWSER_BUILD||(this._config=new q(this.logger)),this._clientConfig.sindri=this,this.authorize(e),o&&(this.retryOptions=structuredClone(o))}get apiKey(){return this._clientConfig.TOKEN&&typeof this._clientConfig.TOKEN!="string"?null:this._clientConfig.TOKEN||null}get baseUrl(){return this._clientConfig.BASE}get logLevel(){return this.logger.level}set logLevel(e){this.logger.level=e,this.logger.debug(`Set log level to "${this.logger.level}".`)}authorize(e){return process.env.BROWSER_BUILD?(this._clientConfig.BASE=e.baseUrl||"https://sindri.app",this._clientConfig.TOKEN=e.apiKey):(this._config.reload(),this._clientConfig.BASE=e.baseUrl||process.env.SINDRI_BASE_URL||this._config.auth?.baseUrl||this._clientConfig.BASE||"https://sindri.app",this._clientConfig.TOKEN=e.apiKey||process.env.SINDRI_API_KEY||this._config.auth?.apiKey),!!(this._clientConfig.BASE&&this._clientConfig.TOKEN)}create(e,o){return new r(e,o)}async createCircuit(e,o=["latest"]){let n=new K;o=typeof o=="string"?[o]:o??[];for(let d of o){if(!/^[-a-zA-Z0-9_.]+$/.test(d))throw new Error(`"${d}" is not a valid tag. Tags may only contain alphanumeric characters, underscores, hyphens, and periods.`);n.append("tags",d)}if(o.length===0&&n.append("tags",""),typeof e=="string"){if(process.env.BROWSER_BUILD)throw new Error("Specifying `project` as a path is not allowed in the browser build.");let d;try{d=await(0,ne.stat)(e)}catch{throw new Error(`The "${e}" path does not exist or you do not have permission to access it.`)}if(d.isFile()){if(!/\.(zip|tar|tar\.gz|tgz)$/i.test(e))throw new Error("Only gzipped tarballs or zip files are supported.");let m=fe.default.basename(e),u=await(0,ne.readFile)(e);n.append("files",new Z([u],m))}else if(d.isDirectory()){let m=fe.default.join(e,"sindri.json"),u;try{u=await(0,ne.readFile)(m,{encoding:"utf-8"})}catch{throw new Error(`Expected Sindri manifest file at "${m}" does not exist.`)}let g;try{g=JSON.parse(u)}catch{throw new Error(`Could not parse "${m}", is it valid JSON?`)}let l=g?.name;if(!l)throw new Error(`No circuit "name" field was found in "${m}", the manifest is invalid.`);let p=tr.default.sync({follow:!0,ignoreFiles:[".sindriignore"],path:e}).filter(b=>!/(^|\/)\.git(\/|$)/.test(b)),h=fe.default.basename(m);p.includes(h)||p.push(h);let R=`${l}.tar.gz`;p.sort((b,T)=>b.localeCompare(T));let w=or.default.c({cwd:e,gzip:!0,onwarn:(b,T)=>{this.logger.warn(`While creating tarball: ${b} - ${T}`)},prefix:`${l}/`,sync:!0},p);n.append("files",new Z([w.read()],R))}else throw new Error(`The "${e}" path is not a file or directory.`)}else if(Array.isArray(e)){if(!e.every(h=>h instanceof Z))throw new Error("All entries in `project` must be `File` instances.");let d=e.find(h=>h.name==="sindri.json");if(!d)throw new Error("The `project` array must include a `sindri.json` file.");let m;try{m=JSON.parse(await d.text())}catch{throw new Error('Could not parse "sindri.json", is it valid JSON?')}let u=m?.name;if(!u)throw new Error('No circuit "name" field was found in "sindri.json", the manifest is invalid.');let g=new ir.default;e.sort((h,R)=>h.name.localeCompare(R.name));for(let h of e){let R=new Uint8Array(await h.arrayBuffer());await new Promise(w=>g.append(`${u}/${h.name}`,R,w))}let l=new Uint8Array(rr.default.zip(g.out)),p=new Z([l],`${u}.tar.gz`);process.env.BROWSER_BUILD,n.append("files",p)}let i=this._clientConfig.HEADERS;this._clientConfig.HEADERS={...i,"Content-Type":"multipart/form-data; boundary=----WebKitFormBoundary0buQ8d6EhWcs9X9d"};let c=await this._client.circuits.circuitCreate(n);this._clientConfig.HEADERS=i;let a=c.circuit_id,f;for(;f=await this._client.circuits.circuitDetail(a,!1),!(f.status==="Ready"||f.status==="Failed");)await new Promise(d=>setTimeout(d,this.pollingInterval));return f}async getAllCircuitProofs(e){return await this._client.circuits.circuitProofs(e)}async getAllCircuits(){return await this._client.circuits.circuitList()}async getCircuit(e){return await this._client.circuits.circuitDetail(e)}async getProof(e){return await this._client.proofs.proofDetail(e)}async proveCircuit(e,o,n=!1,i=!1){let s=await this._client.circuits.proofCreate(e,{perform_verify:n,proof_input:o}),c;for(;c=await this._client.proofs.proofDetail(s.proof_id,!0,!0,i,!0),!(c.status==="Ready"||c.status==="Failed");)await new Promise(a=>setTimeout(a,this.pollingInterval));return c}};var t=new ge;var lr=new ar.Command().name("clone").description("Clone a circuit into a local directory.").argument("<circuit>","The circuit to clone.").argument("[directory]","The directory to clone the circuit into.").action(async(r,e)=>{let n=/^(?:([-a-zA-Z0-9_]+)\/)?([-a-zA-Z0-9_]+)(?::([-a-zA-Z0-9_.]+))?$/.exec(r);if(!n)return t.logger.error(`"${r}" is not a valid circuit identifier.`),F.default.exit(1);(0,nr.default)(n[2],"The circuit name must be provided.");let i=n[2],s=sr.default.resolve(e??i);if(Ne.default.existsSync(s))return t.logger.error(`The directory "${s}" already exists. Aborting.`),F.default.exit(1);if(!t.apiKey||!t.baseUrl)return t.logger.warn("You must login first with `sindri login`."),F.default.exit(1);let c;t.logger.info(`Cloning the circuit "${r}" into "${s}".`);try{c=await t._client.internal.circuitDownload(r)}catch(a){return a instanceof x&&a.status===401?(t.logger.error("Your credentials are invalid. Please log in again with `sindri login`."),F.default.exit(1)):a instanceof x&&a.status===404?(t.logger.error(`The circuit "${r}" does not exist or you lack permission to access it.`),F.default.exit(1)):(t.logger.fatal("An unknown error occurred."),t.logger.error(a),F.default.exit(1))}try{Ne.default.mkdirSync(s,{recursive:!0})}catch(a){return t.logger.fatal(`Failed to create the directory "${s}". Aborting.`),t.logger.error(a),F.default.exit(1)}await new Promise((a,f)=>{c.on("end",a),c.on("error",f),c.pipe(cr.default.x({cwd:s,noChmod:!0,noMtime:!0,onwarn:(d,m)=>{t.logger.warn(`While extracting tarball: ${d} - ${m}`)},preserveOwner:!1,preservePaths:!1,strip:1}))}),t.logger.info("Circuit cloned successfully.")});var _e=require("@commander-js/extra-typings");var ht=new _e.Command().name("list").description("Show the current config.").action(async()=>{t._config.reload(),j(t._config.config)}),pr=new _e.Command().name("config").description("Commands related to configuration and config files.").addCommand(ht);var Cr=y(require("assert")),be=y(require("path")),$=y(require("process")),Ce=require("@commander-js/extra-typings");var ur=y(require("assert")),De=require("child_process"),W=require("fs"),P=require("fs/promises"),mr=y(require("os")),v=y(require("path")),E=y(require("process")),dr=require("stream"),fr=require("url"),gr=y(require("axios")),hr=require("compare-versions"),he=y(require("dockerode")),yr=y(require("nunjucks")),yt=(0,fr.fileURLToPath)(C),bt=v.default.dirname(yt);function Rt(r){return new Promise(e=>{let o=(0,De.spawn)(r,["--version"]);o.on("error",()=>{e(!1)}),o.on("exit",n=>{e(n!==127&&n!==null)})})}async function qe(r){let e=new he.default;try{await e.ping()}catch(o){return r?.debug("Failed to connect to the Docker daemon."),r?.debug(o),!1}return r?.debug("Docker daemon is accessible."),!0}var Ct=new dr.Writable({write(r,e,o){o()}});async function je(r,e=[],{cwd:o=E.default.cwd(),docker:n=new he.default,logger:i,rootDirectory:s,tag:c="auto",tty:a=!1}){if(Fe(E.default.env.SINDRI_FORCE_DOCKER??"false"))i?.debug(`Forcing docker usage for command "${r}" because "SINDRI_FORCE_DOCKER" is set to "${E.default.env.SINDRI_FORCE_DOCKER}".`);else{if(await Rt(r))return i?.debug({args:e,command:r},`Executing the "${r}" command locally.`),{code:await wt(r,e,{cwd:o,logger:i,tty:a}),method:"local"};i?.debug(`The "${r}" command was not found locally, trying Docker instead.`)}return await qe(i)?(i?.debug({args:e,command:r},`Executing the "${r}" command in a Docker container.`),{code:await ye(r,e,{cwd:o,docker:n,logger:i,rootDirectory:s,tag:c,tty:a}),method:"docker"}):(i?.debug(`The "${r}" command is not available locally or in Docker.`),{code:null,method:null})}async function ye(r,e=[],{cwd:o=E.default.cwd(),docker:n=new he.default,logger:i,rootDirectory:s,tag:c="auto",tty:a=!1}){let f=O(/^sindri.json$/i,o);s||(f?s=v.default.dirname(f):(s=o,i?.warn(`No "sindri.json" file was found in or above "${o}", using the current directory as the project root.`))),s=v.default.normalize(v.default.resolve(s));let d;if(r==="nargo"&&c==="auto"){let p="latest";if(f)try{let h=await(0,P.readFile)(f,{encoding:"utf-8"}),R=JSON.parse(h);R.noirVersion&&(p=R.noirVersion,p&&!p.startsWith("v")&&(p=`v${p}`))}catch(h){i?.error(`Failed to parse the "${f}" file, using the "latest" tag for the "nargo" command.`),i?.debug(h)}else i?.warn(`No "sindri.json" file was found in or above "${o}", using the "latest" tag for the "nargo" command.`);d=`sindrilabs/${r}:${p}`}else if(["circomspect","nargo"].includes(r))d=`sindrilabs/${r}:${c==="auto"?"latest":c}`;else throw new Error(`The command "${r}" is not supported.`);i?.debug(`Pulling the "${d}" image.`);try{await new Promise((p,h)=>{n.pull(d,(R,w)=>{R?h(R):n.modem.followProgress(w,(b,T)=>b?h(b):p(T))})})}catch(p){return i?.error(`Failed to pull the "${d}" image.`),i?.error(p),E.default.exit(1)}let m=s;if(E.default.env.SINDRI_DEVELOPMENT_HOST_ROOT)if(s==="/sindri"||s.startsWith("/sindri/"))m=s.replace("/sindri",E.default.env.SINDRI_DEVELOPMENT_HOST_ROOT),i?.debug(`Remapped "${s}" to "${m}" for bind mount on the Docker host.`);else return i?.fatal(`The root directory path "${s}" must be under "/sindri/"when using "SINDRI_DEVELOPMENT_HOST_ROOT".`),E.default.exit(1);let u=v.default.relative(s,o),g;return u.startsWith("..")?(g="/sindri/",i?.warn(`The current working directory ("${o}") is not under the project root ("${s}"), will use the project root as the current working directory.`)):g=v.default.join("/sindri/",u),i?.debug(`Remapped the "${o}" working directory to "${g}" in the Docker container.`),(await new Promise((p,h)=>{n.run(d,e,a?[E.default.stdout,E.default.stderr]:Ct,{AttachStderr:a,AttachStdin:a,AttachStdout:a,HostConfig:{Binds:[`${m}:/sindri`,"/tmp/sindri/:/tmp/sindri/"]},OpenStdin:a,StdinOnce:!1,Tty:a,WorkingDir:g},(R,w)=>{R?h(R):p(w)}).on("container",R=>{if(!a)return;let w=R.attach({stream:!0,stdin:!0,stdout:!0,stderr:!0},function(b,T){b&&h(b),T.pipe(E.default.stdout)});w&&w.resume()})})).StatusCode}async function wt(r,e=[],{cwd:o=E.default.cwd(),logger:n,tty:i=!1}){let s=(0,De.spawn)(r,e,{cwd:o,stdio:i?"inherit":"ignore"});try{return await new Promise((a,f)=>{s.on("error",d=>{f(d)}),s.on("close",(d,m)=>{d==null&&m!=null&&(d=128+mr.default.constants.signals[m]),(0,ur.default)(d!=null),a(d)})})}catch(c){return n?.error(`Failed to execute the "${r}" command.`),n?.error(c),E.default.exit(1)}}async function $e(r){try{return await(0,P.access)(r,W.constants.F_OK),!0}catch{return!1}}function O(r,e=bt){let o=(0,W.readdirSync)(e);for(let i of o)if(typeof r=="string"?i===r:r.test(i))return v.default.join(e,i);let n=v.default.dirname(e);return n===e?null:O(r,n)}async function br(r,e="sindrilabs"){let o=`https://hub.docker.com/v2/repositories/${e}/${r}/tags/?page_size=1`,n=[];for(;o;){let i=await gr.default.get(o);n=n.concat(i.data.results),o=i.data.next}return n.filter(({tag_status:i})=>i==="active").filter(({name:i})=>i!=="dev").sort((i,s)=>i.last_updated.localeCompare(s.last_updated)).map(({name:i})=>i).sort((i,s)=>i==="latest"?1:s==="latest"?-1:(0,hr.compareVersions)(i,s))}function Fe(r){return["1","true","t","yes","y","on"].includes(r.toLowerCase())}function Rr(){let r=O("sindri-manifest.json");if(!r)throw new Error("A `sindri-manifest.json` file was unexpectedly not found.");let e=(0,W.readFileSync)(r,{encoding:"utf-8"});return JSON.parse(e)}async function Le(r,e,o,n){let i=v.default.resolve(e);await $e(i)||await(0,P.mkdir)(i,{recursive:!0});let s=O("templates");if(!s)throw new Error("Root template directory not found.");let c=v.default.isAbsolute(r)?r:v.default.resolve(s,r);if(!await $e(c))throw new Error(`The "${c}" directory does not exist.`);let a=(d,m)=>{let u=d;return Object.entries(m).forEach(([g,l])=>{typeof l=="string"&&(u=u.replace(new RegExp(`template${g.toUpperCase()}`,"gi"),l))}),yr.default.renderString(u,m)},f=async(d,m)=>{if((await(0,P.stat)(d)).isDirectory()){if(await $e(m)||(await(0,P.mkdir)(m,{recursive:!0}),n?.debug(`Created directory: "${m}"`)),!(await(0,P.stat)(m)).isDirectory())throw new Error(`"File ${m} exists and is not a directory.`);let l=await(0,P.readdir)(d);await Promise.all(l.map(async p=>{let h=a(p,o);await f(v.default.join(d,p),v.default.join(m,h))}));return}let u=await(0,P.readFile)(d,{encoding:"utf-8"}),g=a(u,o);await(0,P.writeFile)(m,g,{encoding:"utf-8"}),n?.debug(`Rendered "${d}" template to "${m}".`)};await f(c,i)}var Re,G,Be,St=new Ce.Command().name("circomspect").description("Trail of Bit's Circomspect static analysis tool for Circom circuits.").helpOption(!1).addHelpCommand(!1).allowUnknownOption().passThroughOptions().argument("[args...]","Arguments to pass to the tool.").action(async r=>{if(!Re)try{let e=await ye("circomspect",r,{logger:t.logger,rootDirectory:G,tag:Be,tty:!0});$.default.exit(e)}catch(e){return t.logger.error("Failed to run the circomspect command."),t.logger.debug(e),$.default.exit(1)}}),Tt=new Ce.Command().name("nargo").description("Aztec Lab's Noir compiler and package manager.").helpOption(!1).addHelpCommand(!1).allowUnknownOption().passThroughOptions().argument("[args...]","Arguments to pass to the tool.").action(async r=>{if(!Re)try{let e=await ye("nargo",r,{logger:t.logger,rootDirectory:G,tag:Be,tty:!0});$.default.exit(e)}catch(e){return t.logger.error("Failed to run the nargo command."),t.logger.debug(e),$.default.exit(1)}}),wr=new Ce.Command().name("exec").alias("x").description("Run a ZKP tool in your project root inside of an optimized docker container.").passThroughOptions().option("-l, --list-tags","List the available docker image tags for a given tool.").option("-t, --tag <tag>","The version tag of the docker image to use.","auto").addCommand(St).addCommand(Tt).hook("preAction",async r=>{let e=r.opts();if(Re=!!e.listTags,Be=e.tag,Re){let i=r.args[0];(0,Cr.default)(i,"The preAction hook should only run if there's a subcommand.");try{(await br(i)).forEach(c=>j(c))}catch(s){return t.logger.fatal("Error listing available docker image tags."),t.logger.error(s),$.default.exit(1)}return $.default.exit(0)}let o=$.default.cwd(),n=O(/^sindri.json$/i,o);n?G=be.default.dirname(n):(G=o,t.logger.warn(`No "sindri.json" file was found in or above "${o}", using the current directory as the project root.`)),G=be.default.normalize(be.default.resolve(G)),await qe(t.logger)||(t.logger.fatal(`Docker is either not installed or the daemon isn't currently running, but it is required by "sindri exec".


TypeError: c.on is not a function
    at /Users/billy/.npm/_npx/2dfc35fe94c077da/node_modules/sindri/dist/cli/index.js:4:7174
    at new Promise (<anonymous>)
    at Command.<anonymous> (/Users/billy/.npm/_npx/2dfc35fe94c077da/node_modules/sindri/dist/cli/index.js:4:7152)
    at process.processTicksAndRejections (node:internal/process/task_queues:95:5)

Node.js v18.20.3

sindri whoami command broken

sindri whoami command broken. I believe this is because the openapi spec needs updating from the production api due to updates.

ubuntu:/qa$ node --version
v21.4.0

ubuntu:/qa$ sindri --version
v0.0.1-alpha.52

ubuntu:/qa$ sindri whoami
[02:28:37.274] FATAL: An unknown error occurred.
[02:28:37.277] ERROR: Cannot read properties of undefined (reading 'slug')
    err: {
      "type": "TypeError",
      "message": "Cannot read properties of undefined (reading 'slug')",
      "stack":
          TypeError: Cannot read properties of undefined (reading 'slug')
              at Command.<anonymous> (/usr/local/nvm/versions/node/v21.6.2/lib/node_modules/sindri/dist/cli/index.js:7:8427)
              at process.processTicksAndRejections (node:internal/process/task_queues:95:5)
    }

Clean up tsdoc comments

These are pretty fluffy placeholders right now, we'll want to clean them up and make them more concise.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.