@opendatalabs/connect 0.8.0 → 0.8.1-canary.400bcfc
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 +67 -0
- package/dist/cli/bin.d.ts +3 -0
- package/dist/cli/bin.d.ts.map +1 -0
- package/dist/cli/bin.js +7 -0
- package/dist/cli/bin.js.map +1 -0
- package/dist/cli/index.d.ts +2 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +567 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/cli/main.d.ts +2 -0
- package/dist/cli/main.d.ts.map +1 -0
- package/dist/cli/main.js +2 -0
- package/dist/cli/main.js.map +1 -0
- package/dist/connectors/index.d.ts +2 -0
- package/dist/connectors/index.d.ts.map +1 -0
- package/dist/connectors/index.js +2 -0
- package/dist/connectors/index.js.map +1 -0
- package/dist/connectors/registry.d.ts +29 -0
- package/dist/connectors/registry.d.ts.map +1 -0
- package/dist/connectors/registry.js +134 -0
- package/dist/connectors/registry.js.map +1 -0
- package/dist/core/cli-types.d.ts +110 -0
- package/dist/core/cli-types.d.ts.map +1 -0
- package/dist/core/cli-types.js +61 -0
- package/dist/core/cli-types.js.map +1 -0
- package/dist/core/index.d.ts +3 -0
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/index.js +3 -0
- package/dist/core/index.js.map +1 -1
- package/dist/core/paths.d.ts +9 -0
- package/dist/core/paths.d.ts.map +1 -0
- package/dist/core/paths.js +28 -0
- package/dist/core/paths.js.map +1 -0
- package/dist/core/state-store.d.ts +16 -0
- package/dist/core/state-store.d.ts.map +1 -0
- package/dist/core/state-store.js +23 -0
- package/dist/core/state-store.js.map +1 -0
- package/dist/core/types.d.ts +4 -0
- package/dist/core/types.d.ts.map +1 -1
- package/dist/personal-server/index.d.ts +8 -0
- package/dist/personal-server/index.d.ts.map +1 -0
- package/dist/personal-server/index.js +76 -0
- package/dist/personal-server/index.js.map +1 -0
- package/dist/react/useVanaData.d.ts +1 -1
- package/dist/react/useVanaData.d.ts.map +1 -1
- package/dist/react/useVanaData.js +22 -9
- package/dist/react/useVanaData.js.map +1 -1
- package/dist/runtime/bundled-assets.d.ts +6 -0
- package/dist/runtime/bundled-assets.d.ts.map +1 -0
- package/dist/runtime/bundled-assets.js +44 -0
- package/dist/runtime/bundled-assets.js.map +1 -0
- package/dist/runtime/index.d.ts +3 -0
- package/dist/runtime/index.d.ts.map +1 -0
- package/dist/runtime/index.js +3 -0
- package/dist/runtime/index.js.map +1 -0
- package/dist/runtime/managed-playwright.d.ts +33 -0
- package/dist/runtime/managed-playwright.d.ts.map +1 -0
- package/dist/runtime/managed-playwright.js +237 -0
- package/dist/runtime/managed-playwright.js.map +1 -0
- package/dist/runtime/repo-paths.d.ts +2 -0
- package/dist/runtime/repo-paths.d.ts.map +1 -0
- package/dist/runtime/repo-paths.js +19 -0
- package/dist/runtime/repo-paths.js.map +1 -0
- package/dist/server/config.d.ts +2 -0
- package/dist/server/config.d.ts.map +1 -1
- package/dist/server/config.js +1 -0
- package/dist/server/config.js.map +1 -1
- package/dist/server/connect.d.ts.map +1 -1
- package/dist/server/connect.js +7 -0
- package/dist/server/connect.js.map +1 -1
- package/package.json +29 -4
- package/runtime-assets/playwright-runner/entitlements.plist +12 -0
- package/runtime-assets/playwright-runner/index.cjs +1243 -0
- package/runtime-assets/playwright-runner/package-lock.json +1242 -0
- package/runtime-assets/playwright-runner/package.json +29 -0
- package/runtime-assets/playwright-runner/scripts/build.js +182 -0
- package/runtime-assets/run-connector.cjs +355 -0
package/README.md
CHANGED
|
@@ -72,6 +72,73 @@ Then, in your dev version of DataConnect (likely built from the `main` branch) y
|
|
|
72
72
|
|
|
73
73
|
If you prefer to integrate the SDK into an existing project, follow the steps below.
|
|
74
74
|
|
|
75
|
+
## Headless CLI
|
|
76
|
+
|
|
77
|
+
`vana-connect` now also ships a local collection CLI for connector setup and data export flows.
|
|
78
|
+
|
|
79
|
+
### Install
|
|
80
|
+
|
|
81
|
+
Use the published package directly:
|
|
82
|
+
|
|
83
|
+
```bash
|
|
84
|
+
pnpm dlx @opendatalabs/connect status
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
Or install it globally:
|
|
88
|
+
|
|
89
|
+
```bash
|
|
90
|
+
pnpm add -g @opendatalabs/connect
|
|
91
|
+
vana status
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
### Commands
|
|
95
|
+
|
|
96
|
+
```bash
|
|
97
|
+
vana sources
|
|
98
|
+
vana connect github
|
|
99
|
+
vana status
|
|
100
|
+
vana setup
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
### Local development
|
|
104
|
+
|
|
105
|
+
From this repo:
|
|
106
|
+
|
|
107
|
+
```bash
|
|
108
|
+
pnpm install
|
|
109
|
+
pnpm build
|
|
110
|
+
node dist/cli/bin.js status
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
The CLI installs its local browser runtime under `~/.dataconnect/`.
|
|
114
|
+
That runtime is bundled from `vana-connect` itself, so `vana setup` does not require a separate `data-connect` checkout.
|
|
115
|
+
|
|
116
|
+
To build a standalone SEA executable locally:
|
|
117
|
+
|
|
118
|
+
```bash
|
|
119
|
+
pnpm build
|
|
120
|
+
pnpm build:sea
|
|
121
|
+
./artifacts/sea/vana-linux-x64 status --json
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
`pnpm build:sea` uses Node 25's `--build-sea` flow and embeds the runtime assets needed for `vana setup`.
|
|
125
|
+
It also produces `artifacts/sea/vana-linux-x64.tar.gz` and a matching `.sha256` checksum file for release/distribution.
|
|
126
|
+
|
|
127
|
+
### Programmatic runtime access
|
|
128
|
+
|
|
129
|
+
If you are building an app surface like DataConnect Desktop or a hosted orchestration layer, use the SDK modules instead of shelling out to the CLI where possible.
|
|
130
|
+
|
|
131
|
+
```ts
|
|
132
|
+
import { ManagedPlaywrightRuntime } from "@opendatalabs/connect/runtime";
|
|
133
|
+
import { listAvailableSources } from "@opendatalabs/connect/connectors";
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
Intended split:
|
|
137
|
+
|
|
138
|
+
- app surfaces consume SDK/runtime APIs
|
|
139
|
+
- agent skills consume the CLI
|
|
140
|
+
- `data-connectors` remains the connector and schema source of truth
|
|
141
|
+
|
|
75
142
|
### Installation
|
|
76
143
|
|
|
77
144
|
```bash
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bin.d.ts","sourceRoot":"","sources":["../../src/cli/bin.ts"],"names":[],"mappings":""}
|
package/dist/cli/bin.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bin.js","sourceRoot":"","sources":["../../src/cli/bin.ts"],"names":[],"mappings":";AAEA,OAAO,EAAE,MAAM,EAAE,MAAM,YAAY,CAAC;AAEpC,MAAM,QAAQ,GAAG,MAAM,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;AAC5C,IAAI,OAAO,QAAQ,KAAK,QAAQ,EAAE,CAAC;IACjC,OAAO,CAAC,QAAQ,GAAG,QAAQ,CAAC;AAC9B,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/cli/index.ts"],"names":[],"mappings":"AAkDA,wBAAsB,MAAM,CAAC,IAAI,WAAe,GAAG,OAAO,CAAC,MAAM,CAAC,CA2CjE"}
|
|
@@ -0,0 +1,567 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import fsp from "node:fs/promises";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { confirm, input, password } from "@inquirer/prompts";
|
|
5
|
+
import { Command } from "commander";
|
|
6
|
+
import { CliOutcomeStatus, getBrowserProfilesDir, getConnectorCacheDir, getLastResultPath, readCliState, updateSourceState, } from "../core/index.js";
|
|
7
|
+
import { listAvailableSources } from "../connectors/registry.js";
|
|
8
|
+
import { detectPersonalServerTarget, ingestResult, } from "../personal-server/index.js";
|
|
9
|
+
import { findDataConnectorsDir, ManagedPlaywrightRuntime, } from "../runtime/index.js";
|
|
10
|
+
export async function runCli(argv = process.argv) {
|
|
11
|
+
const normalizedArgv = normalizeArgv(argv);
|
|
12
|
+
const parsedOptions = extractGlobalOptions(normalizedArgv);
|
|
13
|
+
const program = new Command();
|
|
14
|
+
program.name("vana").description("Vana Connect CLI");
|
|
15
|
+
program
|
|
16
|
+
.command("connect <source>")
|
|
17
|
+
.option("--json", "Output machine-readable JSON")
|
|
18
|
+
.option("--no-input", "Fail instead of prompting for input")
|
|
19
|
+
.option("--yes", "Approve safe setup prompts automatically")
|
|
20
|
+
.option("--quiet", "Reduce non-essential output")
|
|
21
|
+
.action(async (source) => {
|
|
22
|
+
process.exitCode = await runConnect(source, parsedOptions);
|
|
23
|
+
});
|
|
24
|
+
program
|
|
25
|
+
.command("sources")
|
|
26
|
+
.description("List supported sources")
|
|
27
|
+
.option("--json", "Output machine-readable JSON")
|
|
28
|
+
.action(async () => {
|
|
29
|
+
process.exitCode = await runList(parsedOptions);
|
|
30
|
+
});
|
|
31
|
+
program
|
|
32
|
+
.command("status")
|
|
33
|
+
.description("Show runtime and Personal Server status")
|
|
34
|
+
.option("--json", "Output machine-readable JSON")
|
|
35
|
+
.action(async () => {
|
|
36
|
+
process.exitCode = await runStatus(parsedOptions);
|
|
37
|
+
});
|
|
38
|
+
program
|
|
39
|
+
.command("setup")
|
|
40
|
+
.description("Install or repair the local runtime")
|
|
41
|
+
.option("--json", "Output machine-readable JSON")
|
|
42
|
+
.option("--yes", "Approve safe setup prompts automatically")
|
|
43
|
+
.action(async () => {
|
|
44
|
+
process.exitCode = await runSetup(parsedOptions);
|
|
45
|
+
});
|
|
46
|
+
await program.parseAsync(normalizedArgv);
|
|
47
|
+
return Number(process.exitCode ?? 0);
|
|
48
|
+
}
|
|
49
|
+
async function runConnect(source, options) {
|
|
50
|
+
const runtime = new ManagedPlaywrightRuntime();
|
|
51
|
+
const emit = createEmitter(options);
|
|
52
|
+
const registrySources = await loadRegistrySources();
|
|
53
|
+
const sourceLabels = createSourceLabelMap(registrySources);
|
|
54
|
+
let setupLogPath;
|
|
55
|
+
let fetchLogPath;
|
|
56
|
+
let runLogPath;
|
|
57
|
+
try {
|
|
58
|
+
emit.info(`Finding a connector for ${displaySource(source, sourceLabels)}...`);
|
|
59
|
+
const target = await detectPersonalServerTarget();
|
|
60
|
+
if (runtime.state !== "installed") {
|
|
61
|
+
emit.info(`Vana Connect needs a local browser runtime before it can connect ${displaySource(source, sourceLabels)}.`);
|
|
62
|
+
emit.info("");
|
|
63
|
+
emit.info("This will install:");
|
|
64
|
+
emit.info("- the connector runner");
|
|
65
|
+
emit.info("- a Chromium browser engine");
|
|
66
|
+
emit.info("- local runtime files under ~/.dataconnect/");
|
|
67
|
+
emit.info("");
|
|
68
|
+
emit.info("Your credentials stay on this machine. Nothing is sent anywhere except the platform you’re connecting to.");
|
|
69
|
+
if (!options.yes) {
|
|
70
|
+
if (options.noInput) {
|
|
71
|
+
emit.event({
|
|
72
|
+
type: "outcome",
|
|
73
|
+
status: CliOutcomeStatus.SETUP_REQUIRED,
|
|
74
|
+
source,
|
|
75
|
+
});
|
|
76
|
+
return 1;
|
|
77
|
+
}
|
|
78
|
+
const shouldContinue = await confirm({
|
|
79
|
+
message: "Continue?",
|
|
80
|
+
default: true,
|
|
81
|
+
});
|
|
82
|
+
if (!shouldContinue) {
|
|
83
|
+
emit.event({
|
|
84
|
+
type: "outcome",
|
|
85
|
+
status: CliOutcomeStatus.SETUP_REQUIRED,
|
|
86
|
+
source,
|
|
87
|
+
reason: "setup_declined",
|
|
88
|
+
});
|
|
89
|
+
return 1;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
const installResult = await runtime.ensureInstalled(Boolean(options.yes));
|
|
93
|
+
setupLogPath = installResult.logPath;
|
|
94
|
+
emit.event({
|
|
95
|
+
type: "setup-complete",
|
|
96
|
+
runtime: installResult.runtime,
|
|
97
|
+
logPath: installResult.logPath,
|
|
98
|
+
});
|
|
99
|
+
emit.info("Runtime ready.");
|
|
100
|
+
if (installResult.logPath) {
|
|
101
|
+
emit.info(`Setup log: ${installResult.logPath}`);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
else {
|
|
105
|
+
emit.event({
|
|
106
|
+
type: "setup-check",
|
|
107
|
+
runtime: runtime.state,
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
const fetched = await runtime.fetchConnector(source);
|
|
111
|
+
fetchLogPath = fetched.logPath;
|
|
112
|
+
const sourceDetails = registrySources.find((item) => item.id === source);
|
|
113
|
+
const resolution = {
|
|
114
|
+
source,
|
|
115
|
+
connectorPath: fetched.connectorPath,
|
|
116
|
+
};
|
|
117
|
+
emit.event({
|
|
118
|
+
type: "connector-resolved",
|
|
119
|
+
source: resolution.source,
|
|
120
|
+
connectorPath: resolution.connectorPath,
|
|
121
|
+
logPath: fetched.logPath,
|
|
122
|
+
});
|
|
123
|
+
emit.info("Connector ready.");
|
|
124
|
+
if (sourceDetails?.description) {
|
|
125
|
+
emit.info(sourceDetails.description);
|
|
126
|
+
}
|
|
127
|
+
const profilePath = path.join(getBrowserProfilesDir(), `${path.basename(resolution.connectorPath, path.extname(resolution.connectorPath))}`);
|
|
128
|
+
if (fs.existsSync(profilePath)) {
|
|
129
|
+
emit.info(`Found an existing ${displaySource(source, sourceLabels)} session. Trying that first...`);
|
|
130
|
+
}
|
|
131
|
+
await updateSourceState(resolution.source, {
|
|
132
|
+
connectorInstalled: true,
|
|
133
|
+
sessionPresent: fs.existsSync(profilePath),
|
|
134
|
+
lastError: null,
|
|
135
|
+
});
|
|
136
|
+
emit.info(`Connecting to ${displaySource(source, sourceLabels)}...`);
|
|
137
|
+
emit.info("Collecting your data...");
|
|
138
|
+
let finalStatus = CliOutcomeStatus.UNEXPECTED_INTERNAL_ERROR;
|
|
139
|
+
let resultPath = getLastResultPath();
|
|
140
|
+
let collectedResult = false;
|
|
141
|
+
for await (const event of runtime.runConnector({
|
|
142
|
+
connectorPath: resolution.connectorPath,
|
|
143
|
+
source: resolution.source,
|
|
144
|
+
noInput: options.noInput,
|
|
145
|
+
onNeedInput: async (needInput) => {
|
|
146
|
+
emit.info("");
|
|
147
|
+
emit.info(`To connect ${displaySource(source, sourceLabels)}, Vana Connect will open a local browser session on this machine.`);
|
|
148
|
+
emit.info("Your credentials stay local.");
|
|
149
|
+
emit.info("");
|
|
150
|
+
emit.info(needInput.message ??
|
|
151
|
+
`${displaySource(source, sourceLabels)} needs additional details to continue.`);
|
|
152
|
+
const values = {};
|
|
153
|
+
for (const field of needInput.fields) {
|
|
154
|
+
if (field.toLowerCase().includes("password")) {
|
|
155
|
+
values[field] = await password({ message: humanizeField(field) });
|
|
156
|
+
}
|
|
157
|
+
else {
|
|
158
|
+
values[field] = await input({ message: humanizeField(field) });
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
return values;
|
|
162
|
+
},
|
|
163
|
+
})) {
|
|
164
|
+
emit.event(event);
|
|
165
|
+
if (event.logPath) {
|
|
166
|
+
runLogPath = event.logPath;
|
|
167
|
+
}
|
|
168
|
+
if (event.type === "needs-input") {
|
|
169
|
+
await updateSourceState(resolution.source, {
|
|
170
|
+
lastRunAt: new Date().toISOString(),
|
|
171
|
+
lastRunOutcome: CliOutcomeStatus.NEEDS_INPUT,
|
|
172
|
+
lastError: event.message ?? "Input required.",
|
|
173
|
+
});
|
|
174
|
+
emit.event({
|
|
175
|
+
type: "outcome",
|
|
176
|
+
status: CliOutcomeStatus.NEEDS_INPUT,
|
|
177
|
+
source: resolution.source,
|
|
178
|
+
});
|
|
179
|
+
if (!options.json) {
|
|
180
|
+
emit.info(`${displaySource(source, sourceLabels)} needs additional input before it can connect.`);
|
|
181
|
+
emit.info(`Next: run \`vana connect ${source}\` without \`--no-input\`.`);
|
|
182
|
+
}
|
|
183
|
+
return 1;
|
|
184
|
+
}
|
|
185
|
+
if (event.type === "runtime-error") {
|
|
186
|
+
await updateSourceState(resolution.source, {
|
|
187
|
+
lastRunAt: new Date().toISOString(),
|
|
188
|
+
lastRunOutcome: CliOutcomeStatus.RUNTIME_ERROR,
|
|
189
|
+
lastError: event.message ?? "Connector run failed.",
|
|
190
|
+
});
|
|
191
|
+
emit.info(event.message ?? "Connector run failed.");
|
|
192
|
+
emit.event({
|
|
193
|
+
type: "outcome",
|
|
194
|
+
status: CliOutcomeStatus.RUNTIME_ERROR,
|
|
195
|
+
source: resolution.source,
|
|
196
|
+
});
|
|
197
|
+
if (event.logPath) {
|
|
198
|
+
emit.info(`Run log: ${event.logPath}`);
|
|
199
|
+
}
|
|
200
|
+
return 1;
|
|
201
|
+
}
|
|
202
|
+
if (event.type === "legacy-auth") {
|
|
203
|
+
await updateSourceState(resolution.source, {
|
|
204
|
+
lastRunAt: new Date().toISOString(),
|
|
205
|
+
lastRunOutcome: CliOutcomeStatus.LEGACY_AUTH,
|
|
206
|
+
lastError: event.message ?? "Legacy authentication is required.",
|
|
207
|
+
dataState: "none",
|
|
208
|
+
});
|
|
209
|
+
emit.info(event.message ??
|
|
210
|
+
"This connector requires legacy headed authentication that is not available in batch mode.");
|
|
211
|
+
emit.info(`Next: establish a reusable ${displaySource(source, sourceLabels)} session manually, or migrate this connector to requestInput.`);
|
|
212
|
+
if (event.logPath) {
|
|
213
|
+
emit.info(`Run log: ${event.logPath}`);
|
|
214
|
+
}
|
|
215
|
+
emit.event({
|
|
216
|
+
type: "outcome",
|
|
217
|
+
status: CliOutcomeStatus.LEGACY_AUTH,
|
|
218
|
+
source: resolution.source,
|
|
219
|
+
});
|
|
220
|
+
return 1;
|
|
221
|
+
}
|
|
222
|
+
if (event.type === "collection-complete" && event.resultPath) {
|
|
223
|
+
collectedResult = true;
|
|
224
|
+
resultPath = event.resultPath;
|
|
225
|
+
const ingestEvents = await ingestResult(resolution.source, resultPath, target);
|
|
226
|
+
for (const ingestEvent of ingestEvents) {
|
|
227
|
+
emit.event(ingestEvent);
|
|
228
|
+
}
|
|
229
|
+
const ingestCompleted = ingestEvents.some((ingestEvent) => ingestEvent.type === "ingest-complete");
|
|
230
|
+
finalStatus = ingestCompleted
|
|
231
|
+
? CliOutcomeStatus.CONNECTED_AND_INGESTED
|
|
232
|
+
: CliOutcomeStatus.CONNECTED_LOCAL_ONLY;
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
if (!collectedResult) {
|
|
236
|
+
await updateSourceState(resolution.source, {
|
|
237
|
+
connectorInstalled: true,
|
|
238
|
+
sessionPresent: fs.existsSync(profilePath),
|
|
239
|
+
lastRunAt: new Date().toISOString(),
|
|
240
|
+
lastRunOutcome: CliOutcomeStatus.UNEXPECTED_INTERNAL_ERROR,
|
|
241
|
+
dataState: "none",
|
|
242
|
+
lastError: "Connector run ended without a result.",
|
|
243
|
+
});
|
|
244
|
+
emit.event({
|
|
245
|
+
type: "outcome",
|
|
246
|
+
status: CliOutcomeStatus.UNEXPECTED_INTERNAL_ERROR,
|
|
247
|
+
source: resolution.source,
|
|
248
|
+
reason: "Connector run ended without a result.",
|
|
249
|
+
});
|
|
250
|
+
if (runLogPath) {
|
|
251
|
+
emit.info(`Run log: ${runLogPath}`);
|
|
252
|
+
}
|
|
253
|
+
return 1;
|
|
254
|
+
}
|
|
255
|
+
const dataState = finalStatus === CliOutcomeStatus.CONNECTED_AND_INGESTED
|
|
256
|
+
? "ingested_personal_server"
|
|
257
|
+
: "collected_local";
|
|
258
|
+
await updateSourceState(resolution.source, {
|
|
259
|
+
connectorInstalled: true,
|
|
260
|
+
sessionPresent: true,
|
|
261
|
+
lastRunAt: new Date().toISOString(),
|
|
262
|
+
lastRunOutcome: finalStatus,
|
|
263
|
+
dataState,
|
|
264
|
+
lastError: null,
|
|
265
|
+
});
|
|
266
|
+
if (finalStatus === CliOutcomeStatus.CONNECTED_AND_INGESTED) {
|
|
267
|
+
emit.info(`Connected ${displaySource(source, sourceLabels)}.`);
|
|
268
|
+
emit.info(`Collected your ${displaySource(source, sourceLabels)} data and synced it to your Personal Server.`);
|
|
269
|
+
}
|
|
270
|
+
else {
|
|
271
|
+
if (target.state !== "available") {
|
|
272
|
+
emit.info(`No Personal Server is available right now, so your ${displaySource(source, sourceLabels)} data was saved locally.`);
|
|
273
|
+
}
|
|
274
|
+
emit.info(`Connected ${displaySource(source, sourceLabels)}.`);
|
|
275
|
+
emit.info(`Collected your ${displaySource(source, sourceLabels)} data and saved it locally.`);
|
|
276
|
+
emit.info(`Local result: ${resultPath}`);
|
|
277
|
+
}
|
|
278
|
+
if (runLogPath) {
|
|
279
|
+
emit.info(`Run log: ${runLogPath}`);
|
|
280
|
+
}
|
|
281
|
+
else if (fetchLogPath) {
|
|
282
|
+
emit.info(`Fetch log: ${fetchLogPath}`);
|
|
283
|
+
}
|
|
284
|
+
else if (setupLogPath) {
|
|
285
|
+
emit.info(`Setup log: ${setupLogPath}`);
|
|
286
|
+
}
|
|
287
|
+
emit.info("Next: run `vana status` to inspect your current connection state.");
|
|
288
|
+
emit.event({
|
|
289
|
+
type: "outcome",
|
|
290
|
+
status: finalStatus,
|
|
291
|
+
source: resolution.source,
|
|
292
|
+
resultPath,
|
|
293
|
+
});
|
|
294
|
+
return 0;
|
|
295
|
+
}
|
|
296
|
+
catch (error) {
|
|
297
|
+
const message = error instanceof Error ? error.message : "Unexpected error.";
|
|
298
|
+
emit.info(message);
|
|
299
|
+
emit.event({
|
|
300
|
+
type: "outcome",
|
|
301
|
+
status: CliOutcomeStatus.UNEXPECTED_INTERNAL_ERROR,
|
|
302
|
+
source,
|
|
303
|
+
reason: message,
|
|
304
|
+
});
|
|
305
|
+
if (runLogPath) {
|
|
306
|
+
emit.info(`Run log: ${runLogPath}`);
|
|
307
|
+
}
|
|
308
|
+
else if (fetchLogPath) {
|
|
309
|
+
emit.info(`Fetch log: ${fetchLogPath}`);
|
|
310
|
+
}
|
|
311
|
+
else if (setupLogPath) {
|
|
312
|
+
emit.info(`Setup log: ${setupLogPath}`);
|
|
313
|
+
}
|
|
314
|
+
return 1;
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
async function runList(options) {
|
|
318
|
+
const sources = await loadRegistrySources();
|
|
319
|
+
const installedSourceIds = new Set((await listInstalledConnectorFiles()).map((source) => source.source));
|
|
320
|
+
const enrichedSources = sources.map((source) => ({
|
|
321
|
+
...source,
|
|
322
|
+
installed: installedSourceIds.has(source.id),
|
|
323
|
+
}));
|
|
324
|
+
if (options.json) {
|
|
325
|
+
process.stdout.write(`${JSON.stringify({ sources: enrichedSources })}\n`);
|
|
326
|
+
return 0;
|
|
327
|
+
}
|
|
328
|
+
const emit = createEmitter(options);
|
|
329
|
+
for (const source of enrichedSources) {
|
|
330
|
+
const description = source.description ? ` - ${source.description}` : "";
|
|
331
|
+
const installed = source.installed ? " [installed]" : "";
|
|
332
|
+
emit.info(`${source.name}${installed}${description}`);
|
|
333
|
+
}
|
|
334
|
+
return 0;
|
|
335
|
+
}
|
|
336
|
+
async function runStatus(options) {
|
|
337
|
+
const emit = createEmitter(options);
|
|
338
|
+
const runtime = new ManagedPlaywrightRuntime();
|
|
339
|
+
const personalServer = await detectPersonalServerTarget();
|
|
340
|
+
const state = await readCliState();
|
|
341
|
+
const registrySources = await loadRegistrySources();
|
|
342
|
+
const sourceLabels = createSourceLabelMap(registrySources);
|
|
343
|
+
const sourceMetadata = createSourceMetadataMap(registrySources);
|
|
344
|
+
const sources = await gatherSourceStatuses(state.sources, sourceMetadata);
|
|
345
|
+
const status = {
|
|
346
|
+
runtime: runtime.state,
|
|
347
|
+
runtimePath: runtime.state === "installed" ? runtime.runnerDir : null,
|
|
348
|
+
personalServer: personalServer.state,
|
|
349
|
+
personalServerUrl: personalServer.url,
|
|
350
|
+
sources,
|
|
351
|
+
};
|
|
352
|
+
if (options.json) {
|
|
353
|
+
process.stdout.write(`${JSON.stringify(status)}\n`);
|
|
354
|
+
return 0;
|
|
355
|
+
}
|
|
356
|
+
emit.info("Vana Connect status");
|
|
357
|
+
emit.info("");
|
|
358
|
+
emit.info(`Runtime: ${status.runtime}`);
|
|
359
|
+
emit.info(`Personal Server: ${status.personalServer}`);
|
|
360
|
+
emit.info("");
|
|
361
|
+
for (const source of status.sources) {
|
|
362
|
+
emit.info(formatSourceStatus(source, sourceLabels));
|
|
363
|
+
const details = formatSourceStatusDetail(source);
|
|
364
|
+
if (details) {
|
|
365
|
+
emit.info(` ${details}`);
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
return 0;
|
|
369
|
+
}
|
|
370
|
+
async function runSetup(options) {
|
|
371
|
+
const emit = createEmitter(options);
|
|
372
|
+
const runtime = new ManagedPlaywrightRuntime();
|
|
373
|
+
if (runtime.state === "installed") {
|
|
374
|
+
emit.info("Vana Connect runtime is already installed.");
|
|
375
|
+
emit.event({ type: "setup-check", runtime: runtime.state });
|
|
376
|
+
return 0;
|
|
377
|
+
}
|
|
378
|
+
try {
|
|
379
|
+
const result = await runtime.ensureInstalled(Boolean(options.yes));
|
|
380
|
+
emit.info("Runtime ready.");
|
|
381
|
+
if (result.logPath) {
|
|
382
|
+
emit.info(`Setup log: ${result.logPath}`);
|
|
383
|
+
}
|
|
384
|
+
emit.event({
|
|
385
|
+
type: "setup-complete",
|
|
386
|
+
runtime: result.runtime,
|
|
387
|
+
logPath: result.logPath,
|
|
388
|
+
});
|
|
389
|
+
return 0;
|
|
390
|
+
}
|
|
391
|
+
catch (error) {
|
|
392
|
+
const message = error instanceof Error
|
|
393
|
+
? error.message
|
|
394
|
+
: "Vana Connect could not finish installing the local runtime.";
|
|
395
|
+
emit.info(message);
|
|
396
|
+
emit.event({
|
|
397
|
+
type: "outcome",
|
|
398
|
+
status: CliOutcomeStatus.RUNTIME_ERROR,
|
|
399
|
+
reason: message,
|
|
400
|
+
});
|
|
401
|
+
return 1;
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
function createEmitter(options) {
|
|
405
|
+
return {
|
|
406
|
+
event(event) {
|
|
407
|
+
if (options.json) {
|
|
408
|
+
process.stdout.write(`${JSON.stringify(event)}\n`);
|
|
409
|
+
}
|
|
410
|
+
},
|
|
411
|
+
info(message) {
|
|
412
|
+
if (options.json || options.quiet) {
|
|
413
|
+
return;
|
|
414
|
+
}
|
|
415
|
+
process.stdout.write(`${message}\n`);
|
|
416
|
+
},
|
|
417
|
+
};
|
|
418
|
+
}
|
|
419
|
+
function displaySource(source, labels = {}) {
|
|
420
|
+
return labels[source] ?? source.charAt(0).toUpperCase() + source.slice(1);
|
|
421
|
+
}
|
|
422
|
+
function humanizeField(value) {
|
|
423
|
+
return value
|
|
424
|
+
.replace(/([a-z])([A-Z])/g, "$1 $2")
|
|
425
|
+
.replace(/[_-]/g, " ")
|
|
426
|
+
.replace(/^\w/, (match) => match.toUpperCase());
|
|
427
|
+
}
|
|
428
|
+
async function gatherSourceStatuses(storedSources, metadata = {}) {
|
|
429
|
+
const installedFiles = await listInstalledConnectorFiles();
|
|
430
|
+
const sourceNames = new Set([
|
|
431
|
+
...Object.keys(storedSources),
|
|
432
|
+
...installedFiles.map((file) => file.source),
|
|
433
|
+
]);
|
|
434
|
+
return [...sourceNames]
|
|
435
|
+
.sort((left, right) => left.localeCompare(right))
|
|
436
|
+
.map((source) => {
|
|
437
|
+
const stored = storedSources[source] ?? {};
|
|
438
|
+
const installed = installedFiles.some((file) => file.source === source);
|
|
439
|
+
const details = metadata[source];
|
|
440
|
+
return {
|
|
441
|
+
source,
|
|
442
|
+
name: details?.name,
|
|
443
|
+
company: details?.company,
|
|
444
|
+
description: details?.description,
|
|
445
|
+
installed,
|
|
446
|
+
sessionPresent: stored.sessionPresent ?? false,
|
|
447
|
+
lastRunAt: stored.lastRunAt ?? null,
|
|
448
|
+
lastRunOutcome: stored.lastRunOutcome ?? null,
|
|
449
|
+
dataState: stored.dataState === "ingested_personal_server"
|
|
450
|
+
? "ingested_personal_server"
|
|
451
|
+
: stored.dataState === "collected_local"
|
|
452
|
+
? "collected_local"
|
|
453
|
+
: "none",
|
|
454
|
+
lastError: stored.lastError ?? null,
|
|
455
|
+
};
|
|
456
|
+
});
|
|
457
|
+
}
|
|
458
|
+
async function listInstalledConnectorFiles() {
|
|
459
|
+
const connectorsDir = getConnectorCacheDir();
|
|
460
|
+
try {
|
|
461
|
+
const results = [];
|
|
462
|
+
const entries = await fsp.readdir(connectorsDir, { withFileTypes: true });
|
|
463
|
+
for (const entry of entries) {
|
|
464
|
+
if (!entry.isDirectory()) {
|
|
465
|
+
continue;
|
|
466
|
+
}
|
|
467
|
+
const companyDir = path.join(connectorsDir, entry.name);
|
|
468
|
+
const files = await fsp.readdir(companyDir);
|
|
469
|
+
for (const file of files) {
|
|
470
|
+
if (!file.endsWith("-playwright.js")) {
|
|
471
|
+
continue;
|
|
472
|
+
}
|
|
473
|
+
results.push({
|
|
474
|
+
source: file.replace(/-playwright\.js$/, ""),
|
|
475
|
+
path: path.join(companyDir, file),
|
|
476
|
+
});
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
return results;
|
|
480
|
+
}
|
|
481
|
+
catch {
|
|
482
|
+
return [];
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
function formatSourceStatus(source, labels = {}) {
|
|
486
|
+
if (!source.installed) {
|
|
487
|
+
return `${displaySource(source.source, labels)}: not connected`;
|
|
488
|
+
}
|
|
489
|
+
if (!source.lastRunOutcome) {
|
|
490
|
+
return `${displaySource(source.source, labels)}: installed`;
|
|
491
|
+
}
|
|
492
|
+
if (source.lastRunOutcome === CliOutcomeStatus.NEEDS_INPUT) {
|
|
493
|
+
return `${displaySource(source.source, labels)}: needs input`;
|
|
494
|
+
}
|
|
495
|
+
if (source.lastRunOutcome === CliOutcomeStatus.RUNTIME_ERROR) {
|
|
496
|
+
return `${displaySource(source.source, labels)}: error`;
|
|
497
|
+
}
|
|
498
|
+
if (source.lastRunOutcome === CliOutcomeStatus.LEGACY_AUTH) {
|
|
499
|
+
return `${displaySource(source.source, labels)}: legacy auth required`;
|
|
500
|
+
}
|
|
501
|
+
if (source.dataState === "ingested_personal_server") {
|
|
502
|
+
return `${displaySource(source.source, labels)}: connected, synced`;
|
|
503
|
+
}
|
|
504
|
+
if (source.dataState === "collected_local") {
|
|
505
|
+
return `${displaySource(source.source, labels)}: connected, local only`;
|
|
506
|
+
}
|
|
507
|
+
return `${displaySource(source.source, labels)}: connected`;
|
|
508
|
+
}
|
|
509
|
+
function formatSourceStatusDetail(source) {
|
|
510
|
+
if (source.lastRunOutcome === CliOutcomeStatus.NEEDS_INPUT) {
|
|
511
|
+
return source.lastError
|
|
512
|
+
? `${source.lastError}. Run \`vana connect ${source.source}\` interactively.`
|
|
513
|
+
: `Run \`vana connect ${source.source}\` interactively.`;
|
|
514
|
+
}
|
|
515
|
+
if (source.lastRunOutcome === CliOutcomeStatus.LEGACY_AUTH) {
|
|
516
|
+
return "This source still uses legacy headed auth and cannot complete in batch mode.";
|
|
517
|
+
}
|
|
518
|
+
if (source.lastRunOutcome === CliOutcomeStatus.RUNTIME_ERROR) {
|
|
519
|
+
return source.lastError ?? "The last connector run failed.";
|
|
520
|
+
}
|
|
521
|
+
if (!source.lastRunOutcome && source.installed) {
|
|
522
|
+
return `Run \`vana connect ${source.source}\` to collect data.`;
|
|
523
|
+
}
|
|
524
|
+
return null;
|
|
525
|
+
}
|
|
526
|
+
function normalizeArgv(argv) {
|
|
527
|
+
if (argv[2] === "connect" &&
|
|
528
|
+
["list", "status", "setup"].includes(argv[3] ?? "")) {
|
|
529
|
+
const mapping = {
|
|
530
|
+
list: "sources",
|
|
531
|
+
status: "status",
|
|
532
|
+
setup: "setup",
|
|
533
|
+
};
|
|
534
|
+
return [argv[0], argv[1], mapping[argv[3]], ...argv.slice(4)];
|
|
535
|
+
}
|
|
536
|
+
return argv;
|
|
537
|
+
}
|
|
538
|
+
function extractGlobalOptions(argv) {
|
|
539
|
+
return {
|
|
540
|
+
json: argv.includes("--json"),
|
|
541
|
+
noInput: argv.includes("--no-input"),
|
|
542
|
+
yes: argv.includes("--yes"),
|
|
543
|
+
quiet: argv.includes("--quiet"),
|
|
544
|
+
};
|
|
545
|
+
}
|
|
546
|
+
function createSourceLabelMap(sources) {
|
|
547
|
+
return Object.fromEntries(sources.map((source) => [source.id, source.name]));
|
|
548
|
+
}
|
|
549
|
+
function createSourceMetadataMap(sources) {
|
|
550
|
+
return Object.fromEntries(sources.map((source) => [
|
|
551
|
+
source.id,
|
|
552
|
+
{
|
|
553
|
+
name: source.name,
|
|
554
|
+
company: source.company,
|
|
555
|
+
description: source.description,
|
|
556
|
+
},
|
|
557
|
+
]));
|
|
558
|
+
}
|
|
559
|
+
async function loadRegistrySources() {
|
|
560
|
+
try {
|
|
561
|
+
return ((await listAvailableSources(findDataConnectorsDir() ?? undefined)) ?? []);
|
|
562
|
+
}
|
|
563
|
+
catch {
|
|
564
|
+
return [];
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
//# sourceMappingURL=index.js.map
|