@localez/cli 0.1.0 → 0.1.1
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 +4 -4
- package/dist/localez.mjs +5 -5
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -3,11 +3,11 @@
|
|
|
3
3
|
Read-only Localez business CLI for AI agents. Requires Node.js 22 or newer and a Localez plugin deployment that exposes `/agent/api/v1`.
|
|
4
4
|
|
|
5
5
|
```sh
|
|
6
|
-
npm install --global @localez/cli@0.1.
|
|
6
|
+
npm install --global @localez/cli@0.1.1
|
|
7
7
|
localez --version
|
|
8
8
|
```
|
|
9
9
|
|
|
10
|
-
The administrator issues a dedicated, expiring
|
|
10
|
+
The administrator opens the target Partner in **Admin → Partners**, then issues a dedicated, expiring CLI token in **CLI / AI Agent Token**. The token is automatically limited to that Partner. Inject `LOCALEZ_TOKEN` through your runtime's secret manager. The default service is `https://admin.localez.ai`; no base URL configuration is required for production. Do not use an admin login token or put credentials in URLs.
|
|
11
11
|
|
|
12
12
|
```sh
|
|
13
13
|
localez auth status
|
|
@@ -19,9 +19,9 @@ localez conversations get ID_FROM_LIST
|
|
|
19
19
|
localez conversations messages ID_FROM_LIST --limit 50
|
|
20
20
|
```
|
|
21
21
|
|
|
22
|
-
|
|
22
|
+
URL precedence is `--base-url` > `LOCALEZ_BASE_URL` > `https://admin.localez.ai`. Overrides must name the plugin origin (or proxy prefix), without `/agent/api/v1`. `hook.localez.ai` serves Engine webhooks and is not the plugin API. HTTPS is required, except for loopback development. JSON is the default output; diagnostics go to stderr. Always check the exit code and `ok` before consuming `data`. Use `meta.nextCursor` with the same filters to fetch the next page. IDs are opaque strings. A conversation reference identifies a specific session; an old reference does not follow a reopened customer thread.
|
|
23
23
|
|
|
24
|
-
0.1 supports platform credentials and Partner credentials
|
|
24
|
+
0.1 supports platform credentials and Partner service credentials. Partner service credentials cover conversations, messages and staff within the bound Partner; legacy employee-delegated credentials remain restricted by employee ownership and region visibility. Partner customer/ticket lookup, Supplier login credentials, prior-session history, translations, attachments, full ticket transcripts and all business writes are **not enabled**. `capabilities` advertises these limits. Current conversation lists include the latest closed session as well as open sessions; use `--status open` to restrict them. Ticket details expose at most 100 context messages, and only with `messages:read`.
|
|
25
25
|
|
|
26
26
|
Platform `staff list --kind operator`, `staff list --kind supplier-seat --supplier-id ID`, `customers get ID --platform line` and `tickets get ID` require the relevant scopes. Supplier A/B records are shared seats, not named employees. Credential issuance, expiry and revocation are managed by the administrator; the CLI never renews or elevates credentials.
|
|
27
27
|
|
package/dist/localez.mjs
CHANGED
|
@@ -1,18 +1,18 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import{readFileSync as
|
|
3
|
-
`;let t=Object.keys(s[0]),o=a=>String(typeof a=="object"?JSON.stringify(a):a??"").replace(/[\u0000-\u001f\u007f-\u009f]/g," ");return[t.join(" "),...s.map(a=>t.map(
|
|
2
|
+
import{readFileSync as L}from"node:fs";var h={"auth status":{path:"/me",parameters:[]},capabilities:{path:"/capabilities",parameters:[]},schema:{path:"/schema",parameters:["command"]},"conversations list":{path:"/conversations",parameters:["scope","kind","status","partnerId","supplierId","staffId","customerId","platform","updatedSince","limit","cursor"]},"conversations get":{path:"/conversations/:id",parameters:[]},"conversations messages":{path:"/conversations/:id/messages",parameters:["limit","cursor","after"]},"staff list":{path:"/staff",parameters:["kind","partnerId","supplierId","query","limit","cursor"]},"staff get":{path:"/staff/:id",parameters:[]},"customers get":{path:"/customers/:platform/:id",parameters:[]},"tickets get":{path:"/tickets/:id",parameters:[]}};var E=JSON.parse(L(new URL("../package.json",import.meta.url),"utf8")).version,N="https://admin.localez.ai",R={INVALID_ARGUMENT:2,CURSOR_INVALID:2,UNAUTHENTICATED:3,FORBIDDEN:4,NOT_FOUND:5,STATE_CONFLICT:6,SESSION_CHANGED:6,RATE_LIMITED:7,NETWORK_ERROR:8,TIMEOUT:8,DEPENDENCY_UNAVAILABLE:8,INTERNAL_ERROR:9,PROTOCOL_ERROR:9,UNSUPPORTED_CAPABILITY:10,VERSION_UNSUPPORTED:10},p=class extends Error{constructor(s,t){super(t),this.code=s}},l=n=>{throw new p("INVALID_ARGUMENT",n)},T=n=>n.replace(/-([a-z])/g,(s,t)=>t.toUpperCase());function U(n){let s=[],t=Object.create(null);for(let r=0;r<n.length;r++){let c=n[r];if(!c.startsWith("--")){s.push(c);continue}let f=c.indexOf("="),u=T(c.slice(2,f<0?void 0:f));Object.hasOwn(t,u)&&l(`Repeated option: ${c}.`);let O=f<0?n[++r]:c.slice(f+1);(!O||O.startsWith("--"))&&l(`Missing value for ${c}.`),t[u]=O}let o=Object.hasOwn(h,s[0])?s.shift():s.splice(0,2).join(" "),a=h[o];a||l("Unknown command. Run localez --help.");let d=[...a.parameters,"baseUrl","format","fields","timeoutMs",...a.path.includes(":platform")?["platform"]:[]];for(let r of Object.keys(t))d.includes(r)||l(`Unknown option: ${r}.`);let e=a.path;e.includes(":id")&&(s.length!==1&&l("Exactly one resource ID is required."),[".",".."].includes(s[0])&&l("Invalid resource ID."),e=e.replace(":id",encodeURIComponent(s.shift()))),o==="schema"&&((s.length!==1||t.command)&&l('Use localez schema "command name".'),t.command=s.shift()),s.length&&l("Unexpected positional arguments."),e.includes(":platform")&&((!t.platform||[".",".."].includes(t.platform))&&l("--platform is required."),e=e.replace(":platform",encodeURIComponent(t.platform))),t.format&&!["json","table"].includes(t.format)&&l("--format must be json or table."),t.limit&&(!/^\d+$/.test(t.limit)||+t.limit<1||+t.limit>100)&&l("--limit must be 1..100.");let i=t.timeoutMs??"30000";(!/^\d+$/.test(i)||+i<1e3||+i>12e4)&&l("--timeout-ms must be 1000..120000."),o==="staff list"&&!t.kind&&l("--kind is required.");let m=new URLSearchParams;for(let r of[...a.parameters,"fields"])t[r]!==void 0&&m.set(r,t[r]);return{name:o,path:e,query:m,options:t,timeout:Number(i)}}function w(n,s,t){let o;try{o=new URL(n)}catch{l("Set LOCALEZ_BASE_URL to the service HTTPS address.")}(o.username||o.password||o.search||o.hash)&&l("Base URL cannot contain credentials, query, or fragment.");let a=["localhost","127.0.0.1","[::1]"].includes(o.hostname);return o.protocol!=="https:"&&!(o.protocol==="http:"&&a)&&l("HTTPS is required except for loopback development."),o.pathname=`${o.pathname.replace(/\/$/,"")}/agent/api/v1${s}`,o.search=t.toString(),o}async function I(n,s,t,o,a){let d=Date.now()+12e4,e;for(let i=0;i<3;i++){let m=Math.floor(300*2**i+Math.random()*200);try{let r=await o(n,{method:"GET",redirect:"error",headers:{Authorization:`Bearer ${s}`,Accept:"application/json","User-Agent":`localez-cli/${E}`},signal:AbortSignal.timeout(Math.max(1,Math.min(t,d-Date.now())))}),c=r.headers.get("retry-after");if(c){let u=/^\d+$/.test(c)?Number(c):Math.max(0,(Date.parse(c)-Date.now())/1e3);Number.isFinite(u)&&(m=u*1e3)}let f=await r.text();try{e=JSON.parse(f)}catch{throw new p("PROTOCOL_ERROR","Service returned non-JSON output. Check the service address and deployment.")}if(!e||typeof e.ok!="boolean"||!e.meta||e.ok&&(!r.ok||!Object.hasOwn(e,"data"))||!e.ok&&(r.ok||typeof e.error?.code!="string"))throw new p("PROTOCOL_ERROR","Service returned an invalid response envelope.");if(e.meta.apiVersion!=="v1")throw new p("VERSION_UNSUPPORTED","Service API major version is not supported.");if(e.ok||![429,502,503,504].includes(r.status))return e;r.status===429&&(c||(m=6e4),e.error.retryAfterSeconds=Math.ceil(m/1e3))}catch(r){if(r instanceof p)throw r;let c=r?.name==="TimeoutError"||r?.name==="AbortError";e={ok:!1,error:{code:c?"TIMEOUT":"NETWORK_ERROR",message:c?"Request timed out.":"Unable to reach the service securely.",retryable:!0},meta:{apiVersion:"v1",requestId:null}}}if(i===2||Date.now()+m>=d)return e;await a(m)}return e}function y(n){let s=Array.isArray(n)?n:[n];if(!s.length)return`(no results)
|
|
3
|
+
`;let t=Object.keys(s[0]),o=a=>String(typeof a=="object"?JSON.stringify(a):a??"").replace(/[\u0000-\u001f\u007f-\u009f]/g," ");return[t.join(" "),...s.map(a=>t.map(d=>o(a[d])).join(" "))].join(`
|
|
4
4
|
`)+`
|
|
5
|
-
`}async function A(n,{env:s=process.env,stdout:t=process.stdout,stderr:o=process.stderr,fetchImpl:a=fetch,sleep:
|
|
5
|
+
`}async function A(n,{env:s=process.env,stdout:t=process.stdout,stderr:o=process.stderr,fetchImpl:a=fetch,sleep:d=e=>new Promise(i=>setTimeout(i,e))}={}){try{if(n.length===1&&n[0]==="--version")return t.write(`${E}
|
|
6
6
|
`),0;if(!n.length||n.includes("--help"))return t.write(`Localez CLI ${E} (read-only)
|
|
7
7
|
|
|
8
8
|
${Object.keys(h).join(`
|
|
9
9
|
`)}
|
|
10
10
|
|
|
11
11
|
Use a resource ID after get/messages; schema takes a quoted command name.
|
|
12
|
-
|
|
12
|
+
Credential: LOCALEZ_TOKEN. Default service: https://admin.localez.ai. Override: --base-url or LOCALEZ_BASE_URL.
|
|
13
13
|
Options: --format json|table, --fields a,b, --timeout-ms 30000.
|
|
14
14
|
Lists: --limit 1..100, --cursor VALUE. Use schema for command parameters.
|
|
15
|
-
`),0;let e=
|
|
15
|
+
`),0;let e=U(n),i=w(e.options.baseUrl??s.LOCALEZ_BASE_URL??N,e.path,e.query),m=s.LOCALEZ_TOKEN;if(!m||!/^lz_agent_[A-Za-z0-9_-]{43}$/.test(m))throw new p("UNAUTHENTICATED","Set LOCALEZ_TOKEN to a dedicated Agent credential issued by the administrator.");let r=await I(i,m,e.timeout,a,d);return t.write(e.options.format==="table"&&r.ok?y(r.data):JSON.stringify(r)+`
|
|
16
16
|
`),r.ok||o.write(`Localez: ${Object.hasOwn(R,r.error.code)?r.error.code:"PROTOCOL_ERROR"}
|
|
17
17
|
`),r.ok?0:R[r.error.code]??9}catch(e){let i=e instanceof p?e.code:"INTERNAL_ERROR",m=e instanceof p?e.message:"CLI could not complete the request.";return t.write(JSON.stringify({ok:!1,error:{code:i,message:m,retryable:!1},meta:{apiVersion:"v1",requestId:null}})+`
|
|
18
18
|
`),o.write(`Localez: ${i}
|