oneclient 0.1.0 → 0.1.2

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 CHANGED
@@ -1,44 +1,74 @@
1
1
  # oneclient
2
2
 
3
- One package for the complete OneClient TypeScript SDK and its developer CLI.
3
+ One package for the complete OneClient TypeScript SDK and developer CLI.
4
4
 
5
5
  ```bash
6
6
  npm install oneclient
7
7
  npx oneclient login --email you@company.com
8
- npx oneclient init
8
+ npx oneclient init --project-name "My app"
9
9
  ```
10
10
 
11
- The same package works in code:
11
+ The hosted CLI already knows the production control API. Set `ONECLIENT_API_URL` only when a command
12
+ targets a project environment or a self-hosted/local control plane.
13
+
14
+ ## Browser application
15
+
16
+ `oneclient init` shows the development API URL, environment ID, and initial `pk_*`/`sk_*` values
17
+ once. A publishable key is safe to embed in browser code; server and deploy keys are not.
12
18
 
13
19
  ```ts
14
20
  import { createClient } from "oneclient";
15
21
 
16
22
  const one = createClient({
17
- baseUrl: process.env.ONECLIENT_API_URL!,
18
- serverKey: process.env.ONECLIENT_SERVER_KEY!,
23
+ baseUrl: import.meta.env.VITE_ONECLIENT_API_URL,
24
+ publishableKey: import.meta.env.VITE_ONECLIENT_PUBLISHABLE_KEY,
19
25
  });
20
26
 
21
- const notes = await one.from("notes").select("id", "title").limit(20).execute();
27
+ await one.auth.sendOtp({ email: "developer@example.com" });
28
+ ```
29
+
30
+ ## Schema, policy, and local auth origin
31
+
32
+ ```bash
33
+ export ONECLIENT_API_URL=https://dev_REFERENCE.api.one-client.com
34
+ export ONECLIENT_SERVER_KEY=sk_REDACTED
35
+
36
+ npx oneclient sql --file migrations/001_create_feedback.sql
37
+ npx oneclient policy:publish --table feedback --file oneclient.policy.json
38
+ npx oneclient auth-origin:add --environment env_DEVELOPMENT --origin http://localhost:5173
22
39
  ```
23
40
 
24
- Useful commands:
41
+ Each SQL file contains one statement so its read, write, and retained-storage bounds are explicit.
42
+
43
+ ## Development deployment
25
44
 
26
45
  ```bash
27
- oneclient status
28
- oneclient projects
29
- oneclient usage
30
- oneclient deployments --environment env_...
31
- oneclient models
32
- oneclient billing:topup --usd 25
33
- oneclient doctor
34
- oneclient docs
35
- oneclient mcp
46
+ npm run build
47
+ npx oneclient artifact:create \
48
+ --directory . \
49
+ --manifest .oneclient/manifest.json \
50
+ --archive .oneclient/app.tar \
51
+ --framework vite \
52
+ --build-command "npm run build" \
53
+ --output-directory dist \
54
+ --lockfile npm
55
+
56
+ npx oneclient deploy-token:create --environment env_DEVELOPMENT --name local-cli
57
+ export ONECLIENT_DEPLOY_TOKEN=dp_VALUE_SHOWN_ONCE
58
+ npx oneclient deploy \
59
+ --manifest .oneclient/manifest.json \
60
+ --archive .oneclient/app.tar \
61
+ --project prj_PROJECT \
62
+ --environment env_DEVELOPMENT \
63
+ --wait
64
+
65
+ # The deploy token is an environment key; revoke it by the id returned at creation.
66
+ npx oneclient key:revoke --environment env_DEVELOPMENT --key key_DEPLOY_TOKEN
36
67
  ```
37
68
 
