@remnic/import-gemini 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.
package/README.md ADDED
@@ -0,0 +1,32 @@
1
+ # @remnic/import-gemini
2
+
3
+ Optional importer for Google Takeout "Gemini Apps Activity" exports. Ships
4
+ as a separately installable companion to the Remnic CLI.
5
+
6
+ ```bash
7
+ npm install -g @remnic/import-gemini
8
+ remnic import --adapter gemini --file ~/takeout/My\ Activity.json
9
+ ```
10
+
11
+ ## What it imports
12
+
13
+ - **One memory per prompt** — every Gemini Apps activity record becomes one
14
+ memory containing the user prompt text. Assistant responses are NOT
15
+ imported because Google Takeout does not export them.
16
+ - Legacy "Bard" records are included (pre-rebrand exports).
17
+ - Short prompts (under 10 characters by default) are dropped because they
18
+ rarely carry durable intent.
19
+
20
+ ## Input shapes
21
+
22
+ - `My Activity.json` — Google Takeout's Gemini activity export
23
+ - A combined bundle object `{ "activities": [...] }`
24
+
25
+ Synthetic fixtures under `fixtures/` mirror the real shapes without any
26
+ personal data.
27
+
28
+ ## À-la-carte contract
29
+
30
+ This package is declared as an **optional peer dependency** of
31
+ `@remnic/cli`. Installing the CLI without this package produces a
32
+ friendly install hint — never `MODULE_NOT_FOUND`.
@@ -0,0 +1,79 @@
1
+ import { ImporterAdapter, ImportedMemory } from '@remnic/core';
2
+
3
+ /**
4
+ * A single Gemini Apps activity record. Other `header` values (Search, Maps,
5
+ * YouTube) co-exist in the same file when the user exports multiple products;
6
+ * we filter to Gemini Apps only.
7
+ */
8
+ interface GeminiActivityRecord {
9
+ header?: string;
10
+ title?: string;
11
+ titleUrl?: string;
12
+ /** Newer exports put the prompt text here. */
13
+ text?: string;
14
+ /** ISO 8601 timestamp of the prompt. */
15
+ time?: string;
16
+ products?: string[];
17
+ subtitles?: Array<{
18
+ name?: string;
19
+ url?: string;
20
+ }>;
21
+ /** Some exports embed the model response as a follow-up subtitle. */
22
+ details?: Array<{
23
+ name?: string;
24
+ }>;
25
+ }
26
+ interface ParsedGeminiExport {
27
+ /** Prompts filtered to `header === "Gemini Apps"`. */
28
+ activities: GeminiActivityRecord[];
29
+ filePath?: string;
30
+ }
31
+ interface GeminiParseOptions {
32
+ strict?: boolean;
33
+ filePath?: string;
34
+ /**
35
+ * When true, keep records even if their header is not "Gemini Apps".
36
+ * Default false — we only import Gemini activity.
37
+ */
38
+ keepNonGemini?: boolean;
39
+ }
40
+ /**
41
+ * Parse a Takeout activity payload. Accepts a JSON string, parsed object, or
42
+ * parsed array. Non-Gemini records are filtered out by default.
43
+ */
44
+ declare function parseGeminiExport(input: unknown, options?: GeminiParseOptions): ParsedGeminiExport;
45
+ /**
46
+ * Extract the user prompt text from an activity record. Newer exports put it
47
+ * in `text`; older ones nest it inside `title` as "Asked: <prompt>". Returns
48
+ * `undefined` when no usable prompt is present.
49
+ */
50
+ declare function extractUserPrompt(record: GeminiActivityRecord): string | undefined;
51
+
52
+ /**
53
+ * Canonical `ImporterAdapter` exposed by `@remnic/import-gemini`.
54
+ *
55
+ * Loaded by `remnic-cli/optional-importer.ts` via a computed-specifier dynamic
56
+ * import. The CLI drives `adapter.parse` → `adapter.transform` →
57
+ * `adapter.writeTo` through the shared `runImporter` helper in `@remnic/core`.
58
+ */
59
+ declare const adapter: ImporterAdapter<ParsedGeminiExport>;
60
+ /** Alias kept for symmetry with other @remnic/import-* packages. */
61
+ declare const geminiAdapter: ImporterAdapter<ParsedGeminiExport>;
62
+
63
+ declare const GEMINI_SOURCE_LABEL = "gemini";
64
+ interface GeminiTransformOptions {
65
+ /** Optional cap on total memories emitted — primarily for tests. */
66
+ maxMemories?: number;
67
+ /**
68
+ * Minimum prompt length (in characters) to import. Very short prompts
69
+ * ("yes", "ok", "tell me more") provide little durable signal. Default 10.
70
+ */
71
+ minPromptLength?: number;
72
+ }
73
+ /**
74
+ * Transform a parsed Gemini export into `ImportedMemory[]`. One memory per
75
+ * Gemini activity record containing a non-trivial user prompt.
76
+ */
77
+ declare function transformGeminiExport(parsed: ParsedGeminiExport, options?: GeminiTransformOptions): ImportedMemory[];
78
+
79
+ export { GEMINI_SOURCE_LABEL, type GeminiActivityRecord, type GeminiParseOptions, type GeminiTransformOptions, type ParsedGeminiExport, adapter, extractUserPrompt, geminiAdapter, parseGeminiExport, transformGeminiExport };
package/dist/index.js ADDED
@@ -0,0 +1,158 @@
1
+ // openclaw-engram: Local-first memory plugin
2
+
3
+ // src/adapter.ts
4
+ import { defaultWriteMemoriesToOrchestrator } from "@remnic/core";
5
+
6
+ // src/parser.ts
7
+ function parseGeminiExport(input, options = {}) {
8
+ if (input === void 0 || input === null) {
9
+ throw new Error(
10
+ "The 'gemini' importer requires a file. Pass `--file <path>` pointing at your Google Takeout `My Activity.json` (Gemini Apps section)."
11
+ );
12
+ }
13
+ const raw = coerceJson(input);
14
+ const result = {
15
+ activities: [],
16
+ ...options.filePath !== void 0 ? { filePath: options.filePath } : {}
17
+ };
18
+ if (Array.isArray(raw)) {
19
+ appendActivities(result.activities, raw, options);
20
+ return result;
21
+ }
22
+ if (raw && typeof raw === "object") {
23
+ const obj = raw;
24
+ let sawKnownKey = false;
25
+ for (const key of ["activities", "MyActivity", "activity"]) {
26
+ const v = obj[key];
27
+ if (Array.isArray(v)) {
28
+ sawKnownKey = true;
29
+ appendActivities(result.activities, v, options);
30
+ }
31
+ }
32
+ if (!sawKnownKey) {
33
+ throw new Error(
34
+ "Gemini export object has no recognized activity key. Expected one of 'activities', 'MyActivity', or 'activity'. Point --file at your Google Takeout `My Activity.json` (Gemini Apps section)."
35
+ );
36
+ }
37
+ return result;
38
+ }
39
+ throw new Error(
40
+ `Gemini export must be a JSON array or object; received ${describeType(raw)}`
41
+ );
42
+ }
43
+ function describeType(value) {
44
+ if (value === null) return "null";
45
+ return typeof value;
46
+ }
47
+ function appendActivities(dest, src, options) {
48
+ for (const entry of src) {
49
+ if (!entry || typeof entry !== "object") {
50
+ if (options.strict) {
51
+ throw new Error("Gemini activity entry must be an object");
52
+ }
53
+ continue;
54
+ }
55
+ const record = entry;
56
+ if (!options.keepNonGemini && !isGeminiRecord(record)) continue;
57
+ dest.push(record);
58
+ }
59
+ }
60
+ function isGeminiRecord(record) {
61
+ if (record.header === "Gemini Apps") return true;
62
+ if (record.header === "Bard") return true;
63
+ if (Array.isArray(record.products) && record.products.some((p) => p === "Gemini Apps" || p === "Bard")) {
64
+ return true;
65
+ }
66
+ return false;
67
+ }
68
+ function coerceJson(input) {
69
+ if (typeof input === "string") {
70
+ try {
71
+ return JSON.parse(input);
72
+ } catch (err) {
73
+ throw new Error(
74
+ `Gemini export is not valid JSON: ${err instanceof Error ? err.message : String(err)}`
75
+ );
76
+ }
77
+ }
78
+ return input;
79
+ }
80
+ function extractUserPrompt(record) {
81
+ if (typeof record.text === "string") {
82
+ const t = record.text.trim();
83
+ if (t.length > 0) return t;
84
+ }
85
+ if (typeof record.title === "string") {
86
+ const t = record.title.trim();
87
+ if (t.length === 0) return void 0;
88
+ const stripped = t.replace(/^(Asked|Prompted|Searched|Typed):\s*/i, "");
89
+ return stripped.length > 0 ? stripped : void 0;
90
+ }
91
+ return void 0;
92
+ }
93
+
94
+ // src/transform.ts
95
+ var GEMINI_SOURCE_LABEL = "gemini";
96
+ var DEFAULT_MIN_PROMPT_LENGTH = 10;
97
+ function transformGeminiExport(parsed, options = {}) {
98
+ const out = [];
99
+ const cap = options.maxMemories;
100
+ const minLen = options.minPromptLength ?? DEFAULT_MIN_PROMPT_LENGTH;
101
+ for (const record of parsed.activities) {
102
+ if (cap !== void 0 && out.length >= cap) return out;
103
+ const memory = activityToImported(record, parsed.filePath, minLen);
104
+ if (memory) out.push(memory);
105
+ }
106
+ return out;
107
+ }
108
+ function activityToImported(record, filePath, minLen) {
109
+ const prompt = extractUserPrompt(record);
110
+ if (!prompt || prompt.length < minLen) return void 0;
111
+ const metadata = { kind: "prompt" };
112
+ if (typeof record.titleUrl === "string" && record.titleUrl.length > 0) {
113
+ metadata.activityUrl = record.titleUrl;
114
+ }
115
+ if (Array.isArray(record.subtitles) && record.subtitles.length > 0) {
116
+ const modelSubtitle = record.subtitles.find(
117
+ (s) => typeof s.name === "string" && /model/i.test(s.name)
118
+ );
119
+ if (modelSubtitle?.name) metadata.modelTag = modelSubtitle.name;
120
+ }
121
+ return {
122
+ content: prompt,
123
+ sourceLabel: GEMINI_SOURCE_LABEL,
124
+ ...record.time !== void 0 ? { sourceTimestamp: record.time } : {},
125
+ ...filePath !== void 0 ? { importedFromPath: filePath } : {},
126
+ metadata
127
+ };
128
+ }
129
+
130
+ // src/adapter.ts
131
+ var adapter = {
132
+ name: "gemini",
133
+ sourceLabel: GEMINI_SOURCE_LABEL,
134
+ parse(input, options) {
135
+ return parseGeminiExport(input, {
136
+ ...options?.strict !== void 0 ? { strict: options.strict } : {},
137
+ ...options?.filePath !== void 0 ? { filePath: options.filePath } : {}
138
+ });
139
+ },
140
+ transform(parsed, options) {
141
+ return transformGeminiExport(parsed, {
142
+ ...options?.maxMemories !== void 0 ? { maxMemories: options.maxMemories } : {}
143
+ });
144
+ },
145
+ async writeTo(target, memories) {
146
+ return defaultWriteMemoriesToOrchestrator(target, memories);
147
+ }
148
+ };
149
+ var geminiAdapter = adapter;
150
+ export {
151
+ GEMINI_SOURCE_LABEL,
152
+ adapter,
153
+ extractUserPrompt,
154
+ geminiAdapter,
155
+ parseGeminiExport,
156
+ transformGeminiExport
157
+ };
158
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/adapter.ts","../src/parser.ts","../src/transform.ts"],"sourcesContent":["// ---------------------------------------------------------------------------\n// Gemini importer adapter (issue #568 slice 4)\n// ---------------------------------------------------------------------------\n\nimport type {\n ImportedMemory,\n ImporterAdapter,\n ImporterParseOptions,\n ImporterTransformOptions,\n ImporterWriteResult,\n ImporterWriteTarget,\n} from \"@remnic/core\";\nimport { defaultWriteMemoriesToOrchestrator } from \"@remnic/core\";\n\nimport { parseGeminiExport, type ParsedGeminiExport } from \"./parser.js\";\nimport { GEMINI_SOURCE_LABEL, transformGeminiExport } from \"./transform.js\";\n\n/**\n * Canonical `ImporterAdapter` exposed by `@remnic/import-gemini`.\n *\n * Loaded by `remnic-cli/optional-importer.ts` via a computed-specifier dynamic\n * import. The CLI drives `adapter.parse` → `adapter.transform` →\n * `adapter.writeTo` through the shared `runImporter` helper in `@remnic/core`.\n */\nexport const adapter: ImporterAdapter<ParsedGeminiExport> = {\n name: \"gemini\",\n sourceLabel: GEMINI_SOURCE_LABEL,\n\n parse(input: unknown, options?: ImporterParseOptions): ParsedGeminiExport {\n return parseGeminiExport(input, {\n ...(options?.strict !== undefined ? { strict: options.strict } : {}),\n ...(options?.filePath !== undefined ? { filePath: options.filePath } : {}),\n });\n },\n\n transform(\n parsed: ParsedGeminiExport,\n options?: ImporterTransformOptions,\n ): ImportedMemory[] {\n return transformGeminiExport(parsed, {\n ...(options?.maxMemories !== undefined\n ? { maxMemories: options.maxMemories }\n : {}),\n });\n },\n\n async writeTo(\n target: ImporterWriteTarget,\n memories: ImportedMemory[],\n ): Promise<ImporterWriteResult> {\n return defaultWriteMemoriesToOrchestrator(target, memories);\n },\n};\n\n/** Alias kept for symmetry with other @remnic/import-* packages. */\nexport const geminiAdapter = adapter;\n","// ---------------------------------------------------------------------------\n// Gemini (Google Takeout \"Gemini Apps Activity\") parser (issue #568 slice 4)\n// ---------------------------------------------------------------------------\n//\n// Google Takeout bundles Gemini Apps activity into `My Activity.json` (and a\n// legacy `MyActivity.json` spelling). Each record represents one prompt the\n// user sent. The schema is stable across exports we have observed:\n//\n// {\n// \"header\": \"Gemini Apps\",\n// \"title\": \"Asked: <user prompt>\", // older exports\n// \"text\": \"<user prompt>\", // newer exports\n// \"titleUrl\": \"https://gemini.google.com/...\",\n// \"time\": \"2026-02-14T09:30:00.000Z\",\n// \"products\": [\"Gemini Apps\"],\n// \"subtitles\": [{ \"name\": \"Model: Gemini X\" }]\n// }\n//\n// Prompts are pre-extracted by Google (no DOM scraping). The user prompt text\n// lives in `text`, or inside `title` as \"Asked: <prompt>\" for legacy records.\n// Assistant responses are NOT exported by Takeout (Google omits them), so\n// we only import the user's prompts.\n//\n// The parser accepts either the raw `My Activity.json` contents (JSON string\n// or already-parsed array) or a combined bundle object\n// `{ activities: [...] }` for future bundle-auto-detect (PR 7).\n\n// ---------------------------------------------------------------------------\n// Raw export shapes\n// ---------------------------------------------------------------------------\n\n/**\n * A single Gemini Apps activity record. Other `header` values (Search, Maps,\n * YouTube) co-exist in the same file when the user exports multiple products;\n * we filter to Gemini Apps only.\n */\nexport interface GeminiActivityRecord {\n header?: string;\n title?: string;\n titleUrl?: string;\n /** Newer exports put the prompt text here. */\n text?: string;\n /** ISO 8601 timestamp of the prompt. */\n time?: string;\n products?: string[];\n subtitles?: Array<{ name?: string; url?: string }>;\n /** Some exports embed the model response as a follow-up subtitle. */\n details?: Array<{ name?: string }>;\n}\n\nexport interface ParsedGeminiExport {\n /** Prompts filtered to `header === \"Gemini Apps\"`. */\n activities: GeminiActivityRecord[];\n filePath?: string;\n}\n\n// ---------------------------------------------------------------------------\n// Public parse API\n// ---------------------------------------------------------------------------\n\nexport interface GeminiParseOptions {\n strict?: boolean;\n filePath?: string;\n /**\n * When true, keep records even if their header is not \"Gemini Apps\".\n * Default false — we only import Gemini activity.\n */\n keepNonGemini?: boolean;\n}\n\n/**\n * Parse a Takeout activity payload. Accepts a JSON string, parsed object, or\n * parsed array. Non-Gemini records are filtered out by default.\n */\nexport function parseGeminiExport(\n input: unknown,\n options: GeminiParseOptions = {},\n): ParsedGeminiExport {\n // File-backed adapter contract: `runImportCommand` passes `undefined` when\n // `--file` is omitted. Gemini is a file-only importer (Takeout doesn't\n // expose an API), so a missing payload MUST surface as a user-facing\n // error rather than silently succeeding with 0 memories. Cursor review\n // on PR #600 flagged the silent-success path.\n if (input === undefined || input === null) {\n throw new Error(\n \"The 'gemini' importer requires a file. Pass `--file <path>` pointing at \" +\n \"your Google Takeout `My Activity.json` (Gemini Apps section).\",\n );\n }\n const raw = coerceJson(input);\n const result: ParsedGeminiExport = {\n activities: [],\n ...(options.filePath !== undefined ? { filePath: options.filePath } : {}),\n };\n\n if (Array.isArray(raw)) {\n appendActivities(result.activities, raw, options);\n return result;\n }\n\n if (raw && typeof raw === \"object\") {\n const obj = raw as Record<string, unknown>;\n // Common wrapper shapes: { activities: [...] } or { MyActivity: [...] }.\n let sawKnownKey = false;\n for (const key of [\"activities\", \"MyActivity\", \"activity\"] as const) {\n const v = obj[key];\n if (Array.isArray(v)) {\n sawKnownKey = true;\n appendActivities(result.activities, v, options);\n }\n }\n // Codex review on PR #600: pointing --file at a random JSON object\n // (e.g. a config file) used to report a successful 0-memory import.\n // We now throw a user-facing error when no known wrapper key was\n // present. This differs from strict mode because the \"array or\n // object\" shape check already passed — we just didn't find anything\n // that looked like a Gemini export inside the object.\n if (!sawKnownKey) {\n throw new Error(\n \"Gemini export object has no recognized activity key. Expected one of \" +\n \"'activities', 'MyActivity', or 'activity'. Point --file at your \" +\n \"Google Takeout `My Activity.json` (Gemini Apps section).\",\n );\n }\n return result;\n }\n\n // Codex review on PR #600 — primitive payloads (numbers, booleans,\n // strings, etc.) must always throw, regardless of strict mode. Silently\n // returning a 0-memory success on a JSON primitive (e.g. `true`,\n // `\"text\"`, `123`) hides operator input mistakes and makes automation\n // treat a broken import as healthy. Report the actual received value —\n // `typeof null === \"object\"` is the JS trap CLAUDE.md rule 18 calls\n // out. Using describeType() sidesteps the \"received object\" message\n // for null inputs.\n throw new Error(\n `Gemini export must be a JSON array or object; received ${describeType(raw)}`,\n );\n}\n\nfunction describeType(value: unknown): string {\n if (value === null) return \"null\";\n return typeof value;\n}\n\nfunction appendActivities(\n dest: GeminiActivityRecord[],\n src: unknown[],\n options: GeminiParseOptions,\n): void {\n for (const entry of src) {\n if (!entry || typeof entry !== \"object\") {\n if (options.strict) {\n throw new Error(\"Gemini activity entry must be an object\");\n }\n continue;\n }\n const record = entry as GeminiActivityRecord;\n if (!options.keepNonGemini && !isGeminiRecord(record)) continue;\n dest.push(record);\n }\n}\n\nfunction isGeminiRecord(record: GeminiActivityRecord): boolean {\n if (record.header === \"Gemini Apps\") return true;\n if (record.header === \"Bard\") return true; // pre-rebrand exports\n if (\n Array.isArray(record.products) &&\n record.products.some((p) => p === \"Gemini Apps\" || p === \"Bard\")\n ) {\n return true;\n }\n return false;\n}\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\nfunction coerceJson(input: unknown): unknown {\n if (typeof input === \"string\") {\n try {\n return JSON.parse(input);\n } catch (err) {\n throw new Error(\n `Gemini export is not valid JSON: ${\n err instanceof Error ? err.message : String(err)\n }`,\n );\n }\n }\n return input;\n}\n\n/**\n * Extract the user prompt text from an activity record. Newer exports put it\n * in `text`; older ones nest it inside `title` as \"Asked: <prompt>\". Returns\n * `undefined` when no usable prompt is present.\n */\nexport function extractUserPrompt(record: GeminiActivityRecord): string | undefined {\n if (typeof record.text === \"string\") {\n const t = record.text.trim();\n if (t.length > 0) return t;\n }\n if (typeof record.title === \"string\") {\n const t = record.title.trim();\n if (t.length === 0) return undefined;\n // Strip legacy prefixes that Takeout added in older exports.\n const stripped = t.replace(/^(Asked|Prompted|Searched|Typed):\\s*/i, \"\");\n return stripped.length > 0 ? stripped : undefined;\n }\n return undefined;\n}\n","// ---------------------------------------------------------------------------\n// Gemini parsed → ImportedMemory transform (issue #568 slice 4)\n// ---------------------------------------------------------------------------\n//\n// Google Takeout only exports the user's prompts — assistant responses are\n// omitted by Google. We therefore import every Gemini Apps activity record\n// as one memory containing the prompt text. Each prompt is first-person\n// intent (what the user asked), which the downstream extraction pipeline\n// can score / cluster just like any other memory source.\n//\n// Unlike the ChatGPT and Claude importers, there is no \"conversation\" layer\n// to opt into — Takeout doesn't preserve conversation boundaries for\n// Gemini Apps. The `includeConversations` flag is ignored.\n\nimport type { ImportedMemory } from \"@remnic/core\";\n\nimport type { GeminiActivityRecord, ParsedGeminiExport } from \"./parser.js\";\nimport { extractUserPrompt } from \"./parser.js\";\n\nexport const GEMINI_SOURCE_LABEL = \"gemini\";\n\nexport interface GeminiTransformOptions {\n /** Optional cap on total memories emitted — primarily for tests. */\n maxMemories?: number;\n /**\n * Minimum prompt length (in characters) to import. Very short prompts\n * (\"yes\", \"ok\", \"tell me more\") provide little durable signal. Default 10.\n */\n minPromptLength?: number;\n}\n\nconst DEFAULT_MIN_PROMPT_LENGTH = 10;\n\n/**\n * Transform a parsed Gemini export into `ImportedMemory[]`. One memory per\n * Gemini activity record containing a non-trivial user prompt.\n */\nexport function transformGeminiExport(\n parsed: ParsedGeminiExport,\n options: GeminiTransformOptions = {},\n): ImportedMemory[] {\n const out: ImportedMemory[] = [];\n const cap = options.maxMemories;\n const minLen = options.minPromptLength ?? DEFAULT_MIN_PROMPT_LENGTH;\n\n for (const record of parsed.activities) {\n if (cap !== undefined && out.length >= cap) return out;\n const memory = activityToImported(record, parsed.filePath, minLen);\n if (memory) out.push(memory);\n }\n return out;\n}\n\nfunction activityToImported(\n record: GeminiActivityRecord,\n filePath: string | undefined,\n minLen: number,\n): ImportedMemory | undefined {\n const prompt = extractUserPrompt(record);\n if (!prompt || prompt.length < minLen) return undefined;\n\n const metadata: Record<string, unknown> = { kind: \"prompt\" };\n if (typeof record.titleUrl === \"string\" && record.titleUrl.length > 0) {\n metadata.activityUrl = record.titleUrl;\n }\n if (Array.isArray(record.subtitles) && record.subtitles.length > 0) {\n const modelSubtitle = record.subtitles.find(\n (s) => typeof s.name === \"string\" && /model/i.test(s.name),\n );\n if (modelSubtitle?.name) metadata.modelTag = modelSubtitle.name;\n }\n\n return {\n content: prompt,\n sourceLabel: GEMINI_SOURCE_LABEL,\n ...(record.time !== undefined ? { sourceTimestamp: record.time } : {}),\n ...(filePath !== undefined ? { importedFromPath: filePath } : {}),\n metadata,\n };\n}\n"],"mappings":"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","names":[]}
package/package.json ADDED
@@ -0,0 +1,40 @@
1
+ {
2
+ "name": "@remnic/import-gemini",
3
+ "version": "0.1.0",
4
+ "description": "Import activity history from Google Takeout (Gemini Apps) exports into Remnic (issue #568)",
5
+ "type": "module",
6
+ "main": "dist/index.js",
7
+ "types": "dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/index.d.ts",
11
+ "import": "./dist/index.js"
12
+ }
13
+ },
14
+ "files": ["dist"],
15
+ "scripts": {
16
+ "build": "tsup src/index.ts --format esm --dts",
17
+ "check-types": "tsc --noEmit",
18
+ "test": "tsx --test src/adapter.test.ts src/parser.test.ts src/transform.test.ts",
19
+ "prepublishOnly": "npm run build"
20
+ },
21
+ "publishConfig": {
22
+ "access": "public",
23
+ "provenance": true
24
+ },
25
+ "dependencies": {
26
+ "@remnic/core": "workspace:^"
27
+ },
28
+ "devDependencies": {
29
+ "tsup": "^8.0.0",
30
+ "tsx": "^4.0.0",
31
+ "typescript": "^5.7.0"
32
+ },
33
+ "license": "MIT",
34
+ "repository": {
35
+ "type": "git",
36
+ "url": "https://github.com/joshuaswarren/remnic.git",
37
+ "directory": "packages/import-gemini"
38
+ },
39
+ "keywords": ["remnic", "memory", "gemini", "google", "takeout", "import"]
40
+ }