orangeslice 2.0.2 → 2.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 CHANGED
@@ -8,7 +8,15 @@ Orangeslice provides a `services.*` API for B2B research, enrichment, scraping,
8
8
  npx orangeslice
9
9
  ```
10
10
 
11
- The CLI copies docs to `./orangeslice-docs`, creates `./orangeslice-docs/AGENTS.md`, initializes `package.json` when missing, and installs `orangeslice` in the current directory.
11
+ The CLI copies docs to `./orangeslice-docs`, creates `./orangeslice-docs/AGENTS.md`, initializes `package.json` when missing, installs `orangeslice` in the current directory, opens browser auth, and stores your API key in `~/.config/orangeslice/config.json`.
12
+
13
+ ### Auth behavior
14
+
15
+ - `npx orangeslice` uses browser-based device auth and auto-provisions an API key.
16
+ - SDK credential precedence:
17
+ 1. `configure({ apiKey })`
18
+ 2. `ORANGESLICE_API_KEY` env var
19
+ 3. `~/.config/orangeslice/config.json`
12
20
 
13
21
  Install as a dependency when writing app code:
14
22
 
package/dist/api.js CHANGED
@@ -11,6 +11,7 @@ const DEFAULT_BASE_URL = "https://enrichly-production.up.railway.app";
11
11
  const POLL_TIMEOUT_MS = 600000;
12
12
  const DEFAULT_POLL_INTERVAL_MS = 1000;
13
13
  const DEFAULT_INLINE_WAIT_MS = 5000;
14
+ const USER_CONFIG_PATH = ".config/orangeslice/config.json";
14
15
  const _config = {};
15
16
  function configure(opts) {
16
17
  if (opts.apiKey !== undefined)
@@ -22,7 +23,26 @@ function resolveBaseUrl() {
22
23
  return (_config.baseUrl || process.env.ORANGESLICE_BASE_URL || DEFAULT_BASE_URL).replace(/\/+$/, "");
23
24
  }
24
25
  function resolveApiKey() {
25
- return _config.apiKey || process.env.ORANGESLICE_API_KEY || "";
26
+ return _config.apiKey || process.env.ORANGESLICE_API_KEY || readApiKeyFromConfigFile() || "";
27
+ }
28
+ function readApiKeyFromConfigFile() {
29
+ // Guard for browser-like runtimes.
30
+ if (!process?.versions?.node)
31
+ return "";
32
+ try {
33
+ const os = require("os");
34
+ const fs = require("fs");
35
+ const path = require("path");
36
+ const configPath = path.join(os.homedir(), USER_CONFIG_PATH);
37
+ if (!fs.existsSync(configPath))
38
+ return "";
39
+ const raw = fs.readFileSync(configPath, "utf8");
40
+ const parsed = JSON.parse(raw);
41
+ return typeof parsed.apiKey === "string" ? parsed.apiKey : "";
42
+ }
43
+ catch {
44
+ return "";
45
+ }
26
46
  }
27
47
  function sleep(ms) {
28
48
  return new Promise((resolve) => setTimeout(resolve, ms));
@@ -117,7 +137,7 @@ async function post(endpoint, payload) {
117
137
  const apiKey = resolveApiKey();
118
138
  if (!apiKey) {
119
139
  throw new Error("[orangeslice] No API key configured. " +
120
- "Set ORANGESLICE_API_KEY in your environment or call configure({ apiKey: 'osk_...' }).");
140
+ "Run `npx orangeslice`, set ORANGESLICE_API_KEY in your environment, or call configure({ apiKey: 'osk_...' }).");
121
141
  }
122
142
  const url = `${baseUrl}${endpoint}`;
123
143
  const body = JSON.stringify({ ...payload, inlineWaitMs: DEFAULT_INLINE_WAIT_MS });
package/dist/cli.js CHANGED
@@ -36,11 +36,14 @@ var __importStar = (this && this.__importStar) || (function () {
36
36
  Object.defineProperty(exports, "__esModule", { value: true });
37
37
  const child_process_1 = require("child_process");
38
38
  const fs = __importStar(require("fs"));
39
+ const os = __importStar(require("os"));
39
40
  const path = __importStar(require("path"));
40
- const readline = __importStar(require("readline"));
41
41
  const LEGACY_DOCS_DIR = path.join(__dirname, "..", "docs");
42
42
  const TARGET_DIR = path.join(process.cwd(), "orangeslice-docs");
43
43
  const AGENTS_FILE = path.join(TARGET_DIR, "AGENTS.md");
44
+ const AUTH_API_BASE_URL = "https://www.orangeslice.ai";
45
+ const CONFIG_DIR = path.join(os.homedir(), ".config", "orangeslice");
46
+ const CONFIG_PATH = path.join(CONFIG_DIR, "config.json");
44
47
  function isDir(p) {
45
48
  try {
46
49
  return fs.statSync(p).isDirectory();
@@ -111,73 +114,93 @@ function installOrangeslice(cwd) {
111
114
  console.log(" Installing orangeslice...");
112
115
  (0, child_process_1.execSync)("npm install orangeslice", { stdio: "inherit", cwd });
113
116
  }
114
- function prompt(question) {
115
- const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
116
- return new Promise((resolve) => {
117
- rl.question(question, (answer) => {
118
- rl.close();
119
- resolve(answer.trim());
120
- });
121
- });
117
+ function readConfigFile() {
118
+ try {
119
+ if (!fs.existsSync(CONFIG_PATH))
120
+ return {};
121
+ const parsed = JSON.parse(fs.readFileSync(CONFIG_PATH, "utf8"));
122
+ return parsed && typeof parsed === "object" ? parsed : {};
123
+ }
124
+ catch {
125
+ return {};
126
+ }
122
127
  }
123
- function readEnvFile(envPath) {
124
- const entries = new Map();
125
- if (!fs.existsSync(envPath))
126
- return entries;
127
- const lines = fs.readFileSync(envPath, "utf8").split("\n");
128
- for (const line of lines) {
129
- const match = line.match(/^([A-Za-z_][A-Za-z0-9_]*)=(.*)/);
130
- if (match)
131
- entries.set(match[1], match[2]);
128
+ function saveConfigFile(config) {
129
+ fs.mkdirSync(CONFIG_DIR, { recursive: true });
130
+ fs.writeFileSync(CONFIG_PATH, JSON.stringify(config, null, 2) + "\n", { encoding: "utf8", mode: 0o600 });
131
+ try {
132
+ fs.chmodSync(CONFIG_PATH, 0o600);
132
133
  }
133
- return entries;
134
+ catch { }
134
135
  }
135
- function writeEnvVar(envPath, key, value) {
136
- const entries = readEnvFile(envPath);
137
- entries.set(key, value);
138
- const lines = [];
139
- if (fs.existsSync(envPath)) {
140
- const original = fs.readFileSync(envPath, "utf8").split("\n");
141
- let replaced = false;
142
- for (const line of original) {
143
- const match = line.match(/^([A-Za-z_][A-Za-z0-9_]*)=(.*)/);
144
- if (match && match[1] === key) {
145
- lines.push(`${key}=${value}`);
146
- replaced = true;
147
- }
148
- else {
149
- lines.push(line);
150
- }
136
+ function openBrowser(url) {
137
+ try {
138
+ if (process.platform === "darwin") {
139
+ (0, child_process_1.execSync)(`open "${url}"`, { stdio: "ignore" });
140
+ return;
151
141
  }
152
- if (!replaced) {
153
- if (lines.length > 0 && lines[lines.length - 1] !== "")
154
- lines.push("");
155
- lines.push(`${key}=${value}`);
142
+ if (process.platform === "win32") {
143
+ (0, child_process_1.execSync)(`start "" "${url}"`, { stdio: "ignore", shell: "cmd.exe" });
144
+ return;
156
145
  }
146
+ (0, child_process_1.execSync)(`xdg-open "${url}"`, { stdio: "ignore" });
147
+ }
148
+ catch {
149
+ console.log(` Open this URL in your browser:\n ${url}\n`);
157
150
  }
158
- else {
159
- lines.push(`${key}=${value}`, "");
151
+ }
152
+ async function startDeviceAuth() {
153
+ const response = await fetch(`${AUTH_API_BASE_URL}/api/orangeslice/auth/start`, {
154
+ method: "POST",
155
+ headers: { "Content-Type": "application/json" }
156
+ });
157
+ const data = (await response.json());
158
+ if (!response.ok || !data.deviceCode || !data.verificationUrl) {
159
+ throw new Error(data.error || "Failed to start device authentication.");
160
160
  }
161
- fs.writeFileSync(envPath, lines.join("\n"), "utf8");
161
+ return {
162
+ deviceCode: data.deviceCode,
163
+ verificationUrl: data.verificationUrl,
164
+ pollIntervalMs: data.pollIntervalMs && data.pollIntervalMs > 0 ? data.pollIntervalMs : 1000,
165
+ expiresIn: data.expiresIn && data.expiresIn > 0 ? data.expiresIn : 600
166
+ };
162
167
  }
163
- async function setupApiKey(cwd) {
164
- const envPath = path.join(cwd, ".env");
165
- const existing = readEnvFile(envPath).get("ORANGESLICE_API_KEY");
166
- if (existing) {
167
- console.log(` ✓ API key already configured in .env (${existing.slice(0, 12)}...)\n`);
168
- return;
168
+ async function completeDeviceAuth(deviceCode, pollIntervalMs, expiresIn) {
169
+ const timeoutAt = Date.now() + expiresIn * 1000;
170
+ while (Date.now() < timeoutAt) {
171
+ await new Promise((resolve) => setTimeout(resolve, pollIntervalMs));
172
+ const response = await fetch(`${AUTH_API_BASE_URL}/api/orangeslice/auth/poll?deviceCode=${encodeURIComponent(deviceCode)}`, {
173
+ method: "GET",
174
+ headers: { "Content-Type": "application/json" }
175
+ });
176
+ const data = (await response.json());
177
+ if (data.status === "pending")
178
+ continue;
179
+ if (data.status === "approved" && data.apiKey)
180
+ return data.apiKey;
181
+ if (data.status === "expired")
182
+ throw new Error(data.error || "Device code expired.");
183
+ if (data.status === "consumed")
184
+ throw new Error(data.error || "Device code already consumed.");
185
+ if (!response.ok) {
186
+ throw new Error(data.error || "Failed while waiting for authentication.");
187
+ }
169
188
  }
170
- console.log(" API key required. Get one at: https://www.orangeslice.ai/dashboard/api-keys\n");
171
- const key = await prompt(" Paste your API key (osk_...): ");
172
- if (!key) {
173
- console.log("\n ⚠ Skipped. Set ORANGESLICE_API_KEY in .env or call configure({ apiKey }) before using the SDK.\n");
189
+ throw new Error("Timed out waiting for browser authentication.");
190
+ }
191
+ async function setupApiKey() {
192
+ const existing = process.env.ORANGESLICE_API_KEY || readConfigFile().apiKey;
193
+ if (existing && existing.trim()) {
194
+ console.log(` ✓ API key already configured (${existing.slice(0, 12)}...)\n`);
174
195
  return;
175
196
  }
176
- if (!key.startsWith("osk_")) {
177
- console.log("\n ⚠ Key doesn't start with osk_ — saving anyway. Double-check it's correct.\n");
178
- }
179
- writeEnvVar(envPath, "ORANGESLICE_API_KEY", key);
180
- console.log(`\n ✓ API key saved to .env\n`);
197
+ console.log(" Authenticating with Orange Slice...\n");
198
+ const start = await startDeviceAuth();
199
+ openBrowser(start.verificationUrl);
200
+ console.log(" Browser opened. Complete sign-in there, then return here.\n");
201
+ const apiKey = await completeDeviceAuth(start.deviceCode, start.pollIntervalMs, start.expiresIn);
202
+ saveConfigFile({ apiKey });
203
+ console.log(` ✓ API key saved to ${CONFIG_PATH}\n`);
181
204
  }
182
205
  async function main() {
183
206
  console.log("\norangeslice\n");
@@ -194,7 +217,7 @@ async function main() {
194
217
  installOrangeslice(cwd);
195
218
  console.log(" ✓ Package installed in current directory\n");
196
219
  // API key setup
197
- await setupApiKey(cwd);
220
+ await setupApiKey();
198
221
  console.log("\nReady - services-style API\n");
199
222
  console.log(" import { configure, services } from 'orangeslice';\n");
200
223
  console.log(" // Option A: env var (loaded automatically from .env by your framework)");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "orangeslice",
3
- "version": "2.0.2",
3
+ "version": "2.0.3",
4
4
  "description": "B2B LinkedIn database prospector - 1.15B profiles, 85M companies",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",