38
- Interactive terminals receive a compact, colored interface. Pipes and CI receive stable JSON by
39
- default; `--json` makes that behavior explicit. Developer login credentials are stored in an
40
- owner-only local file. `sk_*` and `dp_*` credentials stay in environment variables and are never
41
- persisted by the CLI.
69
+ Interactive terminals receive a compact interface. Pipes and CI receive stable JSON automatically;
70
+ `--json` makes that behavior explicit. Developer sessions are stored in an owner-only local file.
71
+ `sk_*` and `dp_*` values stay in environment variables and are never persisted by the CLI.
42
72
 
43
- Documentation: [one-client.com/docs](https://one-client.com/docs) · AI context:
44
- [one-client.com/llms.txt](https://one-client.com/llms.txt)
73
+ Full quickstart: [one-client.com/docs/quickstart](https://one-client.com/docs/quickstart) · Agent
74
+ context: [one-client.com/llms.txt](https://one-client.com/llms.txt)
@@ -0,0 +1,7 @@
1
+ export declare const DEFAULT_CONTROL_API_URL = "https://control.one-client.com";
2
+ export declare const DEFAULT_CONSOLE_ORIGIN = "https://console.one-client.com";
3
+ export declare function developerAuthOrigin(apiUrl: string): string;
4
+ export declare function inferOrganizationId(explicitId: string | undefined, organizations: Array<{
5
+ id: string;
6
+ }>): string;
7
+ //# sourceMappingURL=config.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,uBAAuB,mCAAmC,CAAC;AACxE,eAAO,MAAM,sBAAsB,mCAAmC,CAAC;AAEvE,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAG1D;AAED,wBAAgB,mBAAmB,CACjC,UAAU,EAAE,MAAM,GAAG,SAAS,EAC9B,aAAa,EAAE,KAAK,CAAC;IAAE,EAAE,EAAE,MAAM,CAAA;CAAE,CAAC,GACnC,MAAM,CAMR"}
package/dist/config.js ADDED
@@ -0,0 +1,15 @@
1
+ export const DEFAULT_CONTROL_API_URL = "https://control.one-client.com";
2
+ export const DEFAULT_CONSOLE_ORIGIN = "https://console.one-client.com";
3
+ export function developerAuthOrigin(apiUrl) {
4
+ const origin = new URL(apiUrl).origin;
5
+ return origin === DEFAULT_CONTROL_API_URL ? DEFAULT_CONSOLE_ORIGIN : origin;
6
+ }
7
+ export function inferOrganizationId(explicitId, organizations) {
8
+ if (explicitId)
9
+ return explicitId;
10
+ if (organizations.length === 1)
11
+ return organizations[0].id;
12
+ if (organizations.length === 0)
13
+ throw new Error("Create an organization with `oneclient organization:create` first");
14
+ throw new Error("This account has multiple organizations; pass --organization <id>");
15
+ }
package/dist/index.js CHANGED
@@ -14,11 +14,12 @@ import { deployTokenFromEnvironment, parseServerKeyScopes, serverKeyFromEnvironm
14
14
  import { secretValueFromOptions } from "./secret-input.js";
15
15
  import { brand, formatOutput, statusLine } from "./presenter.js";
16
16
  import { slugify } from "./slug.js";
17
+ import { DEFAULT_CONTROL_API_URL, inferOrganizationId } from "./config.js";
17
18
  const program = new Command();
18
19
  program
19
20
  .name("oneclient")
20
21
  .description("Deploy and manage OneClient projects")
21
- .version("0.1.0")
22
+ .version("0.1.2")
22
23
  .option("--api-url <url>", "Control or environment API URL", process.env.ONECLIENT_API_URL)
23
24
  .option("--organization <id>", "Organization ID", process.env.ONECLIENT_ORGANIZATION_ID)
24
25
  .option("--json", "Print stable JSON for scripts and agents", false)
@@ -29,7 +30,7 @@ program
29
30
  .description("Sign in as a platform developer with an email OTP")
30
31
  .requiredOption("--email <email>")
31
32
  .action(async (options) => {
32
- const apiUrl = await resolveApiUrl();
33
+ const apiUrl = await resolveControlApiUrl();
33
34
  await runStep(`Sending a private sign-in code to ${options.email.trim()}`, () => sendDeveloperOtp(apiUrl, options.email));
34
35
  const otp = process.env.ONECLIENT_OTP ?? (await promptForOtp());
35
36
  const sessionToken = await runStep("Verifying your code", () => verifyDeveloperOtp(apiUrl, options.email, otp));
@@ -114,7 +115,7 @@ program
114
115
  program
115
116
  .command("projects")
116
117
  .description("List projects")
117
- .action(async () => print(await sessionClient().then((api) => api.projects.list()), "Projects"));
118
+ .action(async () => print(await organizationSessionClient().then((api) => api.projects.list()), "Projects"));
118
119
  program
119
120
  .command("project:create")
120
121
  .description("Create a project and its development and production environments")
@@ -154,7 +155,7 @@ program
154
155
  program
155
156
  .command("usage")
156
157
  .description("Show the prepaid wallet and retention reserve")
157
- .action(async () => print(await sessionClient().then((api) => api.usage.summary()), "Usage & balance"));
158
+ .action(async () => print(await organizationSessionClient().then((api) => api.usage.summary()), "Usage & balance"));
158
159
  program
159
160
  .command("status")
160
161
  .description("Show account, organization, project, and spend status in one view")
@@ -198,7 +199,7 @@ program
198
199
  const limit = Number(options.limit);
199
200
  if (!Number.isInteger(limit) || limit < 1 || limit > 100)
200
201
  throw new Error("limit must be an integer from 1 through 100");
201
- print(await sessionClient().then((api) => api.usage.activity(limit)), "Recent activity");
202
+ print(await organizationSessionClient().then((api) => api.usage.activity(limit)), "Recent activity");
202
203
  });
203
204
  program
204
205
  .command("deployments")
@@ -213,7 +214,9 @@ program
213
214
  .command("doctor")
214
215
  .description("Check local credentials and live OneClient connectivity")
215
216
  .action(async () => {
217
+ const options = program.opts();
216
218
  const credentials = await resolvedDeveloperCredentials();
219
+ const controlApi = options.apiUrl ?? credentials?.apiUrl ?? DEFAULT_CONTROL_API_URL;
217
220
  let account = null;
218
221
  let controlReachable = false;
219
222
  if (credentials) {
@@ -221,9 +224,20 @@ program
221
224
  account = response.user.email;
222
225
  controlReachable = true;
223
226
  }
227
+ else {
228
+ try {
229
+ const response = await fetch(new URL("/v1/me", `${controlApi}/`), {
230
+ headers: { Accept: "application/json" },
231
+ });
232
+ controlReachable = response.status < 500;
233
+ }
234
+ catch {
235
+ controlReachable = false;
236
+ }
237
+ }
224
238
  print({
225
239
  node: process.version,
226
- controlApi: credentials?.apiUrl ?? null,
240
+ controlApi,
227
241
  signedIn: Boolean(credentials),
228
242
  account,
229
243
  controlReachable,
@@ -255,11 +269,11 @@ program
255
269
  program
256
270
  .command("billing:portal")
257
271
  .description("Create a secure Stripe billing-portal session")
258
- .action(async () => print(await sessionClient().then((api) => api.billing.portal()), "Billing portal"));
272
+ .action(async () => print(await organizationSessionClient().then((api) => api.billing.portal()), "Billing portal"));
259
273
  program
260
274
  .command("billing:subscribe")
261
275
  .description("Create a subscription Checkout session")
262
- .action(async () => print(await sessionClient().then((api) => api.billing.subscriptionCheckout()), "Subscription checkout"));
276
+ .action(async () => print(await organizationSessionClient().then((api) => api.billing.subscriptionCheckout()), "Subscription checkout"));
263
277
  program
264
278
  .command("billing:topup")
265
279
  .description("Create a prepaid credit Checkout session")
@@ -269,12 +283,12 @@ program
269
283
  if (!Number.isFinite(dollars) || dollars < 25 || Math.round(dollars * 100) !== dollars * 100)
270
284
  throw new Error("usd must be at least 25 with no more than two decimal places");
271
285
  const amountMicros = String(Math.round(dollars * 1_000_000));
272
- print(await sessionClient().then((api) => api.billing.topupCheckout(amountMicros)), "Credit checkout");
286
+ print(await organizationSessionClient().then((api) => api.billing.topupCheckout(amountMicros)), "Credit checkout");
273
287
  });
274
288
  program
275
289
  .command("domain:list")
276
290
  .description("List application, API, and sending domains")
277
- .action(async () => print(await sessionClient().then((api) => api.domains.list()), "Domains"));
291
+ .action(async () => print(await organizationSessionClient().then((api) => api.domains.list()), "Domains"));
278
292
  program
279
293
  .command("domain:add")
280
294
  .description("Attach a custom hostname")
@@ -372,6 +386,53 @@ program
372
386
  .limit(Number(options.limit))
373
387
  .execute()));
374
388
  });
389
+ program
390
+ .command("sql")
391
+ .description("Execute one bounded SQL statement or trusted server query from a file")
392
+ .requiredOption("--file <path>")
393
+ .option("--max-rows-read <rows>", "Maximum rows the statement may read", "1000")
394
+ .option("--max-rows-written <rows>", "Maximum rows the statement may write", "100")
395
+ .option("--max-storage-delta-bytes <bytes>", "Maximum retained storage increase", "65536")
396
+ .action(async (options) => {
397
+ const maxRowsRead = positiveInteger(options.maxRowsRead, "max-rows-read");
398
+ const maxRowsWritten = positiveInteger(options.maxRowsWritten, "max-rows-written");
399
+ const maxStorageDeltaBytes = nonNegativeInteger(options.maxStorageDeltaBytes, "max-storage-delta-bytes");
400
+ const sql = (await readFile(options.file, "utf8")).trim();
401
+ if (!sql)
402
+ throw new Error("SQL file cannot be empty");
403
+ print(await serverClient().then((api) => api.sql.execute({
404
+ sql,
405
+ maxRowsRead,
406
+ maxRowsWritten,
407
+ maxStorageDeltaBytes,
408
+ })), "SQL completed");
409
+ });
410
+ program
411
+ .command("policy:publish")
412
+ .description("Validate and activate a deny-by-default data policy from a JSON file")
413
+ .requiredOption("--table <table>")
414
+ .requiredOption("--file <path>")
415
+ .action(async (options) => {
416
+ const policy = JSON.parse(await readFile(options.file, "utf8"));
417
+ print(await serverClient().then((api) => api.data.publishPolicy(options.table, policy)), "Policy activated");
418
+ });
419
+ program
420
+ .command("auth-origin:list")
421
+ .description("List trusted browser and native authentication origins")
422
+ .requiredOption("--environment <id>")
423
+ .action(async (options) => print(await sessionClient().then((api) => api.authOrigins.list(options.environment))));
424
+ program
425
+ .command("auth-origin:add")
426
+ .description("Trust a browser origin or native application scheme for authentication")
427
+ .requiredOption("--environment <id>")
428
+ .requiredOption("--origin <origin>")
429
+ .action(async (options) => print(await sessionClient().then((api) => api.authOrigins.create(options.environment, options.origin)), "Authentication origin added"));
430
+ program
431
+ .command("auth-origin:delete")
432
+ .description("Remove a trusted authentication origin")
433
+ .requiredOption("--environment <id>")
434
+ .requiredOption("--origin-id <id>")
435
+ .action(async (options) => print(await sessionClient().then((api) => api.authOrigins.delete(options.environment, options.originId))));
375
436
  program
376
437
  .command("artifact:create")
377
438
  .description("Create a validated manifest and tar archive from a project directory")
@@ -511,13 +572,21 @@ async function sessionClient(organizationId) {
511
572
  : {}),
512
573
  });
513
574
  }
