@usemonoid/cli 0.1.11

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,145 @@
1
+ # @usemonoid/cli
2
+
3
+ MONOid REST API CLI for terminal and script use. Authenticate with an API key and run CRUD and convenience commands against [MONOid](https://usemonoid.com).
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ npm install -g @usemonoid/cli
9
+ ```
10
+
11
+ Or run without installing:
12
+
13
+ ```bash
14
+ npx @usemonoid/cli whoami
15
+ ```
16
+
17
+ The package is published to npm for installation. You do not need access to the source repository to install or run it.
18
+
19
+ ## Release process
20
+
21
+ Publishing is automated through GitHub Actions.
22
+
23
+ 1. Update `packages/cli/package.json` to the new version.
24
+ 2. Commit and push that version bump to the default branch.
25
+ 3. Push a matching tag in the form `cli/vX.Y.Z`.
26
+
27
+ Example:
28
+
29
+ ```bash
30
+ git tag cli/v0.1.1
31
+ git push origin cli/v0.1.1
32
+ ```
33
+
34
+ The publish workflow validates that the tag matches the CLI package version and then runs `npm publish` with the repository `NPM_TOKEN` secret.
35
+
36
+ For tagged releases, the workflow also uploads the packed npm tarball to the GitHub release so the packaged CLI artifact is visible separately from GitHub's default source archives.
37
+
38
+ ## Configure authentication
39
+
40
+ **Option 1: Persist a token (interactive use)**
41
+
42
+ ```bash
43
+ monoid login
44
+ # Enter your API token when prompted (or: monoid login mo_xxxx)
45
+ monoid whoami # verify
46
+ ```
47
+
48
+ Tokens are stored in `~/.config/monoid/credentials.json`.
49
+
50
+ **Option 2: Environment variable (agents / CI)**
51
+
52
+ ```bash
53
+ export MONOID_API_TOKEN="mo_xxxx"
54
+ monoid whoami
55
+ ```
56
+
57
+ **Option 3: Per-command token**
58
+
59
+ ```bash
60
+ monoid --token mo_xxxx organisations list
61
+ ```
62
+
63
+ Create API keys in MONOid (Settings > API Keys). Use the `mo_`-prefixed key; scopes are `read`, `write`, `delete`.
64
+
65
+ ## Base URL
66
+
67
+ Default: `https://api.usemonoid.com`. Override for local or custom deployment:
68
+
69
+ ```bash
70
+ export MONOID_API_BASE_URL="https://api.example.com"
71
+ # or
72
+ monoid --base-url https://api.example.com organisations list
73
+ ```
74
+
75
+ To save it once for interactive use:
76
+
77
+ ```bash
78
+ monoid config set-base-url https://api.example.com
79
+ monoid config show
80
+ ```
81
+
82
+ You can also use `monoid config set-api ...` and `monoid --api ...` as aliases.
83
+
84
+ ## Command pattern
85
+
86
+ Resources: `organisations`, `containers`, `projects`, `tasks`, `routine-blocks`, `reviews`, `daily-notes`.
87
+
88
+ - **List:** `monoid <resource> list [--limit n] [--offset n] [--filter-flags...]`
89
+ - **Get:** `monoid <resource> get <id>` (use `<date>` for `daily-notes`)
90
+ - **Create:** `monoid <resource> create [--field value...] [--body '{"key":"value"}'] [--if-not-exists]`
91
+ - **Update:** `monoid <resource> update <id> [--field value...] [--body '...'] [--ids id1,id2]`
92
+ - **Delete:** `monoid <resource> delete <id> [--ids id1,id2]`
93
+
94
+ Example:
95
+
96
+ ```bash
97
+ monoid organisations list
98
+ monoid projects list --status active --container-id abc123
99
+ monoid tasks create --title "Ship CLI" --bucket todo --project-id xyz
100
+ monoid daily-notes get 2025-03-17
101
+ monoid routine-blocks list --date 2025-03-17
102
+ ```
103
+
104
+ ## Convenience commands
105
+
106
+ - **`monoid whoami`** – Show current user and token scopes (and optional expiry). Use this first to confirm auth.
107
+ - **`monoid status`** – One summary: org/container/project counts, tasks by bucket, today’s routine blocks, reviews.
108
+ - **`monoid calendar day [--date YYYY-MM-DD]`** – Items for the day (routines, tasks, reviews). Use `--routines`, `--tasks`, `--reviews` to filter.
109
+ - **`monoid login [token]`** / **`monoid logout`** – Store or clear the token in `~/.config/monoid/credentials.json`.
110
+ - **`monoid config show|set-base-url|unset-base-url`** – Inspect or persist the default API URL in `~/.config/monoid/credentials.json`.
111
+ - **`monoid setup-agent`** – Print setup instructions for AI agents (env vars, whoami).
112
+
113
+ ## Output format
114
+
115
+ - **`-o json`** (default when stdout is not a TTY, e.g. in scripts): machine-readable JSON.
116
+ - **`-o table`** (default in an interactive terminal): human-readable table.
117
+ - **`-o csv`**: CSV for spreadsheets or other tools.
118
+
119
+ Errors are always structured; in JSON mode they look like:
120
+
121
+ ```json
122
+ { "error": true, "status": 401, "code": "UNAUTHORIZED", "message": "Missing or invalid token" }
123
+ ```
124
+
125
+ Exit codes: `0` success; `1` auth/validation; `2` server/network error.
126
+
127
+ ## Regenerating commands from the OpenAPI spec
128
+
129
+ The resource commands are generated from the MONOid API OpenAPI spec. After changing the spec (e.g. in `apps/api/spec/openapi.yaml`), regenerate and rebuild:
130
+
131
+ ```bash
132
+ cd packages/cli
133
+ pnpm run generate
134
+ pnpm run build
135
+ ```
136
+
137
+ Then commit the updated `src/commands/generated/index.ts` (if the codegen script overwrites it) or the generated output as documented in the script.
138
+
139
+ ## API spec
140
+
141
+ The CLI targets the API described in the repository at `apps/api/spec/openapi.yaml`, and the same API is exposed at `https://api.usemonoid.com` (and `/openapi.json` for the spec).
142
+
143
+ ## Agent use
144
+
145
+ See **SKILL.md** in this package for a concise description of the CLI for AI agents: command pattern, auth, output, and `monoid setup-agent`.
package/SKILL.md ADDED
@@ -0,0 +1,67 @@
1
+ # MONOid CLI – Agent skill
2
+
3
+ The MONOid CLI lets AI agents and scripts interact with the MONOid REST API from the terminal. Use it to list and manage organisations, containers, projects, tasks, routine blocks, reviews, and daily notes.
4
+
5
+ ## Command pattern
6
+
7
+ Every resource follows the same subcommand pattern. Resources use **kebab-case**: `routine-blocks`, `daily-notes`.
8
+
9
+ ```bash
10
+ monoid <resource> list [--filters...]
11
+ monoid <resource> get <id>
12
+ monoid <resource> create [--fields...]
13
+ monoid <resource> update <id> [--fields...]
14
+ monoid <resource> delete <id>
15
+ ```
16
+
17
+ Resources: `organisations`, `containers`, `projects`, `tasks`, `routine-blocks`, `reviews`, `daily-notes`.
18
+
19
+ For **daily-notes**, `get` / `update` / `delete` use `<date>` (YYYY-MM-DD) instead of `<id>`.
20
+
21
+ ## Authentication
22
+
23
+ 1. **`--token <token>`** – Pass the API key on any command (overrides env and config).
24
+ 2. **`MONOID_API_TOKEN`** – Environment variable (recommended for agents and CI).
25
+ 3. **`~/.config/monoid/credentials.json`** – Persisted via `monoid login` (interactive use).
26
+
27
+ Use **Bearer** API keys with prefix `mo_` (create in MONOid Settings > API Keys). Scopes: `read`, `write`, `delete`.
28
+
29
+ **Recommendation for agents:** Call `monoid whoami` first to confirm the token is valid before running other commands.
30
+
31
+ ## Output
32
+
33
+ - **`--output json | table | csv`** (short `-o`). Default: **table** when stdout is a TTY, **json** when not (e.g. piped or called by an agent).
34
+ - Success: list commands return a `data` array; get/create/update return a `data` object.
35
+ - Errors: same format as success but with `{ "error": true, "status": <code>, "code": "<UPPER_SNAKE>", "message": "..." }`. Process exits with non-zero (1 for auth/validation, 2 for server/network).
36
+
37
+ ## Convenience commands
38
+
39
+ - **`monoid whoami`** – Verify auth; returns user id, scopes, and optional token expiry.
40
+ - **`monoid status`** – One-shot summary: org count, container count, active projects, tasks by bucket, today’s routine blocks, reviews.
41
+ - **`monoid calendar <week|day> [--date YYYY-MM-DD]`** – Items for the period (routines, tasks, reviews). Use `--routines`, `--tasks`, `--reviews` to include or omit sections.
42
+
43
+ ## Agent setup
44
+
45
+ Run **`monoid setup-agent`** for instructions and a snippet to set `MONOID_API_TOKEN` and optional `MONOID_API_BASE_URL`, and to confirm auth with `monoid whoami`.
46
+
47
+ ## List filters and create/update body
48
+
49
+ - Every **list** command supports `--limit <n>` and `--offset <n>`, plus resource-specific filters (e.g. `--project-id`, `--container-id`, `--bucket`, `--status`, `--date`, `--type`) in kebab-case.
50
+ - **Create** and **update** accept per-field flags (e.g. `--title`, `--bucket`) and/or **`--body <json>`**. Body and flags are merged; flags override body when both are set.
51
+ - **Create** supports **`--if-not-exists`**: if a matching resource exists (e.g. same date for daily-notes, same name for containers), the existing resource is returned instead of creating a duplicate.
52
+
53
+ ## Bulk operations
54
+
55
+ - **Update** and **delete** support **`--ids id1,id2,...`** (or **`--dates date1,date2,...`** for daily-notes). Max 25 per invocation; up to 5 concurrent requests; 429 responses are retried with backoff (up to 3 retries per ID).
56
+
57
+ ## Base URL
58
+
59
+ Default: `https://api.usemonoid.com`. Override with **`--base-url <url>`** or **`MONOID_API_BASE_URL`**.
60
+
61
+ ## Install and run
62
+
63
+ ```bash
64
+ npm install -g @usemonoid/cli
65
+ # or
66
+ npx @usemonoid/cli whoami
67
+ ```
@@ -0,0 +1,20 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ clearBaseUrl,
4
+ clearCredentials,
5
+ getCredentialsPath,
6
+ resolveStoredBaseUrl,
7
+ resolveToken,
8
+ saveBaseUrl,
9
+ saveToken
10
+ } from "./chunk-JXB3ZJ2U.js";
11
+ export {
12
+ clearBaseUrl,
13
+ clearCredentials,
14
+ getCredentialsPath,
15
+ resolveStoredBaseUrl,
16
+ resolveToken,
17
+ saveBaseUrl,
18
+ saveToken
19
+ };
20
+ //# sourceMappingURL=auth-3IBMFEDD.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
@@ -0,0 +1,16 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ createProgram,
4
+ createProgramForCLI,
5
+ getProgramForDocs,
6
+ syncRootOptsFromCommand
7
+ } from "./chunk-WXPCMBW6.js";
8
+ import "./chunk-GEPWV6MY.js";
9
+ import "./chunk-JXB3ZJ2U.js";
10
+ export {
11
+ createProgram,
12
+ createProgramForCLI,
13
+ getProgramForDocs,
14
+ syncRootOptsFromCommand
15
+ };
16
+ //# sourceMappingURL=build-program.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
@@ -0,0 +1,173 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ resolveStoredBaseUrl
4
+ } from "./chunk-JXB3ZJ2U.js";
5
+
6
+ // src/lib/client.ts
7
+ var DEFAULT_BASE_URL = "https://api.usemonoid.com";
8
+ function getBaseUrl(override) {
9
+ const env = process.env.MONOID_API_BASE_URL;
10
+ const stored = resolveStoredBaseUrl();
11
+ const base = override ?? env ?? stored ?? DEFAULT_BASE_URL;
12
+ return base.replace(/\/$/, "");
13
+ }
14
+ async function apiRequest(options) {
15
+ const base = getBaseUrl(options.baseUrl);
16
+ const url = new URL(`/api/v1/${options.path.replace(/^\//, "")}`, base);
17
+ if (options.query) {
18
+ for (const [k, v] of Object.entries(options.query)) {
19
+ if (v !== void 0 && v !== "") url.searchParams.set(k, String(v));
20
+ }
21
+ }
22
+ const headers = {
23
+ Authorization: `Bearer ${options.token}`,
24
+ "Content-Type": "application/json"
25
+ };
26
+ const init = {
27
+ method: options.method,
28
+ headers
29
+ };
30
+ if (options.body !== void 0 && options.method !== "GET") {
31
+ init.body = JSON.stringify(options.body);
32
+ }
33
+ let res;
34
+ try {
35
+ res = await fetch(url.toString(), init);
36
+ } catch (err) {
37
+ const message = err instanceof Error ? err.message : String(err);
38
+ return { ok: false, status: 0, error: message };
39
+ }
40
+ let data;
41
+ const text = await res.text();
42
+ if (text) {
43
+ try {
44
+ data = JSON.parse(text);
45
+ } catch {
46
+ data = void 0;
47
+ }
48
+ }
49
+ return {
50
+ ok: res.ok,
51
+ status: res.status,
52
+ data,
53
+ error: !res.ok && typeof data?.error === "string" ? data.error : void 0,
54
+ headers: res.headers
55
+ };
56
+ }
57
+
58
+ // src/lib/output.ts
59
+ function getDefaultOutputFormat() {
60
+ return process.stdout.isTTY ? "table" : "json";
61
+ }
62
+ function formatErrorPayload(status, message) {
63
+ const code = statusToCode(status);
64
+ return { error: true, status, code, message };
65
+ }
66
+ function statusToCode(status) {
67
+ if (status === 401) return "UNAUTHORIZED";
68
+ if (status === 403) return "FORBIDDEN";
69
+ if (status === 404) return "NOT_FOUND";
70
+ if (status === 400) return "BAD_REQUEST";
71
+ if (status === 429) return "TOO_MANY_REQUESTS";
72
+ if (status >= 500) return "SERVER_ERROR";
73
+ return "ERROR";
74
+ }
75
+ function printOutput(format, data, isError = false) {
76
+ if (format === "json") {
77
+ const out = Array.isArray(data) ? { data } : typeof data === "object" && data !== null && "data" in data ? data : { data };
78
+ console.log(JSON.stringify(out, null, 0));
79
+ return;
80
+ }
81
+ if (format === "table") {
82
+ printTable(data);
83
+ return;
84
+ }
85
+ if (format === "csv") {
86
+ printCsv(data);
87
+ return;
88
+ }
89
+ console.log(JSON.stringify(data));
90
+ }
91
+ function getRows(data) {
92
+ if (Array.isArray(data)) return data;
93
+ if (typeof data === "object" && data !== null && "data" in data) {
94
+ const d = data.data;
95
+ if (Array.isArray(d)) return d;
96
+ if (typeof d === "object" && d !== null) return [d];
97
+ }
98
+ if (typeof data === "object" && data !== null && !Array.isArray(data)) {
99
+ return [data];
100
+ }
101
+ return [];
102
+ }
103
+ function getColumns(rows) {
104
+ const set = /* @__PURE__ */ new Set();
105
+ for (const row of rows) {
106
+ for (const k of Object.keys(row)) set.add(k);
107
+ }
108
+ return [...set].sort();
109
+ }
110
+ function printTable(data) {
111
+ const rows = getRows(data);
112
+ if (rows.length === 0) {
113
+ console.log("(no data)");
114
+ return;
115
+ }
116
+ const cols = getColumns(rows);
117
+ const widths = cols.map((c) => Math.max(c.length, 8));
118
+ for (const row of rows) {
119
+ for (let i = 0; i < cols.length; i++) {
120
+ const col = cols[i];
121
+ const val = row[col];
122
+ const s = val === null || val === void 0 ? "" : String(val);
123
+ if (s.length > widths[i]) widths[i] = Math.min(s.length, 40);
124
+ }
125
+ }
126
+ const header = cols.map((c, i) => c.padEnd(widths[i])).join(" ");
127
+ console.log(header);
128
+ console.log(cols.map((_, i) => "-".repeat(widths[i])).join(" "));
129
+ for (const row of rows) {
130
+ const line = cols.map((c, i) => {
131
+ const val = row[c];
132
+ const s = val === null || val === void 0 ? "" : String(val);
133
+ return s.slice(0, widths[i]).padEnd(widths[i]);
134
+ }).join(" ");
135
+ console.log(line);
136
+ }
137
+ }
138
+ function printCsv(data) {
139
+ const rows = getRows(data);
140
+ if (rows.length === 0) {
141
+ console.log("");
142
+ return;
143
+ }
144
+ const cols = getColumns(rows);
145
+ const escape = (v) => {
146
+ const s = v === null || v === void 0 ? "" : String(v);
147
+ if (s.includes(",") || s.includes('"') || s.includes("\n")) {
148
+ return `"${s.replace(/"/g, '""')}"`;
149
+ }
150
+ return s;
151
+ };
152
+ console.log(cols.join(","));
153
+ for (const row of rows) {
154
+ console.log(cols.map((c) => escape(row[c])).join(","));
155
+ }
156
+ }
157
+ function printStructuredError(format, payload) {
158
+ if (format === "json") {
159
+ console.log(JSON.stringify(payload));
160
+ return;
161
+ }
162
+ console.error(`Error ${payload.status} (${payload.code}): ${payload.message}`);
163
+ }
164
+
165
+ export {
166
+ getBaseUrl,
167
+ apiRequest,
168
+ getDefaultOutputFormat,
169
+ formatErrorPayload,
170
+ printOutput,
171
+ printStructuredError
172
+ };
173
+ //# sourceMappingURL=chunk-GEPWV6MY.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/lib/client.ts","../src/lib/output.ts"],"sourcesContent":["import { resolveStoredBaseUrl } from \"./auth.js\";\n\nconst DEFAULT_BASE_URL = \"https://api.usemonoid.com\";\n\nexport function getBaseUrl(override?: string): string {\n const env = process.env.MONOID_API_BASE_URL;\n const stored = resolveStoredBaseUrl();\n const base = override ?? env ?? stored ?? DEFAULT_BASE_URL;\n return base.replace(/\\/$/, \"\");\n}\n\nexport interface RequestOptions {\n method: \"GET\" | \"POST\" | \"PATCH\" | \"DELETE\";\n path: string;\n token: string;\n baseUrl?: string;\n query?: Record<string, string | number | undefined>;\n body?: unknown;\n}\n\nexport interface ApiResponse<T = unknown> {\n ok: boolean;\n status: number;\n data?: T;\n error?: string;\n headers?: Headers;\n}\n\n/**\n * Call the MONOid REST API. Path should be relative to /api/v1 (e.g. \"me\", \"organisations\", \"organisations/123\").\n */\nexport async function apiRequest<T = unknown>(\n options: RequestOptions,\n): Promise<ApiResponse<T>> {\n const base = getBaseUrl(options.baseUrl);\n const url = new URL(`/api/v1/${options.path.replace(/^\\//, \"\")}`, base);\n if (options.query) {\n for (const [k, v] of Object.entries(options.query)) {\n if (v !== undefined && v !== \"\") url.searchParams.set(k, String(v));\n }\n }\n\n const headers: Record<string, string> = {\n Authorization: `Bearer ${options.token}`,\n \"Content-Type\": \"application/json\",\n };\n\n const init: RequestInit = {\n method: options.method,\n headers,\n };\n if (\n options.body !== undefined &&\n options.method !== \"GET\"\n ) {\n init.body = JSON.stringify(options.body);\n }\n\n let res: Response;\n try {\n res = await fetch(url.toString(), init);\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n return { ok: false, status: 0, error: message };\n }\n\n let data: T | undefined;\n const text = await res.text();\n if (text) {\n try {\n data = JSON.parse(text) as T;\n } catch {\n data = undefined;\n }\n }\n\n return {\n ok: res.ok,\n status: res.status,\n data,\n error: !res.ok && typeof (data as { error?: string })?.error === \"string\"\n ? (data as { error: string }).error\n : undefined,\n headers: res.headers,\n };\n}\n","export type OutputFormat = \"json\" | \"table\" | \"csv\";\n\nexport function getDefaultOutputFormat(): OutputFormat {\n return process.stdout.isTTY ? \"table\" : \"json\";\n}\n\nexport interface ErrorPayload {\n error: true;\n status: number;\n code: string;\n message: string;\n}\n\nexport function formatErrorPayload(status: number, message: string): ErrorPayload {\n const code = statusToCode(status);\n return { error: true, status, code, message };\n}\n\nfunction statusToCode(status: number): string {\n if (status === 401) return \"UNAUTHORIZED\";\n if (status === 403) return \"FORBIDDEN\";\n if (status === 404) return \"NOT_FOUND\";\n if (status === 400) return \"BAD_REQUEST\";\n if (status === 429) return \"TOO_MANY_REQUESTS\";\n if (status >= 500) return \"SERVER_ERROR\";\n return \"ERROR\";\n}\n\n/**\n * Print result to stdout according to format. For list responses, pass the data array.\n * For single resource, pass the data object.\n */\nexport function printOutput(\n format: OutputFormat,\n data: unknown[] | Record<string, unknown>,\n isError = false,\n): void {\n if (format === \"json\") {\n const out = Array.isArray(data)\n ? { data }\n : typeof data === \"object\" && data !== null && \"data\" in data\n ? data\n : { data };\n console.log(JSON.stringify(out, null, 0));\n return;\n }\n if (format === \"table\") {\n printTable(data);\n return;\n }\n if (format === \"csv\") {\n printCsv(data);\n return;\n }\n console.log(JSON.stringify(data));\n}\n\nfunction getRows(data: unknown[] | Record<string, unknown>): Record<string, unknown>[] {\n if (Array.isArray(data)) return data as Record<string, unknown>[];\n if (typeof data === \"object\" && data !== null && \"data\" in data) {\n const d = (data as { data: unknown }).data;\n if (Array.isArray(d)) return d as Record<string, unknown>[];\n if (typeof d === \"object\" && d !== null) return [d as Record<string, unknown>];\n }\n if (typeof data === \"object\" && data !== null && !Array.isArray(data)) {\n return [data as Record<string, unknown>];\n }\n return [];\n}\n\nfunction getColumns(rows: Record<string, unknown>[]): string[] {\n const set = new Set<string>();\n for (const row of rows) {\n for (const k of Object.keys(row)) set.add(k);\n }\n return [...set].sort();\n}\n\nfunction printTable(data: unknown[] | Record<string, unknown>): void {\n const rows = getRows(data);\n if (rows.length === 0) {\n console.log(\"(no data)\");\n return;\n }\n const cols = getColumns(rows);\n const widths = cols.map((c) => Math.max(c.length, 8));\n for (const row of rows) {\n for (let i = 0; i < cols.length; i++) {\n const col = cols[i]!;\n const val = row[col];\n const s = val === null || val === undefined ? \"\" : String(val);\n if (s.length > widths[i]!) widths[i] = Math.min(s.length, 40);\n }\n }\n const header = cols.map((c, i) => c.padEnd(widths[i]!)).join(\" \");\n console.log(header);\n console.log(cols.map((_, i) => \"-\".repeat(widths[i]!)).join(\" \"));\n for (const row of rows) {\n const line = cols\n .map((c, i) => {\n const val = row[c];\n const s = val === null || val === undefined ? \"\" : String(val);\n return s.slice(0, widths[i]!).padEnd(widths[i]!);\n })\n .join(\" \");\n console.log(line);\n }\n}\n\nfunction printCsv(data: unknown[] | Record<string, unknown>): void {\n const rows = getRows(data);\n if (rows.length === 0) {\n console.log(\"\");\n return;\n }\n const cols = getColumns(rows);\n const escape = (v: unknown): string => {\n const s = v === null || v === undefined ? \"\" : String(v);\n if (s.includes(\",\") || s.includes('\"') || s.includes(\"\\n\")) {\n return `\"${s.replace(/\"/g, '\"\"')}\"`;\n }\n return s;\n };\n console.log(cols.join(\",\"));\n for (const row of rows) {\n console.log(cols.map((c) => escape(row[c])).join(\",\"));\n }\n}\n\nexport function printStructuredError(format: OutputFormat, payload: ErrorPayload): void {\n if (format === \"json\") {\n console.log(JSON.stringify(payload));\n return;\n }\n console.error(`Error ${payload.status} (${payload.code}): ${payload.message}`);\n}\n"],"mappings":"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","names":[]}
@@ -0,0 +1,76 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/lib/auth.ts
4
+ import { readFileSync, mkdirSync, writeFileSync, existsSync } from "fs";
5
+ import { join } from "path";
6
+ import { homedir } from "os";
7
+ var CONFIG_DIR = join(homedir(), ".config", "monoid");
8
+ var CREDENTIALS_FILE = join(CONFIG_DIR, "credentials.json");
9
+ function readCredentials() {
10
+ try {
11
+ const raw = readFileSync(CREDENTIALS_FILE, "utf-8");
12
+ return JSON.parse(raw);
13
+ } catch {
14
+ return {};
15
+ }
16
+ }
17
+ function writeCredentials(data) {
18
+ mkdirSync(CONFIG_DIR, { recursive: true });
19
+ writeFileSync(
20
+ CREDENTIALS_FILE,
21
+ JSON.stringify(data, null, 0),
22
+ "utf-8"
23
+ );
24
+ }
25
+ function resolveToken(explicitToken) {
26
+ if (explicitToken?.trim()) return explicitToken.trim();
27
+ const env = process.env.MONOID_API_TOKEN;
28
+ if (env?.trim()) return env.trim();
29
+ const data = readCredentials();
30
+ if (data.token?.trim()) return data.token.trim();
31
+ return null;
32
+ }
33
+ function saveToken(token) {
34
+ const current = readCredentials();
35
+ writeCredentials({ ...current, token });
36
+ }
37
+ function resolveStoredBaseUrl() {
38
+ const data = readCredentials();
39
+ return data.baseUrl?.trim() ? data.baseUrl.trim() : null;
40
+ }
41
+ function saveBaseUrl(baseUrl) {
42
+ const current = readCredentials();
43
+ writeCredentials({ ...current, baseUrl });
44
+ }
45
+ function clearBaseUrl() {
46
+ const current = readCredentials();
47
+ if (!current.baseUrl) return;
48
+ const next = { ...current };
49
+ delete next.baseUrl;
50
+ writeCredentials(next);
51
+ }
52
+ function clearCredentials() {
53
+ try {
54
+ if (existsSync(CREDENTIALS_FILE)) {
55
+ const current = readCredentials();
56
+ const next = { ...current };
57
+ delete next.token;
58
+ writeCredentials(next);
59
+ }
60
+ } catch {
61
+ }
62
+ }
63
+ function getCredentialsPath() {
64
+ return CREDENTIALS_FILE;
65
+ }
66
+
67
+ export {
68
+ resolveToken,
69
+ saveToken,
70
+ resolveStoredBaseUrl,
71
+ saveBaseUrl,
72
+ clearBaseUrl,
73
+ clearCredentials,
74
+ getCredentialsPath
75
+ };
76
+ //# sourceMappingURL=chunk-JXB3ZJ2U.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/lib/auth.ts"],"sourcesContent":["import { readFileSync, mkdirSync, writeFileSync, existsSync } from \"fs\";\nimport { join } from \"path\";\nimport { homedir } from \"os\";\n\nconst CONFIG_DIR = join(homedir(), \".config\", \"monoid\");\nconst CREDENTIALS_FILE = join(CONFIG_DIR, \"credentials.json\");\n\nexport interface CredentialsFile {\n token?: string;\n baseUrl?: string;\n}\n\nfunction readCredentials(): CredentialsFile {\n try {\n const raw = readFileSync(CREDENTIALS_FILE, \"utf-8\");\n return JSON.parse(raw) as CredentialsFile;\n } catch {\n return {};\n }\n}\n\nfunction writeCredentials(data: CredentialsFile): void {\n mkdirSync(CONFIG_DIR, { recursive: true });\n writeFileSync(\n CREDENTIALS_FILE,\n JSON.stringify(data, null, 0),\n \"utf-8\",\n );\n}\n\n/**\n * Resolve API token in order: explicit token > MONOID_API_TOKEN > credentials file.\n */\nexport function resolveToken(explicitToken?: string): string | null {\n if (explicitToken?.trim()) return explicitToken.trim();\n const env = process.env.MONOID_API_TOKEN;\n if (env?.trim()) return env.trim();\n const data = readCredentials();\n if (data.token?.trim()) return data.token.trim();\n return null;\n}\n\n/**\n * Persist token to credentials file (for monoid login).\n */\nexport function saveToken(token: string): void {\n const current = readCredentials();\n writeCredentials({ ...current, token });\n}\n\nexport function resolveStoredBaseUrl(): string | null {\n const data = readCredentials();\n return data.baseUrl?.trim() ? data.baseUrl.trim() : null;\n}\n\nexport function saveBaseUrl(baseUrl: string): void {\n const current = readCredentials();\n writeCredentials({ ...current, baseUrl });\n}\n\nexport function clearBaseUrl(): void {\n const current = readCredentials();\n if (!current.baseUrl) return;\n const next = { ...current };\n delete next.baseUrl;\n writeCredentials(next);\n}\n\n/**\n * Remove persisted API token (for monoid logout) while keeping other config.\n */\nexport function clearCredentials(): void {\n try {\n if (existsSync(CREDENTIALS_FILE)) {\n const current = readCredentials();\n const next = { ...current };\n delete next.token;\n writeCredentials(next);\n }\n } catch {\n // ignore\n }\n}\n\nexport function getCredentialsPath(): string {\n return CREDENTIALS_FILE;\n}\n"],"mappings":";;;AAAA,SAAS,cAAc,WAAW,eAAe,kBAAkB;AACnE,SAAS,YAAY;AACrB,SAAS,eAAe;AAExB,IAAM,aAAa,KAAK,QAAQ,GAAG,WAAW,QAAQ;AACtD,IAAM,mBAAmB,KAAK,YAAY,kBAAkB;AAO5D,SAAS,kBAAmC;AAC1C,MAAI;AACF,UAAM,MAAM,aAAa,kBAAkB,OAAO;AAClD,WAAO,KAAK,MAAM,GAAG;AAAA,EACvB,QAAQ;AACN,WAAO,CAAC;AAAA,EACV;AACF;AAEA,SAAS,iBAAiB,MAA6B;AACrD,YAAU,YAAY,EAAE,WAAW,KAAK,CAAC;AACzC;AAAA,IACE;AAAA,IACA,KAAK,UAAU,MAAM,MAAM,CAAC;AAAA,IAC5B;AAAA,EACF;AACF;AAKO,SAAS,aAAa,eAAuC;AAClE,MAAI,eAAe,KAAK,EAAG,QAAO,cAAc,KAAK;AACrD,QAAM,MAAM,QAAQ,IAAI;AACxB,MAAI,KAAK,KAAK,EAAG,QAAO,IAAI,KAAK;AACjC,QAAM,OAAO,gBAAgB;AAC7B,MAAI,KAAK,OAAO,KAAK,EAAG,QAAO,KAAK,MAAM,KAAK;AAC/C,SAAO;AACT;AAKO,SAAS,UAAU,OAAqB;AAC7C,QAAM,UAAU,gBAAgB;AAChC,mBAAiB,EAAE,GAAG,SAAS,MAAM,CAAC;AACxC;AAEO,SAAS,uBAAsC;AACpD,QAAM,OAAO,gBAAgB;AAC7B,SAAO,KAAK,SAAS,KAAK,IAAI,KAAK,QAAQ,KAAK,IAAI;AACtD;AAEO,SAAS,YAAY,SAAuB;AACjD,QAAM,UAAU,gBAAgB;AAChC,mBAAiB,EAAE,GAAG,SAAS,QAAQ,CAAC;AAC1C;AAEO,SAAS,eAAqB;AACnC,QAAM,UAAU,gBAAgB;AAChC,MAAI,CAAC,QAAQ,QAAS;AACtB,QAAM,OAAO,EAAE,GAAG,QAAQ;AAC1B,SAAO,KAAK;AACZ,mBAAiB,IAAI;AACvB;AAKO,SAAS,mBAAyB;AACvC,MAAI;AACF,QAAI,WAAW,gBAAgB,GAAG;AAChC,YAAM,UAAU,gBAAgB;AAChC,YAAM,OAAO,EAAE,GAAG,QAAQ;AAC1B,aAAO,KAAK;AACZ,uBAAiB,IAAI;AAAA,IACvB;AAAA,EACF,QAAQ;AAAA,EAER;AACF;AAEO,SAAS,qBAA6B;AAC3C,SAAO;AACT;","names":[]}