@contfu/cli 0.0.2 → 0.0.3
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/README.md +39 -0
- package/dist/main.js +10 -10
- package/package.json +1 -1
package/README.md
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# @contfu/cli
|
|
2
|
+
|
|
3
|
+
Command-line tool for managing Contfu service resources.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```sh
|
|
8
|
+
npm install -g @contfu/cli
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Usage
|
|
12
|
+
|
|
13
|
+
```sh
|
|
14
|
+
contfu [--help] <command> [args...]
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
### Commands
|
|
18
|
+
|
|
19
|
+
```
|
|
20
|
+
login [--no-browser] Authenticate with the Contfu service
|
|
21
|
+
logout Clear stored credentials
|
|
22
|
+
status Show resource summary
|
|
23
|
+
|
|
24
|
+
connections list|get|create|update|delete
|
|
25
|
+
collections list|get|create|update|delete
|
|
26
|
+
flows list|get|create|update|delete
|
|
27
|
+
consumers list|get|create|update|delete
|
|
28
|
+
|
|
29
|
+
connections types List valid connection types
|
|
30
|
+
collections types Generate TypeScript types for a collection
|
|
31
|
+
items query --collection <id> Query items
|
|
32
|
+
items count --collection <id> Count items
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## Authentication
|
|
36
|
+
|
|
37
|
+
Credentials are stored locally after `contfu login`. The `CONTFU_TOKEN` environment variable can be used as an alternative to interactive login.
|
|
38
|
+
|
|
39
|
+
`CONTFU_URL` is only required for `items` commands — it specifies the base URL of the Contfu server that holds the data.
|
package/dist/main.js
CHANGED
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import{parseArgs as
|
|
3
|
-
`)}import{readFileSync as
|
|
2
|
+
import{parseArgs as yt}from"node:util";var b=["$id","$ref","$collection","$changedAt","$connectionType"],Pt=new Set(b),v=Symbol("FieldRef");function tt(t){return typeof t==="object"&&t!==null&&v in t}function N(t){if(tt(t))return t.path;if(t===null)return"null";if(typeof t==="string")return`"${t}"`;if(typeof t==="boolean")return t?"true":"false";return String(t)}function m(t){return(i,n)=>`${N(i)} ${t} ${N(n)}`}var Ht=m("="),Et=m("!="),Gt=m(">"),Wt=m(">="),Kt=m("<"),Mt=m("<="),Qt=m("~"),Ut=m("!~"),Jt=m("?=");var h={CLIENT:0,WEB:1,NOTION:20,STRAPI:21,CONTENTFUL:22};var s={NULL:0,STRING:2,STRINGS:4,NUMBER:8,NUMBERS:16,BOOLEAN:32,REF:64,REFS:128,FILE:256,FILES:512,DATE:1024,ENUM:2048,ENUMS:4096};function d(t){return Array.isArray(t)?t[0]:t}function I(t){return Array.isArray(t)?t[1]:void 0}function nt(t){return t[0].toUpperCase()+t.slice(1)}function y(t,i){if(i==="lookup")return t.map((n)=>`ContfuCollections["${n}"]`).join(" | ");return t.map(nt).join(" | ")}function O(t,i,n="interface",r){switch(t){case s.STRING:return"string";case s.STRINGS:return"string[]";case s.NUMBER:return"number";case s.NUMBERS:return"number[]";case s.BOOLEAN:return"boolean";case s.REF:if(i&&i.length>0)return y(i,n);return"string";case s.REFS:if(i&&i.length>0){let o=y(i,n);return i.length>1?`(${o})[]`:`${o}[]`}return"string[]";case s.FILE:return"{ url: string; alt?: string }";case s.FILES:return"{ url: string; alt?: string }[]";case s.DATE:return"string";case s.ENUM:if(r&&r.length>0)return r.map((o)=>JSON.stringify(o)).join(" | ");return"string";case s.ENUMS:if(r&&r.length>0){let o=r.map((e)=>JSON.stringify(e)).join(" | ");return r.length>1?`(${o})[]`:`${o}[]`}return"string[]";default:return"unknown"}}function it(t,i,n,r,o){let e=[`${r}| {`];for(let[f,g]of Object.entries(t))e.push(`${r} ${f}: ${O(d(g),i?.[f],n,I(g))};`);return e.push(`${r} }${o?";":""}`),e}function ot(t){let i=new Set;return t.filter((n)=>{let r=JSON.stringify(Object.entries(n).sort(([o],[e])=>o.localeCompare(e)));if(i.has(r))return!1;return i.add(r),!0})}function a(t){let i=["// Auto-generated by Contfu — do not edit",""];i.push("export type ContfuCollections = {");for(let n of t){i.push(` /** ${n.displayName} */`);let r=n.inflowSchemas?ot(n.inflowSchemas):[];if(r.length>=2){i.push(` ${n.name}:`);for(let o=0;o<r.length;o++)i.push(...it(r[o],n.refTargets,"lookup"," ",o===r.length-1))}else{i.push(` ${n.name}: {`);for(let[o,e]of Object.entries(n.schema))i.push(` ${o}: ${O(d(e),n.refTargets?.[o],"lookup",I(e))};`);i.push(" };")}}return i.push("};"),i.push(""),i.join(`
|
|
3
|
+
`)}import{readFileSync as rt}from"node:fs";import{join as et}from"node:path";import{homedir as ft}from"node:os";function gt(){if(process.env.CONTFU_API_KEY)return process.env.CONTFU_API_KEY;try{let t=et(ft(),".config","contfu","config.json");return JSON.parse(rt(t,"utf-8")).apiKey}catch{return}}function st(){return process.env.CONTFU_URL??"https://contfu.com"}function ct(t){let i=t.trim();if(!i)return null;try{let n=JSON.parse(i);if(typeof n.message==="string"&&n.message.trim())return n.message.trim()}catch{}return i}async function p(t,i){let n=gt();if(!n)console.error("No API key configured. Set CONTFU_API_KEY or create ~/.config/contfu/config.json"),process.exit(1);let r=`${st()}${t}`,o=new Headers(i?.headers);o.set("Authorization",`Bearer ${n}`);let e=await fetch(r,{...i,headers:o});if(!e.ok){let f=await e.text(),g=ct(f);if(e.status===403){if(g&&g!=="Insufficient permissions")console.error(g);else console.error("Insufficient permissions. Your API key does not have the required scope for this action.");process.exit(1)}if(e.status===429)console.error("Rate limit exceeded. Please slow down and try again."),process.exit(1);console.error(`Error ${e.status}: ${g??f}`),process.exit(1)}return e}var pt=["connections","collections","flows"];function A(t){return pt.includes(t)}var lt={connections:["name"],collections:["display-name"],flows:["source-id","target-id"]};function _(t,i,n){if(i==="create"){let o=lt[t].filter((e)=>n[e]===void 0);if(o.length>0)console.error(`Missing required flag(s): ${o.map((e)=>`--${e}`).join(", ")}`),process.exit(1)}if(t==="connections"){let o={};if(n.name!==void 0)o.label=n.name;if(n.type!==void 0)o.providerId=n.type;else if(i==="create")o.providerId="notion";if(n.token!==void 0)o.token=n.token;return o}if(t==="flows"){let o={};if(n["source-id"]!==void 0)o.sourceId=Number(n["source-id"]);if(n["target-id"]!==void 0)o.targetId=Number(n["target-id"]);if(n["include-ref"]===!0)o.includeRef=!0;if(n["no-include-ref"]===!0)o.includeRef=!1;return o}let r={};if(n.name!==void 0)r.name=n.name;if(n["display-name"]!==void 0)r.displayName=n["display-name"];if(n["include-ref"]===!0)r.includeRef=!0;if(n["no-include-ref"]===!0)r.includeRef=!1;return r}var mt=Object.fromEntries(Object.entries(s).map(([t,i])=>[i,t.toLowerCase()])),wt=Object.fromEntries(Object.entries(s).map(([t,i])=>[t.toLowerCase(),i]));function $(t){if(t===null||typeof t!=="object")return t;if(Array.isArray(t))return t.map($);let i={};for(let[n,r]of Object.entries(t))if(n==="schema"&&r!==null&&typeof r==="object"&&!Array.isArray(r))i[n]=Object.fromEntries(Object.entries(r).map(([o,e])=>[o,typeof e==="string"?wt[e]??e:e]));else i[n]=$(r);return i}function u(t){if(t===null||typeof t!=="object")return t;if(Array.isArray(t))return t.map(u);let i={};for(let[n,r]of Object.entries(t))if(n==="schema"&&r!==null&&typeof r==="object"&&!Array.isArray(r))i[n]=Object.fromEntries(Object.entries(r).map(([o,e])=>{let f=d(e),g=mt[f]??f,c=Array.isArray(e)?e[1]:void 0;return[o,c&&c.length>0?`${g}(${c.join("|")})`:g]}));else i[n]=u(r);return i}function C(t){console.log(JSON.stringify(u(t),null,2))}var xt={connections:[{key:"id",header:"ID"},{key:"label",header:"Label"},{key:"providerId",header:"Provider"},{key:"accountId",header:"Account"},{key:"hasCredentials",header:"Credentials",format:(t)=>t?"yes":"no"}],collections:[{key:"id",header:"ID"},{key:"name",header:"Name"},{key:"displayName",header:"Display Name"},{key:"connectionId",header:"Connection"}],flows:[{key:"id",header:"ID"},{key:"sourceId",header:"Source"},{key:"targetId",header:"Target"},{key:"includeRef",header:"Ref",format:(t)=>t?"yes":"no"}]};function dt(t,i){if(t.length===0){console.log("(none)");return}let n=(o,e)=>o.format?o.format(e[o.key]):String(e[o.key]??""),r=i.map((o)=>Math.max(o.header.length,...t.map((e)=>n(o,e).length)));console.log(i.map((o,e)=>o.header.padEnd(r[e])).join(" ")),console.log(r.map((o)=>"-".repeat(o)).join(" "));for(let o of t)console.log(i.map((e,f)=>n(e,o).padEnd(r[f])).join(" "))}async function L(t,i){let r=await(await p(`/api/v1/${t}`)).json();if(i==="json")C(r);else dt(r,xt[t])}async function P(t,i){let r=await(await p(`/api/v1/${t}/${i}`)).json();C(r)}async function H(t,i,n){let r=i?$(JSON.parse(i)):_(t,"create",n),e=await(await p(`/api/v1/${t}`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(r)})).json();C(e)}async function E(t,i,n,r){let o=n?$(JSON.parse(n)):_(t,"update",r),f=await(await p(`/api/v1/${t}/${i}`,{method:"PATCH",headers:{"Content-Type":"application/json"},body:JSON.stringify(o)})).json();C(f)}async function G(t,i){await p(`/api/v1/${t}/${i}`,{method:"DELETE"}),console.log(`Deleted ${t.slice(0,-1)} ${i}`)}function W(){let t=Object.entries(h),i=t.filter(([,r])=>r<20).map(([r])=>r.toLowerCase()).sort(),n=t.filter(([,r])=>r>=20).map(([r])=>r.toLowerCase()).sort();if(i.length)process.stdout.write(i.join(`
|
|
4
4
|
`)+`
|
|
5
|
-
`);if(i.length&&
|
|
6
|
-
`);if(
|
|
5
|
+
`);if(i.length&&n.length)process.stdout.write(`
|
|
6
|
+
`);if(n.length)process.stdout.write(n.join(`
|
|
7
7
|
`)+`
|
|
8
|
-
`)}async function
|
|
9
|
-
${
|
|
8
|
+
`)}async function K(t){let n=await(await p(`/api/v1/connections/${t}/types`)).json();if(n.length===0)console.error("No collections connected to this connection"),process.exit(1);process.stdout.write(a(n))}async function M(t){let n=await(await p(`/api/v1/collections/${t}/types`)).json();if(n.length===0)console.error("No types found for this collection"),process.exit(1);process.stdout.write(a(n))}import{parseArgs as Q}from"node:util";async function U(t,i){let n=`${t}${i}`,r=await fetch(n);if(!r.ok){let o=await r.text();console.error(`Error ${r.status}: ${o}`),process.exit(1)}return r}function J(t){let i=Object.entries(t).filter((n)=>n[1]!==void 0&&n[1]!=="");if(i.length===0)return"";return"?"+new URLSearchParams(i).toString()}async function z(t){let{values:i}=Q({args:t,options:{"client-url":{type:"string",short:"u"},collection:{type:"string"},filter:{type:"string"},sort:{type:"string"},limit:{type:"string",default:"20"},offset:{type:"string",default:"0"},include:{type:"string"},fields:{type:"string"},flat:{type:"boolean",default:!1}},allowPositionals:!0}),n=i["client-url"];if(!n)console.error("Missing required --client-url flag"),process.exit(1);let r=i.collection?`/api/collections/${i.collection}/items`:"/api/items",o=J({filter:i.filter,sort:i.sort,limit:i.limit,offset:i.offset,include:i.include,fields:i.fields,flat:i.flat?"true":void 0}),f=await(await U(n,`${r}${o}`)).json();console.log(JSON.stringify(f,null,2))}async function X(t){let{values:i}=Q({args:t,options:{"client-url":{type:"string",short:"u"},collection:{type:"string"},filter:{type:"string"}},allowPositionals:!0}),n=i["client-url"];if(!n)console.error("Missing required --client-url flag"),process.exit(1);let r=i.collection?`/api/collections/${i.collection}/items`:"/api/items",o=J({filter:i.filter,limit:"0"}),f=await(await U(n,`${r}${o}`)).json();console.log(f?.meta?.total??0)}import{createServer as Y}from"node:http";import{randomBytes as $t}from"node:crypto";import{promises as S}from"node:fs";import{join as Z}from"node:path";import{homedir as q}from"node:os";import{spawn as Ct}from"node:child_process";var B=Z(q(),".config","contfu","config.json");function at(){return process.env.CONTFU_URL??"https://contfu.com"}async function ut(t){let i=process.platform,[n,r]=i==="darwin"?["open",[t]]:i==="win32"?["cmd",["/c","start","",t]]:["xdg-open",[t]];await new Promise((o,e)=>{let f=Ct(n,r,{stdio:"ignore",detached:!0});f.on("error",e),f.on("close",(g)=>g===0?o():e(Error(`exit ${g}`)))})}function St(){return new Promise((t,i)=>{let n=Y();n.listen(0,"127.0.0.1",()=>{let r=n.address();n.close((o)=>{if(o)i(o);else t(r.port)})})})}function Rt(){return!!(process.env.SSH_CONNECTION||process.env.SSH_CLIENT||process.env.SSH_TTY)}async function Ft(t){let i=await St(),n=$t(16).toString("hex"),r=`http://localhost:${i}/callback`,o=`${t}/auth/cli?callback=${encodeURIComponent(r)}&state=${encodeURIComponent(n)}`,e=new Promise((f,g)=>{let c=setTimeout(()=>{w.close(),g(Error("Login timed out after 5 minutes"))},300000),w=Y((k,l)=>{try{let x=new URL(k.url??"/",`http://localhost:${i}`);if(x.pathname!=="/callback"){l.writeHead(404),l.end("Not found");return}let F=x.searchParams.get("token");if(x.searchParams.get("state")!==n){l.writeHead(400),l.end("State mismatch"),clearTimeout(c),w.close(),g(Error("State mismatch — possible CSRF"));return}if(!F){l.writeHead(400),l.end("No token received"),clearTimeout(c),w.close(),g(Error("No token in callback"));return}l.writeHead(302,{Location:`${t}/auth/cli/success`}),l.end(),clearTimeout(c),w.close(),f(F)}catch(x){l.writeHead(500),l.end("Internal error"),clearTimeout(c),w.close(),g(x)}});w.listen(i,"127.0.0.1")});console.log(`Opening browser to ${o}`);try{await ut(o)}catch{console.log(`Could not open browser automatically. Please visit:
|
|
9
|
+
${o}`)}return e}async function Nt(t){let i=`${t}/auth/cli?mode=code`;console.log(`Open this URL in your browser:
|
|
10
10
|
|
|
11
11
|
${i}
|
|
12
|
-
`),process.stdout.write("Paste the code from the browser: ");let
|
|
13
|
-
`)[0].trim();if(
|
|
14
|
-
`,"utf-8")}async function
|
|
12
|
+
`),process.stdout.write("Paste the code from the browser: ");let n=await new Promise((e)=>{process.stdin.resume(),process.stdin.setEncoding("utf-8");let f="";process.stdin.on("data",(g)=>{f+=g;let c=f.split(`
|
|
13
|
+
`)[0].trim();if(c)process.stdin.pause(),e(c)})}),r=await fetch(`${t}/auth/cli/exchange?code=${encodeURIComponent(n)}`);if(!r.ok)throw Error(`Invalid or expired code (${r.status})`);let{token:o}=await r.json();return o}async function D(t={}){let i=at(),r=t.noBrowser||Rt()?await Nt(i):await Ft(i);await j({apiKey:r,baseUrl:i}),console.log("Logged in successfully")}async function T(){try{let t=await ht();delete t.apiKey,await j(t),console.log("Logged out")}catch{console.log("Logged out (no config found)")}}async function ht(){try{let t=await S.readFile(B,"utf-8");return JSON.parse(t)}catch{return{}}}async function j(t){let i=Z(q(),".config","contfu");await S.mkdir(i,{recursive:!0}),await S.writeFile(B,JSON.stringify(t,null,2)+`
|
|
14
|
+
`,"utf-8")}async function V(t="table"){let n=await(await p("/api/v1/status")).json();if(t==="json"){console.log(JSON.stringify(n,null,2));return}console.log("contfu status"),console.log("-------------"),console.log(`connections ${n.connections}`),console.log(`collections ${n.collections}`),console.log(`flows ${n.flows}`)}function R(){console.error(`Usage: contfu [--help] <command> [args...]
|
|
15
15
|
|
|
16
16
|
Commands:
|
|
17
17
|
login [--no-browser] Authenticate
|
|
@@ -63,4 +63,4 @@ list options:
|
|
|
63
63
|
-f, --format <fmt> Output format: table (default) | json
|
|
64
64
|
|
|
65
65
|
Environment:
|
|
66
|
-
CONTFU_API_KEY API key (overrides stored config)`)}async function
|
|
66
|
+
CONTFU_API_KEY API key (overrides stored config)`)}async function It(){let{values:t,positionals:i}=yt({args:process.argv.slice(2),options:{help:{type:"boolean",short:"h"},data:{type:"string",short:"d"},"no-browser":{type:"boolean"},name:{type:"string",short:"n"},type:{type:"string",short:"t"},url:{type:"string"},"display-name":{type:"string"},"source-id":{type:"string"},"target-id":{type:"string"},"collection-id":{type:"string"},"include-ref":{type:"boolean"},"no-include-ref":{type:"boolean"},token:{type:"string"},format:{type:"string",short:"f"}},allowPositionals:!0,strict:!1}),n=i[0];if(t.help)R(),process.exit(0);if(!n)R(),process.exit(1);if(n==="login"){await D({noBrowser:t["no-browser"]});return}if(n==="logout"){await T();return}if(n==="status"){await V(t.format??"table");return}if(n==="items"){let r=i[1],o=process.argv.slice(process.argv.indexOf("items")+2);switch(r){case"query":case void 0:await z(o);return;case"count":await X(o);return;default:console.error(`Unknown items action: ${r}. Use query or count`),process.exit(1)}}if(A(n)){let r=i[1],o=i[2];if(r==="types"){if(n==="connections"){if(!o)W();else await K(o);return}if(n==="collections"){if(!o)console.error("Missing id"),process.exit(1);await M(o);return}console.error(`'types' is not available for ${n}`),process.exit(1)}let e={name:t.name,type:t.type,url:t.url,"display-name":t["display-name"],"source-id":t["source-id"],"target-id":t["target-id"],"collection-id":t["collection-id"],"include-ref":t["include-ref"],"no-include-ref":t["no-include-ref"],token:t.token};switch(r){case"list":case void 0:await L(n,t.format??"table");return;case"get":if(!o)console.error("Missing id"),process.exit(1);await P(n,o);return;case"create":await H(n,t.data,e);return;case"update":case"set":if(!o)console.error("Missing id"),process.exit(1);await E(n,o,t.data,e);return;case"delete":if(!o)console.error("Missing id"),process.exit(1);await G(n,o);return;default:console.error(`Unknown action: ${r}. Use list, get, create, update, or delete`),process.exit(1)}}console.error(`Unknown command: ${n}`),R(),process.exit(1)}It();
|