orangeslice 2.0.2 → 2.0.4

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,31 @@ 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`
20
+
21
+ ### CLI auth commands
22
+
23
+ ```bash
24
+ # One-time setup (also runs on plain `npx orangeslice` when unauthenticated)
25
+ npx orangeslice login
26
+
27
+ # Force re-auth and replace local stored key
28
+ npx orangeslice login --force
29
+
30
+ # Remove locally stored key
31
+ npx orangeslice logout
32
+
33
+ # Show current auth source (env or config file)
34
+ npx orangeslice auth status
35
+ ```
12
36
 
13
37
  Install as a dependency when writing app code:
14
38
 
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,75 +114,172 @@ 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]);
132
- }
133
- return entries;
134
- }
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
- }
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);
133
+ }
134
+ catch { }
135
+ }
136
+ function clearConfigFile() {
137
+ try {
138
+ if (!fs.existsSync(CONFIG_PATH))
139
+ return false;
140
+ fs.unlinkSync(CONFIG_PATH);
141
+ return true;
142
+ }
143
+ catch {
144
+ return false;
145
+ }
146
+ }
147
+ function maskKey(value) {
148
+ if (value.length <= 12)
149
+ return value;
150
+ return `${value.slice(0, 8)}...${value.slice(-4)}`;
151
+ }
152
+ function getStoredApiKey() {
153
+ const envKey = process.env.ORANGESLICE_API_KEY;
154
+ if (envKey && envKey.trim())
155
+ return envKey.trim();
156
+ const fileKey = readConfigFile().apiKey;
157
+ return fileKey?.trim() || "";
158
+ }
159
+ function openBrowser(url) {
160
+ try {
161
+ if (process.platform === "darwin") {
162
+ (0, child_process_1.execSync)(`open "${url}"`, { stdio: "ignore" });
163
+ return;
151
164
  }
152
- if (!replaced) {
153
- if (lines.length > 0 && lines[lines.length - 1] !== "")
154
- lines.push("");
155
- lines.push(`${key}=${value}`);
165
+ if (process.platform === "win32") {
166
+ (0, child_process_1.execSync)(`start "" "${url}"`, { stdio: "ignore", shell: "cmd.exe" });
167
+ return;
156
168
  }
169
+ (0, child_process_1.execSync)(`xdg-open "${url}"`, { stdio: "ignore" });
157
170
  }
158
- else {
159
- lines.push(`${key}=${value}`, "");
171
+ catch {
172
+ console.log(` Open this URL in your browser:\n ${url}\n`);
160
173
  }
161
- fs.writeFileSync(envPath, lines.join("\n"), "utf8");
162
174
  }
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`);
175
+ async function startDeviceAuth() {
176
+ const response = await fetch(`${AUTH_API_BASE_URL}/api/orangeslice/auth/start`, {
177
+ method: "POST",
178
+ headers: { "Content-Type": "application/json" }
179
+ });
180
+ const data = (await response.json());
181
+ if (!response.ok || !data.deviceCode || !data.verificationUrl) {
182
+ throw new Error(data.error || "Failed to start device authentication.");
183
+ }
184
+ return {
185
+ deviceCode: data.deviceCode,
186
+ verificationUrl: data.verificationUrl,
187
+ pollIntervalMs: data.pollIntervalMs && data.pollIntervalMs > 0 ? data.pollIntervalMs : 1000,
188
+ expiresIn: data.expiresIn && data.expiresIn > 0 ? data.expiresIn : 600
189
+ };
190
+ }
191
+ async function completeDeviceAuth(deviceCode, pollIntervalMs, expiresIn) {
192
+ const timeoutAt = Date.now() + expiresIn * 1000;
193
+ while (Date.now() < timeoutAt) {
194
+ await new Promise((resolve) => setTimeout(resolve, pollIntervalMs));
195
+ const response = await fetch(`${AUTH_API_BASE_URL}/api/orangeslice/auth/poll?deviceCode=${encodeURIComponent(deviceCode)}`, {
196
+ method: "GET",
197
+ headers: { "Content-Type": "application/json" }
198
+ });
199
+ const data = (await response.json());
200
+ if (data.status === "pending")
201
+ continue;
202
+ if (data.status === "approved" && data.apiKey)
203
+ return data.apiKey;
204
+ if (data.status === "expired")
205
+ throw new Error(data.error || "Device code expired.");
206
+ if (data.status === "consumed")
207
+ throw new Error(data.error || "Device code already consumed.");
208
+ if (!response.ok) {
209
+ throw new Error(data.error || "Failed while waiting for authentication.");
210
+ }
211
+ }
212
+ throw new Error("Timed out waiting for browser authentication.");
213
+ }
214
+ async function setupApiKey(force = false) {
215
+ const existing = getStoredApiKey();
216
+ if (!force && existing) {
217
+ console.log(` ✓ API key already configured (${maskKey(existing)})\n`);
168
218
  return;
169
219
  }
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");
220
+ console.log(" Authenticating with Orange Slice...\n");
221
+ const start = await startDeviceAuth();
222
+ openBrowser(start.verificationUrl);
223
+ console.log(" Browser opened. Complete sign-in there, then return here.\n");
224
+ const apiKey = await completeDeviceAuth(start.deviceCode, start.pollIntervalMs, start.expiresIn);
225
+ saveConfigFile({ apiKey });
226
+ console.log(` ✓ API key saved to ${CONFIG_PATH}\n`);
227
+ }
228
+ function printHelp() {
229
+ console.log("Usage:");
230
+ console.log(" npx orangeslice");
231
+ console.log(" npx orangeslice login [--force]");
232
+ console.log(" npx orangeslice logout");
233
+ console.log(" npx orangeslice auth status\n");
234
+ }
235
+ async function runLogin(args) {
236
+ const force = args.includes("--force");
237
+ await setupApiKey(force);
238
+ }
239
+ function runLogout() {
240
+ const removed = clearConfigFile();
241
+ if (removed) {
242
+ console.log(` ✓ Removed local auth at ${CONFIG_PATH}`);
243
+ }
244
+ else {
245
+ console.log(" No local config found to remove.");
246
+ }
247
+ if (process.env.ORANGESLICE_API_KEY) {
248
+ console.log(" Note: ORANGESLICE_API_KEY env var is still set in this shell.");
249
+ }
250
+ }
251
+ function runAuthStatus() {
252
+ const envKey = process.env.ORANGESLICE_API_KEY?.trim() || "";
253
+ if (envKey) {
254
+ console.log(` Authenticated via env var: ${maskKey(envKey)}`);
174
255
  return;
175
256
  }
176
- if (!key.startsWith("osk_")) {
177
- console.log("\n ⚠ Key doesn't start with osk_ — saving anyway. Double-check it's correct.\n");
257
+ const fileKey = readConfigFile().apiKey?.trim() || "";
258
+ if (fileKey) {
259
+ console.log(` Authenticated via config file (${CONFIG_PATH}): ${maskKey(fileKey)}`);
260
+ return;
178
261
  }
179
- writeEnvVar(envPath, "ORANGESLICE_API_KEY", key);
180
- console.log(`\n ✓ API key saved to .env\n`);
262
+ console.log(" Not authenticated. Run `npx orangeslice login`.");
181
263
  }
182
264
  async function main() {
265
+ const args = process.argv.slice(2);
266
+ const command = args[0];
267
+ if (command === "login") {
268
+ await runLogin(args.slice(1));
269
+ return;
270
+ }
271
+ if (command === "logout") {
272
+ runLogout();
273
+ return;
274
+ }
275
+ if (command === "auth" && args[1] === "status") {
276
+ runAuthStatus();
277
+ return;
278
+ }
279
+ if (command && (command === "--help" || command === "-h" || command === "help")) {
280
+ printHelp();
281
+ return;
282
+ }
183
283
  console.log("\norangeslice\n");
184
284
  const docsDir = resolveDocsDir();
185
285
  // Copy docs
@@ -194,7 +294,7 @@ async function main() {
194
294
  installOrangeslice(cwd);
195
295
  console.log(" ✓ Package installed in current directory\n");
196
296
  // API key setup
197
- await setupApiKey(cwd);
297
+ await setupApiKey();
198
298
  console.log("\nReady - services-style API\n");
199
299
  console.log(" import { configure, services } from 'orangeslice';\n");
200
300
  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.4",
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",