@danielsinewe/1cv 0.1.0-beta.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Daniel Sinewe
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,32 @@
1
+ # 1CV CLI
2
+
3
+ Keep one freelancer profile on your machine and reuse it across marketplaces.
4
+
5
+ From this repository:
6
+
7
+ ```bash
8
+ cd cli
9
+ npm install
10
+ npm test
11
+ npm link
12
+
13
+ 1cv init
14
+ 1cv plan bizforward
15
+ 1cv apply bizforward
16
+ ```
17
+
18
+ During the public beta, start without cloning the repository:
19
+
20
+ ```bash
21
+ npx @danielsinewe/1cv@next init
22
+ npx @danielsinewe/1cv@next plan bizforward
23
+ npx @danielsinewe/1cv@next apply bizforward
24
+ ```
25
+
26
+ Or install it globally with `npm install -g @danielsinewe/1cv@next`.
27
+
28
+ `init` guides a new user through the minimum profile fields and writes them to the local profile file. No repository checkout or manual JSON editing is required.
29
+
30
+ `apply` opens Chrome, fills the form, and stops for review. Submission is explicit: `1cv apply bizforward --consent --submit`.
31
+
32
+ Data lives in `~/.1cv/profile.json` by default. Change the location with `ONECV_HOME` or `--profile`. 1CV stores no marketplace passwords, includes no telemetry, and uses a dedicated persistent Chrome profile at `~/.1cv/browser-profile`. To reuse a browser you already started with remote debugging, pass `--cdp-url http://127.0.0.1:9222`.
@@ -0,0 +1,2 @@
1
+ import type { PlatformAdapter } from "./types.js";
2
+ export declare const bizforwardAdapter: PlatformAdapter;
@@ -0,0 +1,91 @@
1
+ import { access, stat } from "node:fs/promises";
2
+ import { expandHome, preferredProfileUrl } from "../profile.js";
3
+ const URL = "https://bizforward.de/freelancer-sign-up/";
4
+ function specialty(profile) {
5
+ return profile.professional.specialties.join(", ");
6
+ }
7
+ export const bizforwardAdapter = {
8
+ id: "bizforward",
9
+ name: "BizForward",
10
+ url: URL,
11
+ async plan(profile) {
12
+ const profileUrl = preferredProfileUrl(profile);
13
+ const warnings = [];
14
+ if (!profileUrl)
15
+ warnings.push("Add a LinkedIn, XING, website, or GitHub URL.");
16
+ if (profile.assets.cv) {
17
+ try {
18
+ const cv = expandHome(profile.assets.cv);
19
+ const file = await stat(cv);
20
+ if (file.size > 25 * 1024 * 1024)
21
+ warnings.push(`CV exceeds BizForward's 25 MB limit: ${cv}`);
22
+ }
23
+ catch {
24
+ warnings.push(`CV not found: ${expandHome(profile.assets.cv)}`);
25
+ }
26
+ }
27
+ else {
28
+ warnings.push("No CV configured. BizForward accepts one, but it is optional.");
29
+ }
30
+ return {
31
+ platform: this.name,
32
+ url: this.url,
33
+ fields: [
34
+ { field: "Name", value: profile.identity.fullName, required: true },
35
+ { field: "E-Mail", value: profile.identity.email, required: true },
36
+ { field: "Fachgebiet", value: specialty(profile), required: true },
37
+ { field: "Dein Profil", value: profileUrl ?? "", required: true },
38
+ { field: "CV", value: profile.assets.cv ? expandHome(profile.assets.cv) : "", required: false }
39
+ ],
40
+ warnings
41
+ };
42
+ },
43
+ async fill(page, profile, options) {
44
+ const plan = await this.plan(profile);
45
+ const missing = plan.fields.filter((field) => field.required && !field.value);
46
+ if (missing.length)
47
+ throw new Error(`Missing required fields: ${missing.map((field) => field.field).join(", ")}`);
48
+ await page.goto(this.url, { waitUntil: "domcontentloaded" });
49
+ const cookieDialog = page.getByRole("dialog");
50
+ if (await cookieDialog.isVisible().catch(() => false)) {
51
+ const save = cookieDialog.getByRole("button", { name: "Speichern", exact: true });
52
+ if (await save.isVisible().catch(() => false))
53
+ await save.click();
54
+ }
55
+ await page.getByRole("textbox", { name: "Name", exact: true }).fill(profile.identity.fullName);
56
+ await page.getByRole("textbox", { name: "E-Mail", exact: true }).fill(profile.identity.email);
57
+ await page.getByRole("textbox", { name: "Fachgebiet", exact: true }).fill(specialty(profile));
58
+ await page.getByRole("textbox", { name: "Dein Profil", exact: true }).fill(preferredProfileUrl(profile) ?? "");
59
+ if (profile.assets.cv) {
60
+ const cv = expandHome(profile.assets.cv);
61
+ await access(cv);
62
+ await page.locator('input[type="file"][name="input_10"]').setInputFiles(cv);
63
+ }
64
+ if (options.consent) {
65
+ await page.getByRole("checkbox", { name: /bizforward meine persönlichen Daten/ }).check();
66
+ }
67
+ const expected = [
68
+ ["Name", profile.identity.fullName],
69
+ ["E-Mail", profile.identity.email],
70
+ ["Fachgebiet", specialty(profile)],
71
+ ["Dein Profil", preferredProfileUrl(profile) ?? ""]
72
+ ];
73
+ for (const [name, value] of expected) {
74
+ const actual = await page.getByRole("textbox", { name, exact: true }).inputValue();
75
+ if (actual !== value)
76
+ throw new Error(`Browser verification failed for ${name}.`);
77
+ }
78
+ if (options.consent && !await page.getByRole("checkbox", { name: /bizforward meine persönlichen Daten/ }).isChecked()) {
79
+ throw new Error("Browser verification failed for BizForward consent.");
80
+ }
81
+ },
82
+ async submit(page) {
83
+ await page.getByRole("button", { name: "SENDEN", exact: true }).click();
84
+ },
85
+ async verify(page) {
86
+ const confirmation = page.locator("#gform_confirmation_wrapper_1");
87
+ await confirmation.waitFor({ state: "visible", timeout: 15_000 });
88
+ return (await confirmation.innerText()).trim() || "BizForward confirmed the submission.";
89
+ }
90
+ };
91
+ //# sourceMappingURL=bizforward.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bizforward.js","sourceRoot":"","sources":["../../src/adapters/bizforward.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,30 @@
1
+ import type { BrowserContext, Page } from "playwright";
2
+ import type { Profile } from "../profile.js";
3
+ export interface FieldPlan {
4
+ field: string;
5
+ value: string;
6
+ required: boolean;
7
+ }
8
+ export interface ApplicationPlan {
9
+ platform: string;
10
+ url: string;
11
+ fields: FieldPlan[];
12
+ warnings: string[];
13
+ }
14
+ export interface ApplyOptions {
15
+ consent: boolean;
16
+ submit: boolean;
17
+ }
18
+ export interface PlatformAdapter {
19
+ id: string;
20
+ name: string;
21
+ url: string;
22
+ plan(profile: Profile): Promise<ApplicationPlan>;
23
+ fill(page: Page, profile: Profile, options: ApplyOptions): Promise<void>;
24
+ submit(page: Page): Promise<void>;
25
+ verify(page: Page): Promise<string>;
26
+ }
27
+ export interface BrowserSession {
28
+ context: BrowserContext;
29
+ close(): Promise<void>;
30
+ }
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/adapters/types.ts"],"names":[],"mappings":""}
@@ -0,0 +1,7 @@
1
+ import type { BrowserSession } from "./adapters/types.js";
2
+ export interface BrowserOptions {
3
+ cdpUrl?: string;
4
+ headless: boolean;
5
+ userDataDir?: string;
6
+ }
7
+ export declare function openBrowser(options: BrowserOptions): Promise<BrowserSession>;
@@ -0,0 +1,25 @@
1
+ import { mkdir } from "node:fs/promises";
2
+ import { chromium } from "playwright";
3
+ import { expandHome, oneCvHome } from "./profile.js";
4
+ export async function openBrowser(options) {
5
+ if (options.cdpUrl) {
6
+ const browser = await chromium.connectOverCDP(options.cdpUrl);
7
+ const context = browser.contexts()[0];
8
+ if (!context)
9
+ throw new Error(`No browser context found at ${options.cdpUrl}`);
10
+ return { context, close: async () => undefined };
11
+ }
12
+ const userDataDir = expandHome(options.userDataDir ?? `${oneCvHome()}/browser-profile`);
13
+ await mkdir(userDataDir, { recursive: true, mode: 0o700 });
14
+ try {
15
+ const context = await chromium.launchPersistentContext(userDataDir, {
16
+ channel: "chrome",
17
+ headless: options.headless
18
+ });
19
+ return { context, close: () => context.close() };
20
+ }
21
+ catch (error) {
22
+ throw new Error(`Could not start Google Chrome. Install it or connect to an existing browser with --cdp-url. ${error instanceof Error ? error.message : ""}`);
23
+ }
24
+ }
25
+ //# sourceMappingURL=browser.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"browser.js","sourceRoot":"","sources":["../src/browser.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,kBAAkB,CAAC;AACzC,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AACtC,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AASrD,MAAM,CAAC,KAAK,UAAU,WAAW,CAAC,OAAuB;IACvD,IAAI,OAAO,CAAC,MAAM,EAAE,CAAC;QACnB,MAAM,OAAO,GAAG,MAAM,QAAQ,CAAC,cAAc,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;QAC9D,MAAM,OAAO,GAAG,OAAO,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC,CAAC;QACtC,IAAI,CAAC,OAAO;YAAE,MAAM,IAAI,KAAK,CAAC,+BAA+B,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;QAC/E,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,KAAK,IAAI,EAAE,CAAC,SAAS,EAAE,CAAC;IACnD,CAAC;IAED,MAAM,WAAW,GAAG,UAAU,CAAC,OAAO,CAAC,WAAW,IAAI,GAAG,SAAS,EAAE,kBAAkB,CAAC,CAAC;IACxF,MAAM,KAAK,CAAC,WAAW,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;IAC3D,IAAI,CAAC;QACH,MAAM,OAAO,GAAG,MAAM,QAAQ,CAAC,uBAAuB,CAAC,WAAW,EAAE;YAClE,OAAO,EAAE,QAAQ;YACjB,QAAQ,EAAE,OAAO,CAAC,QAAQ;SAC3B,CAAC,CAAC;QACH,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,GAAG,EAAE,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC;IACnD,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,MAAM,IAAI,KAAK,CACb,+FAA+F,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,EAAE,CAC7I,CAAC;IACJ,CAAC;AACH,CAAC"}
package/dist/cli.d.ts ADDED
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
package/dist/cli.js ADDED
@@ -0,0 +1,211 @@
1
+ #!/usr/bin/env node
2
+ import { createInterface } from "node:readline/promises";
3
+ import { stdin, stdout } from "node:process";
4
+ import { bizforwardAdapter } from "./adapters/bizforward.js";
5
+ import { openBrowser } from "./browser.js";
6
+ import { createProfile, defaultProfilePath, loadProfile } from "./profile.js";
7
+ const adapters = { bizforward: bizforwardAdapter };
8
+ function parseArgs(args) {
9
+ const positionals = [];
10
+ const flags = new Map();
11
+ for (let index = 0; index < args.length; index += 1) {
12
+ const arg = args[index];
13
+ if (!arg.startsWith("--")) {
14
+ positionals.push(arg);
15
+ continue;
16
+ }
17
+ const [rawKey, inlineValue] = arg.slice(2).split("=", 2);
18
+ if (!rawKey)
19
+ continue;
20
+ if (inlineValue !== undefined) {
21
+ flags.set(rawKey, inlineValue);
22
+ continue;
23
+ }
24
+ const next = args[index + 1];
25
+ if (next && !next.startsWith("--")) {
26
+ flags.set(rawKey, next);
27
+ index += 1;
28
+ }
29
+ else {
30
+ flags.set(rawKey, true);
31
+ }
32
+ }
33
+ return { positionals, flags };
34
+ }
35
+ function textFlag(args, name, fallback) {
36
+ const value = args.flags.get(name);
37
+ return typeof value === "string" ? value : fallback;
38
+ }
39
+ function adapterFor(id) {
40
+ if (!id || !adapters[id])
41
+ throw new Error(`Unknown platform: ${id ?? "(missing)"}. Run "1cv platforms".`);
42
+ return adapters[id];
43
+ }
44
+ function help() {
45
+ console.log(`1CV — one local profile for freelance marketplaces
46
+
47
+ Usage:
48
+ 1cv init [--profile path] [--force]
49
+ 1cv profile [--profile path] [--json]
50
+ 1cv platforms [--json]
51
+ 1cv plan <platform> [--profile path] [--json]
52
+ 1cv apply <platform> [--profile path] [--consent] [--submit]
53
+ [--browser-profile path | --cdp-url url] [--headless]
54
+
55
+ Safety:
56
+ apply fills the form and pauses for review. It never submits unless both
57
+ --submit and --consent are present. Profile data stays on this machine.`);
58
+ }
59
+ function profileLink(url) {
60
+ const hostname = new URL(url).hostname.replace(/^www\./, "");
61
+ if (hostname === "linkedin.com")
62
+ return { linkedin: url };
63
+ if (hostname === "xing.com")
64
+ return { xing: url };
65
+ if (hostname === "github.com")
66
+ return { github: url };
67
+ return { website: url };
68
+ }
69
+ async function requiredAnswer(readline, question, supplied) {
70
+ if (supplied?.trim())
71
+ return supplied.trim();
72
+ if (!stdin.isTTY)
73
+ throw new Error(`Missing ${question.replace(/:\s*$/, "").toLowerCase()}. Pass it as a command option.`);
74
+ while (true) {
75
+ const answer = (await readline.question(question)).trim();
76
+ if (answer)
77
+ return answer;
78
+ }
79
+ }
80
+ async function onboardingProfile(args) {
81
+ const readline = createInterface({ input: stdin, output: stdout });
82
+ try {
83
+ if (stdin.isTTY)
84
+ console.log("Your profile stays on this machine. 1CV does not collect credentials or analytics.\n");
85
+ const fullName = await requiredAnswer(readline, "Name: ", textFlag(args, "name"));
86
+ const email = await requiredAnswer(readline, "Email: ", textFlag(args, "email"));
87
+ const specialty = await requiredAnswer(readline, "Specialty: ", textFlag(args, "specialty"));
88
+ const publicProfile = await requiredAnswer(readline, "LinkedIn, XING, or website URL: ", textFlag(args, "profile-url"));
89
+ const suppliedCv = textFlag(args, "cv");
90
+ const cv = suppliedCv !== undefined
91
+ ? suppliedCv.trim()
92
+ : stdin.isTTY ? (await readline.question("CV file (optional): ")).trim() : "";
93
+ return {
94
+ version: 1,
95
+ identity: {
96
+ fullName,
97
+ email,
98
+ links: profileLink(publicProfile)
99
+ },
100
+ professional: {
101
+ headline: specialty,
102
+ summary: "",
103
+ specialties: [specialty],
104
+ skills: [],
105
+ languages: []
106
+ },
107
+ preferences: {
108
+ remote: true,
109
+ locations: []
110
+ },
111
+ assets: cv ? { cv } : {}
112
+ };
113
+ }
114
+ finally {
115
+ readline.close();
116
+ }
117
+ }
118
+ function printPlan(plan) {
119
+ console.log(`${plan.platform}\n${plan.url}`);
120
+ for (const field of plan.fields)
121
+ console.log(`${field.required ? "*" : " "} ${field.field}: ${field.value || "—"}`);
122
+ for (const warning of plan.warnings)
123
+ console.log(`! ${warning}`);
124
+ }
125
+ async function waitForReview() {
126
+ if (!stdin.isTTY)
127
+ return;
128
+ const readline = createInterface({ input: stdin, output: stdout });
129
+ await readline.question("Review the filled form in Chrome. Press Enter to close 1CV… ");
130
+ readline.close();
131
+ }
132
+ async function main() {
133
+ const args = parseArgs(process.argv.slice(2));
134
+ const [command, platform] = args.positionals;
135
+ const profilePath = textFlag(args, "profile", defaultProfilePath());
136
+ if (!command || command === "help" || args.flags.has("help")) {
137
+ help();
138
+ return;
139
+ }
140
+ if (command === "init") {
141
+ const profile = await onboardingProfile(args);
142
+ const path = await createProfile(profilePath, args.flags.has("force"), profile);
143
+ console.log(`Created ${path}`);
144
+ console.log("Next: 1cv plan bizforward");
145
+ return;
146
+ }
147
+ if (command === "profile") {
148
+ const profile = await loadProfile(profilePath);
149
+ if (args.flags.has("json"))
150
+ console.log(JSON.stringify(profile, null, 2));
151
+ else
152
+ console.log(`Valid profile: ${profile.identity.fullName} <${profile.identity.email}>\n${profilePath}`);
153
+ return;
154
+ }
155
+ if (command === "platforms") {
156
+ const platforms = Object.values(adapters).map(({ id, name, url }) => ({ id, name, url }));
157
+ if (args.flags.has("json"))
158
+ console.log(JSON.stringify(platforms, null, 2));
159
+ else
160
+ platforms.forEach((item) => console.log(`${item.id}\t${item.name}\t${item.url}`));
161
+ return;
162
+ }
163
+ if (command === "plan") {
164
+ const plan = await adapterFor(platform).plan(await loadProfile(profilePath));
165
+ if (args.flags.has("json"))
166
+ console.log(JSON.stringify(plan, null, 2));
167
+ else
168
+ printPlan(plan);
169
+ if (plan.fields.some((field) => field.required && !field.value))
170
+ process.exitCode = 2;
171
+ return;
172
+ }
173
+ if (command === "apply") {
174
+ const adapter = adapterFor(platform);
175
+ const profile = await loadProfile(profilePath);
176
+ const consent = args.flags.has("consent");
177
+ const submit = args.flags.has("submit");
178
+ if (submit && !consent)
179
+ throw new Error("--submit requires --consent. Consent is never inferred.");
180
+ const plan = await adapter.plan(profile);
181
+ if (plan.warnings.length)
182
+ printPlan(plan);
183
+ const session = await openBrowser({
184
+ cdpUrl: textFlag(args, "cdp-url"),
185
+ headless: args.flags.has("headless"),
186
+ userDataDir: textFlag(args, "browser-profile")
187
+ });
188
+ try {
189
+ const page = await session.context.newPage();
190
+ await adapter.fill(page, profile, { consent, submit });
191
+ if (submit) {
192
+ await adapter.submit(page);
193
+ console.log(await adapter.verify(page));
194
+ }
195
+ else {
196
+ console.log("Form filled. Nothing has been submitted.");
197
+ await waitForReview();
198
+ }
199
+ }
200
+ finally {
201
+ await session.close();
202
+ }
203
+ return;
204
+ }
205
+ throw new Error(`Unknown command: ${command}`);
206
+ }
207
+ main().catch((error) => {
208
+ console.error(`1cv: ${error instanceof Error ? error.message : String(error)}`);
209
+ process.exitCode = 1;
210
+ });
211
+ //# sourceMappingURL=cli.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,47 @@
1
+ import { z } from "zod";
2
+ export declare const ProfileSchema: z.ZodObject<{
3
+ version: z.ZodLiteral<1>;
4
+ identity: z.ZodObject<{
5
+ fullName: z.ZodString;
6
+ email: z.ZodString;
7
+ phone: z.ZodOptional<z.ZodString>;
8
+ location: z.ZodOptional<z.ZodString>;
9
+ links: z.ZodDefault<z.ZodObject<{
10
+ website: z.ZodOptional<z.ZodString>;
11
+ linkedin: z.ZodOptional<z.ZodString>;
12
+ xing: z.ZodOptional<z.ZodString>;
13
+ github: z.ZodOptional<z.ZodString>;
14
+ }, z.core.$strip>>;
15
+ }, z.core.$strip>;
16
+ professional: z.ZodObject<{
17
+ headline: z.ZodString;
18
+ summary: z.ZodDefault<z.ZodString>;
19
+ specialties: z.ZodArray<z.ZodString>;
20
+ skills: z.ZodDefault<z.ZodArray<z.ZodString>>;
21
+ languages: z.ZodDefault<z.ZodArray<z.ZodString>>;
22
+ }, z.core.$strip>;
23
+ preferences: z.ZodDefault<z.ZodObject<{
24
+ availableFrom: z.ZodOptional<z.ZodString>;
25
+ remote: z.ZodDefault<z.ZodBoolean>;
26
+ locations: z.ZodDefault<z.ZodArray<z.ZodString>>;
27
+ rate: z.ZodOptional<z.ZodObject<{
28
+ amount: z.ZodNumber;
29
+ currency: z.ZodDefault<z.ZodString>;
30
+ unit: z.ZodEnum<{
31
+ day: "day";
32
+ hour: "hour";
33
+ }>;
34
+ }, z.core.$strip>>;
35
+ }, z.core.$strip>>;
36
+ assets: z.ZodDefault<z.ZodObject<{
37
+ cv: z.ZodOptional<z.ZodString>;
38
+ }, z.core.$strip>>;
39
+ }, z.core.$strip>;
40
+ export type Profile = z.infer<typeof ProfileSchema>;
41
+ export declare const PROFILE_TEMPLATE: Profile;
42
+ export declare function oneCvHome(): string;
43
+ export declare function defaultProfilePath(): string;
44
+ export declare function expandHome(path: string): string;
45
+ export declare function createProfile(path?: string, force?: boolean, profile?: Profile): Promise<string>;
46
+ export declare function loadProfile(path?: string): Promise<Profile>;
47
+ export declare function preferredProfileUrl(profile: Profile): string | undefined;
@@ -0,0 +1,120 @@
1
+ import { mkdir, readFile, stat, writeFile } from "node:fs/promises";
2
+ import { homedir } from "node:os";
3
+ import { dirname, resolve } from "node:path";
4
+ import { z } from "zod";
5
+ const Url = z.string().url();
6
+ export const ProfileSchema = z.object({
7
+ version: z.literal(1),
8
+ identity: z.object({
9
+ fullName: z.string().min(1),
10
+ email: z.string().email(),
11
+ phone: z.string().optional(),
12
+ location: z.string().optional(),
13
+ links: z.object({
14
+ website: Url.optional(),
15
+ linkedin: Url.optional(),
16
+ xing: Url.optional(),
17
+ github: Url.optional()
18
+ }).default({})
19
+ }),
20
+ professional: z.object({
21
+ headline: z.string().min(1),
22
+ summary: z.string().default(""),
23
+ specialties: z.array(z.string().min(1)).min(1),
24
+ skills: z.array(z.string().min(1)).default([]),
25
+ languages: z.array(z.string().min(1)).default([])
26
+ }),
27
+ preferences: z.object({
28
+ availableFrom: z.string().optional(),
29
+ remote: z.boolean().default(true),
30
+ locations: z.array(z.string()).default([]),
31
+ rate: z.object({
32
+ amount: z.number().positive(),
33
+ currency: z.string().length(3).default("EUR"),
34
+ unit: z.enum(["hour", "day"])
35
+ }).optional()
36
+ }).default({ remote: true, locations: [] }),
37
+ assets: z.object({
38
+ cv: z.string().optional()
39
+ }).default({})
40
+ });
41
+ export const PROFILE_TEMPLATE = {
42
+ version: 1,
43
+ identity: {
44
+ fullName: "Your name",
45
+ email: "you@example.com",
46
+ location: "Berlin, Germany",
47
+ links: {
48
+ linkedin: "https://www.linkedin.com/in/your-profile"
49
+ }
50
+ },
51
+ professional: {
52
+ headline: "Independent product and software consultant",
53
+ summary: "",
54
+ specialties: ["Software Development"],
55
+ skills: [],
56
+ languages: ["German", "English"]
57
+ },
58
+ preferences: {
59
+ remote: true,
60
+ locations: []
61
+ },
62
+ assets: {
63
+ cv: "~/Documents/CV.pdf"
64
+ }
65
+ };
66
+ export function oneCvHome() {
67
+ return resolve(process.env.ONECV_HOME ?? `${homedir()}/.1cv`);
68
+ }
69
+ export function defaultProfilePath() {
70
+ return resolve(oneCvHome(), "profile.json");
71
+ }
72
+ export function expandHome(path) {
73
+ return path === "~" || path.startsWith("~/")
74
+ ? resolve(homedir(), path.slice(path === "~" ? 1 : 2))
75
+ : resolve(path);
76
+ }
77
+ export async function createProfile(path = defaultProfilePath(), force = false, profile = PROFILE_TEMPLATE) {
78
+ const target = expandHome(path);
79
+ if (!force) {
80
+ try {
81
+ await stat(target);
82
+ throw new Error(`Profile already exists: ${target}. Use --force to replace it.`);
83
+ }
84
+ catch (error) {
85
+ if (error instanceof Error && error.message.startsWith("Profile already exists:"))
86
+ throw error;
87
+ if (!(error && typeof error === "object" && "code" in error && error.code === "ENOENT"))
88
+ throw error;
89
+ }
90
+ }
91
+ await mkdir(dirname(target), { recursive: true, mode: 0o700 });
92
+ const validated = ProfileSchema.parse(profile);
93
+ await writeFile(target, `${JSON.stringify(validated, null, 2)}\n`, { mode: 0o600 });
94
+ return target;
95
+ }
96
+ export async function loadProfile(path = defaultProfilePath()) {
97
+ const target = expandHome(path);
98
+ let raw;
99
+ try {
100
+ raw = JSON.parse(await readFile(target, "utf8"));
101
+ }
102
+ catch (error) {
103
+ if (error instanceof SyntaxError)
104
+ throw new Error(`Invalid JSON in ${target}: ${error.message}`);
105
+ throw new Error(`Could not read profile at ${target}`);
106
+ }
107
+ const parsed = ProfileSchema.safeParse(raw);
108
+ if (!parsed.success) {
109
+ const details = parsed.error.issues.map((issue) => `${issue.path.join(".")}: ${issue.message}`).join("\n");
110
+ throw new Error(`Invalid 1CV profile:\n${details}`);
111
+ }
112
+ return parsed.data;
113
+ }
114
+ export function preferredProfileUrl(profile) {
115
+ return profile.identity.links.linkedin
116
+ ?? profile.identity.links.xing
117
+ ?? profile.identity.links.website
118
+ ?? profile.identity.links.github;
119
+ }
120
+ //# sourceMappingURL=profile.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"profile.js","sourceRoot":"","sources":["../src/profile.ts"],"names":[],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,51 @@
1
+ {
2
+ "name": "@danielsinewe/1cv",
3
+ "version": "0.1.0-beta.0",
4
+ "description": "Local-first freelancer profile manager and browser automation CLI",
5
+ "type": "module",
6
+ "bin": {
7
+ "1cv": "dist/cli.js"
8
+ },
9
+ "files": [
10
+ "dist",
11
+ "README.md"
12
+ ],
13
+ "scripts": {
14
+ "build": "tsc -p tsconfig.json",
15
+ "test": "npm run build && node --test test/*.test.mjs",
16
+ "check": "npm run test",
17
+ "dev": "tsx src/cli.ts",
18
+ "prepack": "npm run build"
19
+ },
20
+ "engines": {
21
+ "node": ">=20"
22
+ },
23
+ "keywords": [
24
+ "freelancer",
25
+ "profile",
26
+ "browser-automation",
27
+ "local-first"
28
+ ],
29
+ "license": "MIT",
30
+ "homepage": "https://1cv.app",
31
+ "repository": {
32
+ "type": "git",
33
+ "url": "git+https://github.com/danielsinewe/onecv.git",
34
+ "directory": "cli"
35
+ },
36
+ "bugs": {
37
+ "url": "https://github.com/danielsinewe/onecv/issues"
38
+ },
39
+ "publishConfig": {
40
+ "access": "public"
41
+ },
42
+ "dependencies": {
43
+ "playwright": "^1.61.1",
44
+ "zod": "^4.4.3"
45
+ },
46
+ "devDependencies": {
47
+ "@types/node": "^26.1.1",
48
+ "tsx": "^4.20.6",
49
+ "typescript": "^7.0.2"
50
+ }
51
+ }