575
+ async function organizationSessionClient() {
576
+ const explicitId = program.opts().organization;
577
+ if (explicitId)
578
+ return sessionClient(explicitId);
579
+ const base = await sessionClient();
580
+ const organizations = await base.organizations.list();
581
+ return sessionClient(inferOrganizationId(undefined, organizations));
582
+ }
514
583
  async function deployClient() {
515
584
  const options = program.opts();
516
585
  const deployToken = deployTokenFromEnvironment();
517
586
  if (!deployToken?.startsWith("dp_"))
518
587
  throw new Error("This command requires ONECLIENT_DEPLOY_TOKEN with a dp_* token");
519
588
  return createClient({
520
- baseUrl: await resolveApiUrl(),
589
+ baseUrl: await resolveControlApiUrl(),
521
590
  deployToken,
522
591
  ...(options.organization ? { organizationId: options.organization } : {}),
523
592
  });
@@ -528,7 +597,7 @@ async function serverClient() {
528
597
  if (!serverKey?.startsWith("sk_"))
529
598
  throw new Error("This command requires ONECLIENT_SERVER_KEY with an sk_* key");
530
599
  return createClient({
531
- baseUrl: await resolveApiUrl(),
600
+ baseUrl: resolveEnvironmentApiUrl(),
532
601
  serverKey,
533
602
  ...(options.organization ? { organizationId: options.organization } : {}),
534
603
  });
@@ -539,18 +608,32 @@ async function resolvedDeveloperCredentials() {
539
608
  const sessionToken = process.env.ONECLIENT_SESSION_TOKEN ?? stored?.sessionToken;
540
609
  if (!sessionToken)
541
610
  return null;
542
- const apiUrl = options.apiUrl ?? stored?.apiUrl;
543
- if (!apiUrl)
544
- throw new Error("Set ONECLIENT_API_URL or pass --api-url");
611
+ const apiUrl = options.apiUrl ?? stored?.apiUrl ?? DEFAULT_CONTROL_API_URL;
545
612
  return { apiUrl, sessionToken };
546
613
  }
547
- async function resolveApiUrl() {
614
+ async function resolveControlApiUrl() {
548
615
  const options = program.opts();
549
616
  const apiUrl = options.apiUrl ?? (await readDeveloperCredentials())?.apiUrl;
617
+ return apiUrl ?? DEFAULT_CONTROL_API_URL;
618
+ }
619
+ function resolveEnvironmentApiUrl() {
620
+ const apiUrl = program.opts().apiUrl;
550
621
  if (!apiUrl)
551
- throw new Error("Set ONECLIENT_API_URL or pass --api-url");
622
+ throw new Error("Set ONECLIENT_API_URL to the environment API URL or pass --api-url https://<environment>.api.one-client.com");
552
623
  return apiUrl;
553
624
  }
625
+ function positiveInteger(value, name) {
626
+ const parsed = Number(value);
627
+ if (!Number.isSafeInteger(parsed) || parsed < 1)
628
+ throw new Error(`${name} must be a positive integer`);
629
+ return parsed;
630
+ }
631
+ function nonNegativeInteger(value, name) {
632
+ const parsed = Number(value);
633
+ if (!Number.isSafeInteger(parsed) || parsed < 0)
634
+ throw new Error(`${name} must be a non-negative integer`);
635
+ return parsed;
636
+ }
554
637
  async function promptForOtp() {
555
638
  if (!process.stdin.isTTY)
556
639
  throw new Error("Set ONECLIENT_OTP for a non-interactive login");
@@ -1 +1 @@
1
- {"version":3,"file":"platform-auth.d.ts","sourceRoot":"","sources":["../src/platform-auth.ts"],"names":[],"mappings":"AAOA,wBAAsB,gBAAgB,CACpC,MAAM,EAAE,MAAM,EACd,KAAK,EAAE,MAAM,EACb,OAAO,GAAE,OAAO,UAAU,CAAC,KAAwB,GAClD,OAAO,CAAC,IAAI,CAAC,CAKf;AAED,wBAAsB,kBAAkB,CACtC,MAAM,EAAE,MAAM,EACd,KAAK,EAAE,MAAM,EACb,GAAG,EAAE,MAAM,EACX,OAAO,GAAE,OAAO,UAAU,CAAC,KAAwB,GAClD,OAAO,CAAC,MAAM,CAAC,CAQjB;AAED,wBAAsB,sBAAsB,CAC1C,MAAM,EAAE,MAAM,EACd,YAAY,EAAE,MAAM,EACpB,OAAO,GAAE,OAAO,UAAU,CAAC,KAAwB,GAClD,OAAO,CAAC,IAAI,CAAC,CAEf"}
1
+ {"version":3,"file":"platform-auth.d.ts","sourceRoot":"","sources":["../src/platform-auth.ts"],"names":[],"mappings":"AAQA,wBAAsB,gBAAgB,CACpC,MAAM,EAAE,MAAM,EACd,KAAK,EAAE,MAAM,EACb,OAAO,GAAE,OAAO,UAAU,CAAC,KAAwB,GAClD,OAAO,CAAC,IAAI,CAAC,CAKf;AAED,wBAAsB,kBAAkB,CACtC,MAAM,EAAE,MAAM,EACd,KAAK,EAAE,MAAM,EACb,GAAG,EAAE,MAAM,EACX,OAAO,GAAE,OAAO,UAAU,CAAC,KAAwB,GAClD,OAAO,CAAC,MAAM,CAAC,CAQjB;AAED,wBAAsB,sBAAsB,CAC1C,MAAM,EAAE,MAAM,EACd,YAAY,EAAE,MAAM,EACpB,OAAO,GAAE,OAAO,UAAU,CAAC,KAAwB,GAClD,OAAO,CAAC,IAAI,CAAC,CAEf"}
@@ -1,4 +1,5 @@
1
1
  import { validateApiUrl } from "./credentials.js";
2
+ import { developerAuthOrigin } from "./config.js";
2
3
  export async function sendDeveloperOtp(apiUrl, email, fetcher = globalThis.fetch) {
3
4
  await authRequest(fetcher, apiUrl, "/v1/platform/auth/email-otp/send-verification-otp", {
4
5
  email: normalizeEmail(email),
@@ -24,6 +25,7 @@ async function authRequest(fetcher, apiUrl, path, body, sessionToken) {
24
25
  headers: {
25
26
  Accept: "application/json",
26
27
  "Content-Type": "application/json",
28
+ Origin: developerAuthOrigin(apiUrl),
27
29
  ...(sessionToken ? { Authorization: `Bearer ${sessionToken}` } : {}),
28
30
  },
29
31
  body: JSON.stringify(body),
package/llms.txt CHANGED
@@ -11,6 +11,14 @@ MCP: https://control.one-client.com/mcp
11
11
 
12
12
  Install: npm install oneclient
13
13
  CLI login: npx oneclient login --email you@company.com
14
+ Control API configuration is automatic for the hosted service.
15
+
16
+ First project flow:
17
+ 1. npx oneclient init --project-name "My app"
18
+ 2. Capture the shown-once development pk_* and sk_* values.
19
+ 3. Apply SQL with `oneclient sql` and policy with `oneclient policy:publish`.
20
+ 4. Add local/custom auth origins with `oneclient auth-origin:add`.
21
+ 5. Build, run `oneclient artifact:create`, create a dp_* token, then run `oneclient deploy --wait`.
14
22
 
15
23
  Use pk_* only in browser/mobile clients, sk_* only on trusted servers, and dp_* only for deploys.
16
24
  Never expose sk_* or dp_* values in client bundles or model output. Every mutation is idempotent and
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "oneclient",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "description": "One package for the complete OneClient TypeScript SDK and developer CLI.",
5
5
  "license": "UNLICENSED",
6
6
  "homepage": "https://one-client.com",