@b3labs/b3iq-cli 0.0.0-stage → 0.0.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.
@@ -0,0 +1,85 @@
1
+ import { homedir } from "node:os";
2
+ import { join } from "node:path";
3
+ import { mkdirSync, readFileSync, writeFileSync, chmodSync, existsSync, rmSync } from "node:fs";
4
+ export const DEFAULT_API_URL = "https://control-plane.b3iq.org";
5
+ const DEV_WORKER_PORT = 8787;
6
+ const DEV_SPA_PORT = 5175;
7
+ const DEV_SLOT_STRIDE = 10;
8
+ const DEV_SLOT_COUNT = 10;
9
+ export function localDashboardUrl(apiUrl) {
10
+ const offset = Number(apiUrl.port) - DEV_WORKER_PORT;
11
+ const slot = offset >= 0 && offset % DEV_SLOT_STRIDE === 0 && offset / DEV_SLOT_STRIDE < DEV_SLOT_COUNT ? offset / DEV_SLOT_STRIDE : 0;
12
+ return `http://127.0.0.1:${DEV_SPA_PORT + DEV_SLOT_STRIDE * slot}`;
13
+ }
14
+ export const configDir = () => process.env.B3IQ_CONFIG_DIR || join(homedir(), ".b3iq");
15
+ const dir = configDir;
16
+ const file = () => join(dir(), "config.json");
17
+ export function readConfig() {
18
+ try {
19
+ return JSON.parse(readFileSync(file(), "utf8"));
20
+ }
21
+ catch {
22
+ return {};
23
+ }
24
+ }
25
+ export function writeConfig(patch) {
26
+ mkdirSync(dir(), { recursive: true, mode: 0o700 });
27
+ const next = { ...readConfig(), ...patch };
28
+ writeFileSync(file(), JSON.stringify(next, null, 2) + "\n", { mode: 0o600 });
29
+ chmodSync(file(), 0o600);
30
+ return next;
31
+ }
32
+ export function clearToken() {
33
+ const cfg = readConfig();
34
+ delete cfg.token;
35
+ delete cfg.expires_at;
36
+ delete cfg.pat_id;
37
+ delete cfg.org_id;
38
+ delete cfg.org_name;
39
+ if (Object.keys(cfg).length === 0) {
40
+ if (existsSync(file()))
41
+ rmSync(file());
42
+ }
43
+ else {
44
+ writeConfig({ ...cfg, token: undefined, expires_at: undefined, pat_id: undefined, org_id: undefined, org_name: undefined });
45
+ }
46
+ }
47
+ export const configPath = file;
48
+ export function resolveSettings() {
49
+ const cfg = readConfig();
50
+ const envToken = process.env.B3IQ_TOKEN;
51
+ const savedOrgId = envToken ? undefined : cfg.org_id;
52
+ const orgId = process.env.B3IQ_ORG_ID || savedOrgId;
53
+ return {
54
+ apiUrl: (process.env.B3IQ_API_URL || cfg.api_url || DEFAULT_API_URL).replace(/\/+$/, ""),
55
+ token: envToken || cfg.token,
56
+ orgId,
57
+ orgName: orgId && orgId === savedOrgId ? cfg.org_name : undefined,
58
+ defaultMachine: cfg.default_machine,
59
+ savedExpiresAt: envToken ? undefined : cfg.expires_at,
60
+ };
61
+ }
62
+ const LOOPBACK_HOSTS = new Set(["localhost", "127.0.0.1", "[::1]"]);
63
+ export const isLoopbackHost = (hostname) => LOOPBACK_HOSTS.has(hostname);
64
+ export function loginApprovalUrl(serverUrl, userCode) {
65
+ let base = process.env.B3IQ_DASHBOARD_URL;
66
+ if (!base) {
67
+ try {
68
+ const apiUrl = new URL(resolveSettings().apiUrl);
69
+ if (isLoopbackHost(apiUrl.hostname))
70
+ base = localDashboardUrl(apiUrl);
71
+ }
72
+ catch {
73
+ }
74
+ }
75
+ if (!base)
76
+ return serverUrl;
77
+ return `${base.replace(/\/+$/, "")}/dashboard/cli-auth?code=${encodeURIComponent(userCode)}`;
78
+ }
79
+ export function orgLabel(orgId, orgName) {
80
+ return orgName ? `${orgName} (${orgId})` : orgId;
81
+ }
82
+ export function listedOrgLabel(orgId) {
83
+ const { orgId: savedId, orgName } = resolveSettings();
84
+ return orgLabel(orgId, savedId === orgId ? orgName : undefined);
85
+ }
@@ -0,0 +1,19 @@
1
+ export const DESKTOP_PERMISSIONS = {
2
+ sandbox_mode: "workspace-write",
3
+ approval_policy: "on-request",
4
+ network_access: false,
5
+ };
6
+ export const DESKTOP_SERVER_LIFECYCLE_INSTRUCTIONS = `Local servers and network in this desktop session:
7
+ sandbox_mode stays workspace-write and approval_policy stays on-request. Leave those defaults in place. Do not switch sandbox_mode to danger-full-access. Do not set sandbox_permissions to require_escalated to obtain network access.
8
+
9
+ sandbox_permissions use_default keeps the current sandbox. It does not leave the sandbox and it does not grant network access. with_additional_permissions also stays inside the sandbox. In this session it is not a substitute for an approved request_permissions result.
10
+
11
+ Loopback stays blocked until network permission is granted. When curl reports Operation not permitted for 127.0.0.1, treat that as a sandbox denial, including when the exit code is 7. A listening server can still be up. That denial is separate from an inference transport failure.
12
+
13
+ When a command needs loopback or other network access, call request_permissions with permissions.network.enabled set to true and a short reason that names the host. Wait for the tool result. Retry that same command only after the result grants network. If the result denies it, stop. Tell the user what was denied and that a later approval can be requested. Do not poll with write_stdin, restart the server, or try other ports to get around the denial.
14
+
15
+ Before starting a server, inspect the listener on the intended port and its owning pid and working directory. If this task already owns that listener, reuse the pid and its URL. Do not start a second process. If another process owns the port, report the conflict, including the pid, and stop. Do not walk ports after EADDRINUSE.
16
+
17
+ Start the server once, keep its pid and session id, and check that it is listening at most a few times within a bounded deadline. Stop only a process this task started. Never kill an unrelated or system listener.
18
+
19
+ Verify with curl --show-error --silent --max-time 5 --fail-with-body. Do not pipe that command through head or another filter. Keep stderr and the exit status visible. Classify the result as process exit, port conflict, startup failure, sandbox denial, permission denial, connection refusal, timeout, or an HTTP status. HTTP 401, 403 and 500 mean the connection reached an application and the application returned that status. A sandbox denial, a refused connection, a timeout, and an inference transport error are different outcomes and need different next steps.`;