geo-new 0.1.0

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,101 @@
1
+ import { type CredentialStore } from './credentials.js';
2
+ import { type Api, type AuditSnapshot, type ClientKind, type Envelope, type Key, type KeyList } from './types.js';
3
+ export declare const VERSION: string;
4
+ export declare const DEFAULT_ORIGIN = "https://geo.new";
5
+ export type ClientOptions = {
6
+ /** A `gnk_…` key. Without one the client mints and keeps a keyless session. */
7
+ apiKey?: string;
8
+ /** The Pages origin in production; `http://127.0.0.1:8787` against a local `dev:demo`. */
9
+ baseUrl?: string;
10
+ /** Injected in tests. */
11
+ fetch?: typeof fetch;
12
+ /** Where the keyless session and the last audit id live. Memory by default for the library. */
13
+ credentials?: CredentialStore;
14
+ /** Reported in the User-Agent so the server can count adoption by client kind. */
15
+ kind?: ClientKind;
16
+ };
17
+ export type WaitMode = 'none' | 'measured' | 'ai';
18
+ export type WaitOptions = {
19
+ mode?: WaitMode;
20
+ timeoutSeconds?: number;
21
+ signal?: AbortSignal;
22
+ onSnapshot?: (snapshot: AuditSnapshot) => void;
23
+ };
24
+ export type WaitResult = {
25
+ snapshot: AuditSnapshot;
26
+ timedOut: boolean;
27
+ };
28
+ /**
29
+ * One HTTP client behind the CLI, the stdio MCP server and the library. It holds a session before any
30
+ * create and sends an `Idempotency-Key` on every create, so a retry after a timeout can never start a
31
+ * second audit as somebody new; it retries 5xx and network failures twice and never a 429; it treats a
32
+ * `session` in a response to a keyed request as a protocol error rather than persisting a downgrade;
33
+ * and it sends a key only to an https or loopback origin.
34
+ */
35
+ export declare class GeoNewClient implements Api {
36
+ readonly origin: URL;
37
+ private readonly fetchImpl;
38
+ private readonly credentials;
39
+ private readonly kind;
40
+ private readonly explicitKey?;
41
+ constructor(options?: ClientOptions);
42
+ /** The key in force: the option or `GEO_NEW_API_KEY`, else the stored one; re-read on every call. */
43
+ get apiKey(): string | undefined;
44
+ get isDefaultOrigin(): boolean;
45
+ private authorization;
46
+ private request;
47
+ private failureOf;
48
+ /** Holds a keyless session before a create, so a retried create can bind to its idempotency record. */
49
+ ensureSession(signal?: AbortSignal): Promise<void>;
50
+ audit(url: string, options?: {
51
+ fresh?: boolean;
52
+ signal?: AbortSignal;
53
+ }): Promise<Envelope>;
54
+ snapshot(id: string, options?: {
55
+ signal?: AbortSignal;
56
+ }): Promise<AuditSnapshot>;
57
+ markdown(id: string): Promise<{
58
+ text: string;
59
+ revision: number;
60
+ status: string;
61
+ }>;
62
+ fixPrompt(id: string): Promise<string>;
63
+ compare(id: string, baseline: string): Promise<unknown>;
64
+ usage(): Promise<{
65
+ principal: import("./openapi.js").components["schemas"]["Principal"] | null;
66
+ plan: "anonymous" | "free" | "builder";
67
+ allowance: import("./openapi.js").components["schemas"]["Allowance"];
68
+ global: {
69
+ five_minutes: {
70
+ limit: number;
71
+ used: number;
72
+ };
73
+ hour: {
74
+ limit: number;
75
+ used: number;
76
+ };
77
+ };
78
+ }>;
79
+ share(id: string): Promise<{
80
+ url: string;
81
+ expires_at: string;
82
+ }>;
83
+ revokeShare(id: string): Promise<{
84
+ revoked: boolean;
85
+ }>;
86
+ createKey(label?: string): Promise<Key>;
87
+ listKeys(): Promise<KeyList>;
88
+ revokeKey(prefix: string): Promise<{
89
+ revoked: boolean;
90
+ }>;
91
+ /** Polls until the report is measured (`writing` or terminal), terminal (`ai`), or the timeout. */
92
+ waitFor(id: string, options?: WaitOptions): Promise<WaitResult>;
93
+ /** The keyless session token, when the client holds one; the CLI prints it on request, never by default. */
94
+ get lastAuditId(): string | undefined;
95
+ remember(patch: Partial<{
96
+ key: string;
97
+ last_audit_id: string;
98
+ }>): void;
99
+ forget(): void;
100
+ }
101
+ export declare const createClient: (options?: ClientOptions) => GeoNewClient;
@@ -0,0 +1,41 @@
1
+ export type Credentials = {
2
+ key?: string;
3
+ session?: {
4
+ token: string;
5
+ expires_at: string;
6
+ };
7
+ last_audit_id?: string;
8
+ };
9
+ export interface CredentialStore {
10
+ load(): Credentials;
11
+ save(next: Credentials): void;
12
+ }
13
+ /** `$XDG_CONFIG_HOME/geo-new/credentials.json`, `~/.config` when unset, `%APPDATA%\geo-new\` on Windows. */
14
+ export declare function credentialsPath(env?: NodeJS.ProcessEnv, platform?: NodeJS.Platform): string;
15
+ /**
16
+ * A file the CLI and a running MCP server share. Reads are cached on the file's mtime, size and inode,
17
+ * so `geo-new key create` in another terminal reaches a running server on its next call without a
18
+ * restart; writes go to a temporary name and are renamed, so a reader never sees half a file; a file
19
+ * that fails to parse keeps the last good contents and warns once. A home directory that cannot be
20
+ * written means an in-memory session and one warning, never a failure.
21
+ */
22
+ export declare class FileCredentials implements CredentialStore {
23
+ readonly path: string;
24
+ private readonly warn;
25
+ private cache;
26
+ private warned;
27
+ private memory;
28
+ constructor(path?: string, warn?: (message: string) => void);
29
+ private stamp;
30
+ load(): Credentials;
31
+ save(next: Credentials): void;
32
+ clear(): void;
33
+ }
34
+ export declare class MemoryCredentials implements CredentialStore {
35
+ private value;
36
+ constructor(value?: Credentials);
37
+ load(): Credentials;
38
+ save(next: Credentials): void;
39
+ }
40
+ /** A throwaway path for tests and for read-only homes. */
41
+ export declare const scratchCredentialsPath: () => string;
@@ -0,0 +1,6 @@
1
+ export { createClient, GeoNewClient, VERSION, DEFAULT_ORIGIN } from './client.js';
2
+ export type { ClientOptions, WaitMode, WaitOptions, WaitResult } from './client.js';
3
+ export { FileCredentials, MemoryCredentials, credentialsPath } from './credentials.js';
4
+ export type { Credentials, CredentialStore } from './credentials.js';
5
+ export { ApiError, isMeasured, isTerminal } from './types.js';
6
+ export type { Api, ApiFailure, ApiErrorBody, AuditSnapshot, ClientKind, Envelope, Key, KeyList, Share, Usage, } from './types.js';
package/dist/index.js ADDED
@@ -0,0 +1,341 @@
1
+ import { createRequire as __createRequire } from 'node:module'; const require = __createRequire(import.meta.url);
2
+
3
+ // packages/geo-new/src/client.ts
4
+ import { randomBytes } from "node:crypto";
5
+
6
+ // packages/geo-new/src/credentials.ts
7
+ import { mkdirSync, readFileSync, renameSync, statSync, writeFileSync, chmodSync, unlinkSync } from "node:fs";
8
+ import { join } from "node:path";
9
+ import { homedir, tmpdir } from "node:os";
10
+ function credentialsPath(env = process.env, platform = process.platform) {
11
+ const base = platform === "win32" ? env.APPDATA ?? join(homedir(), "AppData", "Roaming") : env.XDG_CONFIG_HOME ?? join(homedir(), ".config");
12
+ return join(base, "geo-new", "credentials.json");
13
+ }
14
+ var FileCredentials = class {
15
+ constructor(path = credentialsPath(), warn = (m) => process.stderr.write(`${m}
16
+ `)) {
17
+ this.path = path;
18
+ this.warn = warn;
19
+ }
20
+ path;
21
+ warn;
22
+ cache = null;
23
+ warned = false;
24
+ memory = null;
25
+ stamp() {
26
+ try {
27
+ const s = statSync(this.path);
28
+ return `${s.mtimeMs}:${s.size}:${s.ino}`;
29
+ } catch {
30
+ return null;
31
+ }
32
+ }
33
+ load() {
34
+ if (this.memory) return this.memory;
35
+ const stamp = this.stamp();
36
+ if (stamp === null) return {};
37
+ if (this.cache && this.cache.stamp === stamp) return this.cache.value;
38
+ try {
39
+ const parsed = JSON.parse(readFileSync(this.path, "utf8"));
40
+ if (!parsed || typeof parsed !== "object") throw new Error("not an object");
41
+ this.cache = { stamp, value: parsed };
42
+ this.warned = false;
43
+ return parsed;
44
+ } catch {
45
+ if (!this.warned) {
46
+ this.warn(`geo-new: could not read ${this.path}; keeping the last good credentials.`);
47
+ this.warned = true;
48
+ }
49
+ return this.cache?.value ?? {};
50
+ }
51
+ }
52
+ save(next) {
53
+ if (this.memory) {
54
+ this.memory = next;
55
+ return;
56
+ }
57
+ try {
58
+ mkdirSync(join(this.path, ".."), { recursive: true, mode: 448 });
59
+ const temporary = `${this.path}.${process.pid}.${Date.now()}.tmp`;
60
+ writeFileSync(temporary, JSON.stringify(next, null, 2) + "\n", { mode: 384 });
61
+ try {
62
+ chmodSync(temporary, 384);
63
+ } catch {
64
+ }
65
+ renameSync(temporary, this.path);
66
+ this.cache = null;
67
+ } catch {
68
+ this.memory = next;
69
+ this.warn(`geo-new: ${this.path} is not writable; this session lives in memory only.`);
70
+ }
71
+ }
72
+ clear() {
73
+ try {
74
+ unlinkSync(this.path);
75
+ } catch {
76
+ }
77
+ this.cache = null;
78
+ this.memory = null;
79
+ }
80
+ };
81
+ var MemoryCredentials = class {
82
+ constructor(value = {}) {
83
+ this.value = value;
84
+ }
85
+ value;
86
+ load() {
87
+ return this.value;
88
+ }
89
+ save(next) {
90
+ this.value = next;
91
+ }
92
+ };
93
+
94
+ // packages/geo-new/src/types.ts
95
+ var isMeasured = (status) => status === "writing" || status === "complete" || status === "partial" || status === "failed";
96
+ var isTerminal = (status) => status === "complete" || status === "partial" || status === "failed";
97
+ var ApiError = class extends Error {
98
+ constructor(failure2) {
99
+ super(failure2.message);
100
+ this.failure = failure2;
101
+ this.name = "ApiError";
102
+ }
103
+ failure;
104
+ get code() {
105
+ return this.failure.code;
106
+ }
107
+ };
108
+
109
+ // packages/geo-new/src/client.ts
110
+ var VERSION = true ? "0.1.0" : "0.0.0-dev";
111
+ var DEFAULT_ORIGIN = "https://geo.new";
112
+ var isLoopback = (url) => url.hostname === "localhost" || url.hostname === "127.0.0.1" || url.hostname === "[::1]" || url.hostname === "::1";
113
+ var sleep = (ms, signal) => new Promise((resolve, reject) => {
114
+ const timer = setTimeout(resolve, ms);
115
+ signal?.addEventListener(
116
+ "abort",
117
+ () => {
118
+ clearTimeout(timer);
119
+ reject(signal.reason ?? new Error("aborted"));
120
+ },
121
+ { once: true }
122
+ );
123
+ });
124
+ var failure = (code, message, status = 0, retryable = false) => new ApiError({ code, message, status, retryable });
125
+ var GeoNewClient = class {
126
+ origin;
127
+ fetchImpl;
128
+ credentials;
129
+ kind;
130
+ explicitKey;
131
+ constructor(options = {}) {
132
+ this.origin = new URL(options.baseUrl ?? process.env.GEO_NEW_API_URL ?? DEFAULT_ORIGIN);
133
+ this.fetchImpl = options.fetch ?? globalThis.fetch;
134
+ this.credentials = options.credentials ?? new MemoryCredentials();
135
+ this.kind = options.kind ?? "client";
136
+ this.explicitKey = options.apiKey ?? process.env.GEO_NEW_API_KEY;
137
+ }
138
+ /** The key in force: the option or `GEO_NEW_API_KEY`, else the stored one; re-read on every call. */
139
+ get apiKey() {
140
+ return this.explicitKey ?? this.credentials.load().key;
141
+ }
142
+ get isDefaultOrigin() {
143
+ return this.origin.origin === DEFAULT_ORIGIN;
144
+ }
145
+ authorization() {
146
+ const key = this.apiKey;
147
+ if (key) {
148
+ if (this.origin.protocol !== "https:" && !isLoopback(this.origin))
149
+ throw failure(
150
+ "INSECURE_ORIGIN",
151
+ `A key is sent only to an https or loopback origin, not ${this.origin.origin}.`
152
+ );
153
+ return `Bearer ${key}`;
154
+ }
155
+ const session = this.credentials.load().session;
156
+ if (session && Date.parse(session.expires_at) > Date.now()) return `Bearer ${session.token}`;
157
+ return void 0;
158
+ }
159
+ async request(method, path, options = {}) {
160
+ const headers = {
161
+ "User-Agent": `geo-new/${VERSION} (${this.kind})`,
162
+ Accept: options.accept ?? "application/json",
163
+ ...options.headers ?? {}
164
+ };
165
+ const authorization = this.authorization();
166
+ if (authorization) headers.Authorization = authorization;
167
+ if (options.body !== void 0) headers["Content-Type"] = "application/json";
168
+ const retries = options.retries ?? 2;
169
+ let attempt = 0;
170
+ for (; ; ) {
171
+ let response;
172
+ try {
173
+ response = await this.fetchImpl(new URL(path, this.origin), {
174
+ method,
175
+ headers,
176
+ body: options.body === void 0 ? void 0 : JSON.stringify(options.body),
177
+ signal: options.signal
178
+ });
179
+ } catch (error) {
180
+ if (options.signal?.aborted) throw error;
181
+ if (attempt < retries) {
182
+ attempt++;
183
+ await sleep(200 * 4 ** (attempt - 1), options.signal);
184
+ continue;
185
+ }
186
+ throw failure("NETWORK", describeNetworkError(error, this.origin), 0, true);
187
+ }
188
+ if (response.status >= 500 && attempt < retries) {
189
+ attempt++;
190
+ await sleep(200 * 4 ** (attempt - 1), options.signal);
191
+ continue;
192
+ }
193
+ if (!response.ok) throw await this.failureOf(response);
194
+ const data = options.text ? await response.text() : await response.json();
195
+ if (authorization?.startsWith("Bearer gnk_") && data && typeof data === "object" && "session" in data)
196
+ throw failure(
197
+ "PROTOCOL_ERROR",
198
+ "The server answered a keyed request with a session token: this server build does not accept keys.",
199
+ response.status
200
+ );
201
+ return { data, response };
202
+ }
203
+ }
204
+ async failureOf(response) {
205
+ const type = response.headers.get("Content-Type") ?? "";
206
+ if (/application\/json/i.test(type)) {
207
+ try {
208
+ const body = await response.json();
209
+ if (body?.error?.code) return new ApiError({ ...body.error, status: response.status });
210
+ } catch {
211
+ }
212
+ }
213
+ const kind = /text\/html/i.test(type) ? "an HTML error page" : `an empty or non-JSON ${response.status} response`;
214
+ return failure(
215
+ "UNEXPECTED_RESPONSE",
216
+ `The server returned ${kind}.`,
217
+ response.status,
218
+ response.status >= 500
219
+ );
220
+ }
221
+ /** Holds a keyless session before a create, so a retried create can bind to its idempotency record. */
222
+ async ensureSession(signal) {
223
+ if (this.apiKey) return;
224
+ const stored = this.credentials.load();
225
+ if (stored.session && Date.parse(stored.session.expires_at) > Date.now() + 6e4) return;
226
+ const { data } = await this.request(
227
+ "POST",
228
+ "/api/v1/session",
229
+ {
230
+ signal
231
+ }
232
+ );
233
+ if (!data.session) throw failure("NO_SESSION", "The server did not return a session token.");
234
+ this.credentials.save({
235
+ ...stored,
236
+ session: {
237
+ token: data.session,
238
+ expires_at: new Date(Date.now() + data.expires_in_seconds * 1e3).toISOString()
239
+ }
240
+ });
241
+ }
242
+ async audit(url, options = {}) {
243
+ await this.ensureSession(options.signal);
244
+ const { data } = await this.request("POST", "/api/v1/audits", {
245
+ body: { url, ...options.fresh ? { fresh: true } : {} },
246
+ headers: { "Idempotency-Key": randomBytes(16).toString("hex") },
247
+ signal: options.signal,
248
+ retries: 2
249
+ });
250
+ const stored = this.credentials.load();
251
+ this.credentials.save({ ...stored, last_audit_id: data.audit_id });
252
+ return data;
253
+ }
254
+ async snapshot(id, options = {}) {
255
+ return (await this.request("GET", `/api/v1/audits/${id}`, { signal: options.signal })).data;
256
+ }
257
+ async markdown(id) {
258
+ const { data, response } = await this.request("GET", `/api/v1/audits/${id}/report.md`, {
259
+ accept: "text/markdown",
260
+ text: true
261
+ });
262
+ return {
263
+ text: data,
264
+ revision: Number(response.headers.get("X-Audit-Revision") ?? 0),
265
+ status: response.headers.get("X-Audit-Status") ?? "unknown"
266
+ };
267
+ }
268
+ async fixPrompt(id) {
269
+ return (await this.request("GET", `/api/v1/audits/${id}/fix-prompt`, {
270
+ accept: "text/plain",
271
+ text: true
272
+ })).data;
273
+ }
274
+ async compare(id, baseline) {
275
+ return (await this.request("GET", `/api/v1/audits/${id}/compare/${baseline}`)).data;
276
+ }
277
+ async usage() {
278
+ return (await this.request("GET", "/api/v1/usage")).data;
279
+ }
280
+ async share(id) {
281
+ return (await this.request("POST", `/api/v1/audits/${id}/share`)).data;
282
+ }
283
+ async revokeShare(id) {
284
+ return (await this.request("DELETE", `/api/v1/audits/${id}/shares`)).data;
285
+ }
286
+ async createKey(label) {
287
+ await this.ensureSession();
288
+ return (await this.request("POST", "/api/v1/keys", { body: label ? { label } : {} })).data;
289
+ }
290
+ async listKeys() {
291
+ return (await this.request("GET", "/api/v1/keys")).data;
292
+ }
293
+ async revokeKey(prefix) {
294
+ return (await this.request("DELETE", `/api/v1/keys/${prefix.replace(/^gnk_/, "")}`)).data;
295
+ }
296
+ /** Polls until the report is measured (`writing` or terminal), terminal (`ai`), or the timeout. */
297
+ async waitFor(id, options = {}) {
298
+ const mode = options.mode ?? "measured", deadline = Date.now() + (options.timeoutSeconds ?? 90) * 1e3;
299
+ let snapshot = await this.snapshot(id, { signal: options.signal });
300
+ for (; ; ) {
301
+ options.onSnapshot?.(snapshot);
302
+ const done = mode === "none" || (mode === "measured" ? isMeasured(snapshot.status) : isTerminal(snapshot.status));
303
+ if (done) return { snapshot, timedOut: false };
304
+ if (Date.now() + 2e3 > deadline) return { snapshot, timedOut: true };
305
+ await sleep(2e3, options.signal);
306
+ snapshot = await this.snapshot(id, { signal: options.signal });
307
+ }
308
+ }
309
+ /** The keyless session token, when the client holds one; the CLI prints it on request, never by default. */
310
+ get lastAuditId() {
311
+ return this.credentials.load().last_audit_id;
312
+ }
313
+ remember(patch) {
314
+ const stored = this.credentials.load();
315
+ this.credentials.save({ ...stored, ...patch });
316
+ }
317
+ forget() {
318
+ this.credentials.save({});
319
+ }
320
+ };
321
+ function describeNetworkError(error, origin) {
322
+ const code = error?.cause?.code ?? error?.code ?? "";
323
+ const host = origin.host;
324
+ if (/ENOTFOUND|EAI_AGAIN/.test(code)) return `DNS could not resolve ${host}.`;
325
+ if (/ECONNREFUSED/.test(code)) return `${host} refused the connection.`;
326
+ if (/CERT|TLS|SSL/i.test(code) || /certificate/i.test(String(error))) return `TLS to ${host} failed.`;
327
+ return `Could not reach ${host}.`;
328
+ }
329
+ var createClient = (options = {}) => new GeoNewClient(options);
330
+ export {
331
+ ApiError,
332
+ DEFAULT_ORIGIN,
333
+ FileCredentials,
334
+ GeoNewClient,
335
+ MemoryCredentials,
336
+ VERSION,
337
+ createClient,
338
+ credentialsPath,
339
+ isMeasured,
340
+ isTerminal
341
+ };
@@ -0,0 +1,53 @@
1
+ import type { components } from './openapi.js';
2
+ export type AuditSnapshot = components['schemas']['AuditSnapshot'];
3
+ export type Envelope = components['schemas']['Admission'];
4
+ export type Usage = components['schemas']['Usage'];
5
+ export type Key = components['schemas']['Key'];
6
+ export type KeyList = components['schemas']['KeyList'];
7
+ export type Share = components['schemas']['Share'];
8
+ export type ApiErrorBody = components['schemas']['ApiError'];
9
+ export type ClientKind = 'cli' | 'mcp' | 'client';
10
+ /** A non-terminal status whose measured rows are final. Scores are frozen once the model starts writing. */
11
+ export declare const isMeasured: (status: string) => status is "writing" | "complete" | "partial" | "failed";
12
+ export declare const isTerminal: (status: string) => status is "complete" | "partial" | "failed";
13
+ /** What the server said when it refused. `status` 0 means the request never reached it. */
14
+ export type ApiFailure = {
15
+ code: string;
16
+ message: string;
17
+ status: number;
18
+ retryable: boolean;
19
+ request_id?: string;
20
+ retry_after_seconds?: number;
21
+ field?: string;
22
+ help_path?: string;
23
+ };
24
+ export declare class ApiError extends Error {
25
+ failure: ApiFailure;
26
+ constructor(failure: ApiFailure);
27
+ get code(): string;
28
+ }
29
+ /**
30
+ * The seam between the tool surface and the transport that reaches the API. The stdio server uses the
31
+ * HTTP client; a hosted server can call the app in process without changing a tool.
32
+ */
33
+ export interface Api {
34
+ audit(url: string, options?: {
35
+ fresh?: boolean;
36
+ signal?: AbortSignal;
37
+ }): Promise<Envelope>;
38
+ snapshot(id: string, options?: {
39
+ signal?: AbortSignal;
40
+ }): Promise<AuditSnapshot>;
41
+ markdown(id: string): Promise<{
42
+ text: string;
43
+ revision: number;
44
+ status: string;
45
+ }>;
46
+ fixPrompt(id: string): Promise<string>;
47
+ compare(id: string, baseline: string): Promise<unknown>;
48
+ usage(): Promise<Usage>;
49
+ share(id: string): Promise<Share>;
50
+ revokeShare(id: string): Promise<{
51
+ revoked: boolean;
52
+ }>;
53
+ }
package/package.json ADDED
@@ -0,0 +1,41 @@
1
+ {
2
+ "name": "geo-new",
3
+ "version": "0.1.0",
4
+ "description": "Audit one page for GEO, SEO and agent readiness from a terminal, a coding agent (MCP) or a script. Keyless by default; a key is optional.",
5
+ "license": "MIT",
6
+ "type": "module",
7
+ "bin": {
8
+ "geo-new": "dist/cli.js"
9
+ },
10
+ "exports": {
11
+ ".": {
12
+ "types": "./dist/index.d.ts",
13
+ "import": "./dist/index.js"
14
+ }
15
+ },
16
+ "types": "./dist/index.d.ts",
17
+ "files": [
18
+ "dist",
19
+ "README.md",
20
+ "LICENSE"
21
+ ],
22
+ "engines": {
23
+ "node": ">=20"
24
+ },
25
+ "keywords": [
26
+ "geo",
27
+ "seo",
28
+ "audit",
29
+ "mcp",
30
+ "cli",
31
+ "ai-search",
32
+ "llms.txt"
33
+ ],
34
+ "homepage": "https://geo.new/agents",
35
+ "repository": {
36
+ "type": "git",
37
+ "url": "git+https://github.com/aeelbeyoglu/geo-new.git",
38
+ "directory": "packages/geo-new"
39
+ },
40
+ "dependencies": {}
41
+ }