@dayofweek/dcli 1.0.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 Day of Week
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,92 @@
1
+ # @dayofweek/dcli
2
+
3
+ CLI for the [Day of Week](https://dayofweek.com) AgTech platform.
4
+
5
+ Read your organization's data and submit proposals for human review — nothing changes until you approve it.
6
+
7
+ ## Install
8
+
9
+ ```bash
10
+ npm install -g @dayofweek/dcli
11
+ ```
12
+
13
+ ## Quick start
14
+
15
+ ```bash
16
+ # Authenticate
17
+ dcli auth set-token <your-token>
18
+
19
+ # Browse your entities
20
+ dcli read entities --json
21
+
22
+ # Submit a proposal
23
+ dcli agent propose --op create --table hierarchyEntities \
24
+ --title "New Farm" --source "my-agent" \
25
+ --parent <parentEntityId> --entity-type Farm \
26
+ --file payload.json
27
+
28
+ # Check proposal status
29
+ dcli agent proposals --status pending
30
+ ```
31
+
32
+ ## Agent integration
33
+
34
+ ```bash
35
+ # Install the Agent Skill for AI agents
36
+ dcli skill install
37
+
38
+ # Works with OpenClaw, Claude Code, Cursor, VS Code Copilot,
39
+ # Gemini CLI, Goose, OpenHands, and 25+ others
40
+ ```
41
+
42
+ The skill follows the open [Agent Skills](https://agentskills.io) standard. After `dcli skill install`, any compatible agent on the machine discovers it automatically.
43
+
44
+ ## Commands
45
+
46
+ ### Authentication
47
+ - `dcli auth login` — Authenticate via browser
48
+ - `dcli auth set-token <token>` — Save a token locally
49
+ - `dcli auth status` — Check token health
50
+ - `dcli auth devices` — List your agent tokens
51
+ - `dcli auth create-token <name>` — Create an agent token
52
+ - `dcli auth revoke <id>` — Revoke a token
53
+
54
+ ### Reading data
55
+ - `dcli read entities [--type Farm] [--parent <id>] [--limit 50]`
56
+ - `dcli read entity <entityId>`
57
+ - `dcli read produce [--entity <id>]`
58
+ - `dcli read contacts [--entity <id>]`
59
+
60
+ ### Proposals
61
+ - `dcli agent propose --op create --table <table> --title <title> [--file payload.json]`
62
+ - `dcli agent propose-batch --label <label> --file batch.json`
63
+ - `dcli agent proposals [--status pending]`
64
+ - `dcli agent show <proposalId>`
65
+
66
+ ### Agent Skill
67
+ - `dcli skill install` — Download skill (requires auth)
68
+ - `dcli skill update` — Update to latest
69
+ - `dcli skill status` — Check installed version
70
+
71
+ ## Configuration
72
+
73
+ dcli stores its config at `~/.config/dayofweek/dcli.json`.
74
+
75
+ Environment variables:
76
+ - `DCLI_AUTH_TOKEN` — Auth token (overrides stored token)
77
+ - `DCLI_API_URL` — Custom API base URL
78
+
79
+ ## REST API
80
+
81
+ dcli talks to the Day of Week platform via a REST API. You can also call it directly:
82
+
83
+ ```bash
84
+ curl -H "Authorization: Bearer dsk_xxx" \
85
+ "https://field.dayofweek.com/app/api/dcli/entities?type=Farm"
86
+ ```
87
+
88
+ See the [API schema](https://field.dayofweek.com/app/api/dcli/schema) for all endpoints.
89
+
90
+ ## License
91
+
92
+ MIT
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
@@ -0,0 +1,276 @@
1
+ #!/usr/bin/env node
2
+ import { Command } from "commander";
3
+ import { DayOfWeekClient } from "../client.js";
4
+ import { getToken, getApiUrl, saveConfig } from "../config.js";
5
+ import { readFileSync, existsSync, mkdirSync, writeFileSync } from "node:fs";
6
+ import { join, dirname } from "node:path";
7
+ import { homedir } from "node:os";
8
+ import { createInterface } from "node:readline/promises";
9
+ const program = new Command()
10
+ .name("dcli")
11
+ .description("CLI for the Day of Week AgTech platform")
12
+ .version("1.0.0")
13
+ .option("--token <token>", "Auth token (overrides DCLI_AUTH_TOKEN)")
14
+ .option("--api-url <url>", "API base URL (overrides DCLI_API_URL)")
15
+ .option("--json", "Output JSON (default for non-interactive use)");
16
+ function getClient() {
17
+ const opts = program.opts();
18
+ const token = opts.token ?? getToken();
19
+ const apiUrl = opts.apiUrl ?? getApiUrl();
20
+ return new DayOfWeekClient(token, apiUrl);
21
+ }
22
+ function output(data) {
23
+ console.log(JSON.stringify(data, null, 2));
24
+ }
25
+ // ── Auth Commands ────────────────────────────────────────────────────────────
26
+ const auth = program.command("auth").description("Authentication commands");
27
+ auth
28
+ .command("login")
29
+ .description("Authenticate via browser")
30
+ .action(async () => {
31
+ const apiUrl = program.opts().apiUrl ?? getApiUrl();
32
+ const baseUrl = apiUrl.replace("/api/dcli", "");
33
+ const authUrl = `${baseUrl}/dcli/auth`;
34
+ console.log(`Opening browser for authentication...`);
35
+ console.log(`If the browser doesn't open, visit: ${authUrl}`);
36
+ const open = (await import("open")).default;
37
+ await open(authUrl);
38
+ console.log("\nAfter authenticating, copy the token and run:");
39
+ console.log(" export DCLI_AUTH_TOKEN=<your-token>");
40
+ console.log(" # or");
41
+ console.log(" dcli auth set-token <your-token>");
42
+ });
43
+ auth
44
+ .command("set-token <token>")
45
+ .description("Save a token to local config")
46
+ .action((token) => {
47
+ saveConfig({ authToken: token });
48
+ console.log("Token saved to ~/.config/dayofweek/dcli.json");
49
+ });
50
+ auth
51
+ .command("status")
52
+ .description("Check token health")
53
+ .action(async () => {
54
+ try {
55
+ const client = getClient();
56
+ const result = await client.checkAuth();
57
+ output(result);
58
+ }
59
+ catch (err) {
60
+ console.error(`Auth check failed: ${err.message}`);
61
+ process.exit(1);
62
+ }
63
+ });
64
+ auth
65
+ .command("devices")
66
+ .description("List your agent tokens")
67
+ .action(async () => {
68
+ const client = getClient();
69
+ const devices = await client.listDevices();
70
+ output(devices);
71
+ });
72
+ auth
73
+ .command("create-token <name>")
74
+ .description("Create an agent token")
75
+ .action(async (name) => {
76
+ const client = getClient();
77
+ const result = await client.createDevice(name);
78
+ console.log("\nToken created — copy it now, it won't be shown again:\n");
79
+ console.log(` ${result.secret}\n`);
80
+ console.log("Set it as DCLI_AUTH_TOKEN in your agent's environment.");
81
+ });
82
+ auth
83
+ .command("revoke <deviceId>")
84
+ .description("Revoke an agent token")
85
+ .action(async (deviceId) => {
86
+ const client = getClient();
87
+ await client.revokeDevice(deviceId);
88
+ console.log("Token revoked.");
89
+ });
90
+ // ── Read Commands ────────────────────────────────────────────────────────────
91
+ const read = program.command("read").description("Read platform data");
92
+ read
93
+ .command("entities")
94
+ .description("List entities in your organization")
95
+ .option("--type <entityType>", "Filter by type (Farm, Producer, Restaurant, ...)")
96
+ .option("--parent <entityId>", "List children of an entity")
97
+ .option("--limit <count>", "Max results", parseInt)
98
+ .action(async (opts) => {
99
+ const client = getClient();
100
+ const result = await client.listEntities(opts);
101
+ output(result);
102
+ });
103
+ read
104
+ .command("entity <entityId>")
105
+ .description("Get entity details")
106
+ .action(async (entityId) => {
107
+ const client = getClient();
108
+ const result = await client.getEntity(entityId);
109
+ output(result);
110
+ });
111
+ read
112
+ .command("produce")
113
+ .description("List produce profiles")
114
+ .option("--entity <entityId>", "Filter by entity")
115
+ .option("--limit <count>", "Max results", parseInt)
116
+ .action(async (opts) => {
117
+ const client = getClient();
118
+ const result = await client.listProduce(opts);
119
+ output(result);
120
+ });
121
+ read
122
+ .command("contacts")
123
+ .description("List contacts and memberships")
124
+ .option("--entity <entityId>", "Filter by entity")
125
+ .option("--limit <count>", "Max results", parseInt)
126
+ .action(async (opts) => {
127
+ const client = getClient();
128
+ const result = await client.listContacts(opts);
129
+ output(result);
130
+ });
131
+ // ── Agent Commands ───────────────────────────────────────────────────────────
132
+ const agent = program.command("agent").description("Submit and view proposals");
133
+ agent
134
+ .command("propose")
135
+ .description("Submit a proposal for review")
136
+ .requiredOption("--op <operation>", "Operation: create, update, or delete")
137
+ .requiredOption("--table <table>", "Target table (e.g. hierarchyEntities)")
138
+ .requiredOption("--title <title>", "Human-readable title")
139
+ .option("--description <text>", "Reasoning/evidence")
140
+ .option("--source <agent>", "Agent identifier")
141
+ .option("--source-url <url>", "Evidence URL")
142
+ .option("--confidence <score>", "Confidence 0-1", parseFloat)
143
+ .option("--parent <entityId>", "Parent entity ID")
144
+ .option("--entity-type <type>", "Entity type (Farm, Producer, ...)")
145
+ .option("--file <path>", "Read payload from JSON file (- for stdin)")
146
+ .option("--payload <json>", "Inline JSON payload")
147
+ .action(async (opts) => {
148
+ let payload = opts.payload ? JSON.parse(opts.payload) : {};
149
+ if (opts.file) {
150
+ const content = opts.file === "-"
151
+ ? await readStdin()
152
+ : readFileSync(opts.file, "utf-8");
153
+ payload = JSON.parse(content);
154
+ }
155
+ const client = getClient();
156
+ const result = await client.submitProposal({
157
+ operation: opts.op,
158
+ targetTable: opts.table,
159
+ title: opts.title,
160
+ description: opts.description,
161
+ payload,
162
+ sourceAgent: opts.source,
163
+ sourceUrl: opts.sourceUrl,
164
+ confidence: opts.confidence,
165
+ proposedParentId: opts.parent,
166
+ proposedEntityType: opts.entityType,
167
+ });
168
+ output(result);
169
+ });
170
+ agent
171
+ .command("propose-batch")
172
+ .description("Submit multiple proposals")
173
+ .requiredOption("--label <label>", "Batch label")
174
+ .option("--source <agent>", "Agent identifier")
175
+ .requiredOption("--file <path>", "JSON file with proposals array (- for stdin)")
176
+ .action(async (opts) => {
177
+ const content = opts.file === "-"
178
+ ? await readStdin()
179
+ : readFileSync(opts.file, "utf-8");
180
+ const proposals = JSON.parse(content);
181
+ const client = getClient();
182
+ const result = await client.submitBatch({
183
+ batchLabel: opts.label,
184
+ sourceAgent: opts.source,
185
+ proposals: Array.isArray(proposals) ? proposals : [proposals],
186
+ });
187
+ output(result);
188
+ });
189
+ agent
190
+ .command("proposals")
191
+ .description("List proposals")
192
+ .option("--status <status>", "Filter: pending, approved, rejected, failed")
193
+ .option("--source <source>", "Filter: discovery, agent, manual")
194
+ .option("--limit <count>", "Max results", parseInt)
195
+ .action(async (opts) => {
196
+ const client = getClient();
197
+ const result = await client.listProposals(opts);
198
+ output(result);
199
+ });
200
+ agent
201
+ .command("show <proposalId>")
202
+ .description("Show proposal details")
203
+ .action(async (proposalId) => {
204
+ const client = getClient();
205
+ const result = await client.getProposal(proposalId);
206
+ output(result);
207
+ });
208
+ // ── Skill Commands ───────────────────────────────────────────────────────────
209
+ const skill = program.command("skill").description("Manage the Day of Week agent skill");
210
+ skill
211
+ .command("install")
212
+ .description("Install the agent skill (requires valid auth)")
213
+ .option("--dir <path>", "Custom install directory")
214
+ .action(async (opts) => {
215
+ const client = getClient();
216
+ const bundle = await client.getSkillBundle();
217
+ const targetDir = opts.dir ?? join(homedir(), ".agents", "skills", bundle.name);
218
+ let filesWritten = 0;
219
+ for (const file of bundle.files) {
220
+ const filePath = join(targetDir, file.path);
221
+ mkdirSync(dirname(filePath), { recursive: true });
222
+ writeFileSync(filePath, file.content, "utf-8");
223
+ filesWritten++;
224
+ }
225
+ console.log(`Installed ${filesWritten} files to ${targetDir}`);
226
+ console.log("Any compatible agent will discover the skill automatically.");
227
+ });
228
+ skill
229
+ .command("update")
230
+ .description("Update the skill to the latest version")
231
+ .option("--dir <path>", "Custom install directory")
232
+ .action(async (opts) => {
233
+ // Same as install — overwrites
234
+ const client = getClient();
235
+ const bundle = await client.getSkillBundle();
236
+ const targetDir = opts.dir ?? join(homedir(), ".agents", "skills", bundle.name);
237
+ let filesWritten = 0;
238
+ for (const file of bundle.files) {
239
+ const filePath = join(targetDir, file.path);
240
+ mkdirSync(dirname(filePath), { recursive: true });
241
+ writeFileSync(filePath, file.content, "utf-8");
242
+ filesWritten++;
243
+ }
244
+ console.log(`Updated ${filesWritten} files in ${targetDir}`);
245
+ });
246
+ skill
247
+ .command("status")
248
+ .description("Check if the skill is installed")
249
+ .option("--dir <path>", "Custom install directory")
250
+ .action(async (opts) => {
251
+ const dir = opts.dir ?? join(homedir(), ".agents", "skills", "dayofweek-platform");
252
+ const skillPath = join(dir, "SKILL.md");
253
+ if (!existsSync(skillPath)) {
254
+ console.log("Not installed. Run: dcli skill install");
255
+ process.exit(1);
256
+ }
257
+ const content = readFileSync(skillPath, "utf-8");
258
+ const versionMatch = content.match(/version:\s*"([^"]+)"/);
259
+ console.log(`Installed at: ${dir}`);
260
+ console.log(`Version: ${versionMatch?.[1] ?? "unknown"}`);
261
+ console.log("\nTo update: dcli skill update");
262
+ });
263
+ // ── Helpers ──────────────────────────────────────────────────────────────────
264
+ async function readStdin() {
265
+ const chunks = [];
266
+ const rl = createInterface({ input: process.stdin });
267
+ for await (const line of rl) {
268
+ chunks.push(line);
269
+ }
270
+ return chunks.join("\n");
271
+ }
272
+ // ── Run ──────────────────────────────────────────────────────────────────────
273
+ program.parseAsync(process.argv).catch((err) => {
274
+ console.error(err.message ?? err);
275
+ process.exit(1);
276
+ });
@@ -0,0 +1,68 @@
1
+ /**
2
+ * HTTP client for the Day of Week platform REST API.
3
+ * All calls go through the proxy at field.dayofweek.com/app/api/dcli.
4
+ */
5
+ export declare class ApiError extends Error {
6
+ status: number;
7
+ constructor(status: number, message: string);
8
+ }
9
+ export declare class DayOfWeekClient {
10
+ private baseUrl;
11
+ private token;
12
+ constructor(token: string, baseUrl?: string);
13
+ checkAuth(): Promise<{
14
+ status: string;
15
+ authenticated: boolean;
16
+ }>;
17
+ listDevices(): Promise<Array<{
18
+ _id: string;
19
+ name: string;
20
+ secretPreview: string;
21
+ lastUsedAt: number;
22
+ createdAt: number;
23
+ }>>;
24
+ createDevice(name: string): Promise<{
25
+ secret: string;
26
+ }>;
27
+ revokeDevice(deviceId: string): Promise<{
28
+ success: boolean;
29
+ }>;
30
+ listEntities(opts?: {
31
+ type?: string;
32
+ parent?: string;
33
+ limit?: number;
34
+ }): Promise<any[]>;
35
+ getEntity(entityId: string): Promise<any>;
36
+ listProduce(opts?: {
37
+ entity?: string;
38
+ limit?: number;
39
+ }): Promise<any[]>;
40
+ listContacts(opts?: {
41
+ entity?: string;
42
+ limit?: number;
43
+ }): Promise<any[]>;
44
+ listProposals(opts?: {
45
+ status?: string;
46
+ source?: string;
47
+ limit?: number;
48
+ }): Promise<any[]>;
49
+ getProposal(proposalId: string): Promise<any>;
50
+ submitProposal(proposal: Record<string, unknown>): Promise<any>;
51
+ submitBatch(batch: {
52
+ batchLabel: string;
53
+ sourceAgent?: string;
54
+ proposals: any[];
55
+ }): Promise<any>;
56
+ getSkillBundle(): Promise<{
57
+ name: string;
58
+ version: string;
59
+ files: Array<{
60
+ path: string;
61
+ content: string;
62
+ }>;
63
+ }>;
64
+ getSchema(): Promise<any>;
65
+ private get;
66
+ private post;
67
+ private delete;
68
+ }
package/dist/client.js ADDED
@@ -0,0 +1,153 @@
1
+ /**
2
+ * HTTP client for the Day of Week platform REST API.
3
+ * All calls go through the proxy at field.dayofweek.com/app/api/dcli.
4
+ */
5
+ const DEFAULT_BASE_URL = "https://field.dayofweek.com/app/api/dcli";
6
+ export class ApiError extends Error {
7
+ status;
8
+ constructor(status, message) {
9
+ super(message);
10
+ this.status = status;
11
+ this.name = "ApiError";
12
+ }
13
+ }
14
+ export class DayOfWeekClient {
15
+ baseUrl;
16
+ token;
17
+ constructor(token, baseUrl) {
18
+ this.token = token;
19
+ this.baseUrl = baseUrl ?? process.env.DCLI_API_URL ?? DEFAULT_BASE_URL;
20
+ }
21
+ // ── Auth ──────────────────────────────────────────────────────────────────
22
+ async checkAuth() {
23
+ return this.get("/auth/status");
24
+ }
25
+ async listDevices() {
26
+ return this.get("/auth/devices");
27
+ }
28
+ async createDevice(name) {
29
+ return this.post("/auth/devices", { name });
30
+ }
31
+ async revokeDevice(deviceId) {
32
+ return this.delete(`/auth/devices/${deviceId}`);
33
+ }
34
+ // ── Read ──────────────────────────────────────────────────────────────────
35
+ async listEntities(opts) {
36
+ const params = new URLSearchParams();
37
+ if (opts?.type)
38
+ params.set("type", opts.type);
39
+ if (opts?.parent)
40
+ params.set("parent", opts.parent);
41
+ if (opts?.limit)
42
+ params.set("limit", String(opts.limit));
43
+ const qs = params.toString();
44
+ return this.get(`/entities${qs ? `?${qs}` : ""}`);
45
+ }
46
+ async getEntity(entityId) {
47
+ return this.get(`/entities/${entityId}`);
48
+ }
49
+ async listProduce(opts) {
50
+ const params = new URLSearchParams();
51
+ if (opts?.entity)
52
+ params.set("entity", opts.entity);
53
+ if (opts?.limit)
54
+ params.set("limit", String(opts.limit));
55
+ const qs = params.toString();
56
+ return this.get(`/produce${qs ? `?${qs}` : ""}`);
57
+ }
58
+ async listContacts(opts) {
59
+ const params = new URLSearchParams();
60
+ if (opts?.entity)
61
+ params.set("entity", opts.entity);
62
+ if (opts?.limit)
63
+ params.set("limit", String(opts.limit));
64
+ const qs = params.toString();
65
+ return this.get(`/contacts${qs ? `?${qs}` : ""}`);
66
+ }
67
+ // ── Proposals ─────────────────────────────────────────────────────────────
68
+ async listProposals(opts) {
69
+ const params = new URLSearchParams();
70
+ if (opts?.status)
71
+ params.set("status", opts.status);
72
+ if (opts?.source)
73
+ params.set("source", opts.source);
74
+ if (opts?.limit)
75
+ params.set("limit", String(opts.limit));
76
+ const qs = params.toString();
77
+ return this.get(`/proposals${qs ? `?${qs}` : ""}`);
78
+ }
79
+ async getProposal(proposalId) {
80
+ return this.get(`/proposals/${proposalId}`);
81
+ }
82
+ async submitProposal(proposal) {
83
+ return this.post("/proposals", proposal);
84
+ }
85
+ async submitBatch(batch) {
86
+ return this.post("/proposals/batch", batch);
87
+ }
88
+ // ── Skill ─────────────────────────────────────────────────────────────────
89
+ async getSkillBundle() {
90
+ return this.get("/skill");
91
+ }
92
+ // ── Schema ────────────────────────────────────────────────────────────────
93
+ async getSchema() {
94
+ // Schema endpoint doesn't require auth
95
+ const res = await fetch(`${this.baseUrl}/schema`);
96
+ if (!res.ok)
97
+ throw new ApiError(res.status, await res.text());
98
+ return res.json();
99
+ }
100
+ // ── HTTP helpers ──────────────────────────────────────────────────────────
101
+ async get(path) {
102
+ const res = await fetch(`${this.baseUrl}${path}`, {
103
+ headers: { Authorization: `Bearer ${this.token}` },
104
+ });
105
+ if (!res.ok) {
106
+ const body = await res.text();
107
+ try {
108
+ const json = JSON.parse(body);
109
+ throw new ApiError(res.status, json.error ?? body);
110
+ }
111
+ catch (e) {
112
+ if (e instanceof ApiError)
113
+ throw e;
114
+ throw new ApiError(res.status, body);
115
+ }
116
+ }
117
+ return res.json();
118
+ }
119
+ async post(path, body) {
120
+ const res = await fetch(`${this.baseUrl}${path}`, {
121
+ method: "POST",
122
+ headers: {
123
+ Authorization: `Bearer ${this.token}`,
124
+ "Content-Type": "application/json",
125
+ },
126
+ body: JSON.stringify(body),
127
+ });
128
+ if (!res.ok) {
129
+ const text = await res.text();
130
+ try {
131
+ const json = JSON.parse(text);
132
+ throw new ApiError(res.status, json.error ?? text);
133
+ }
134
+ catch (e) {
135
+ if (e instanceof ApiError)
136
+ throw e;
137
+ throw new ApiError(res.status, text);
138
+ }
139
+ }
140
+ return res.json();
141
+ }
142
+ async delete(path) {
143
+ const res = await fetch(`${this.baseUrl}${path}`, {
144
+ method: "DELETE",
145
+ headers: { Authorization: `Bearer ${this.token}` },
146
+ });
147
+ if (!res.ok) {
148
+ const text = await res.text();
149
+ throw new ApiError(res.status, text);
150
+ }
151
+ return res.json();
152
+ }
153
+ }
@@ -0,0 +1,8 @@
1
+ export interface DcliConfig {
2
+ authToken?: string;
3
+ apiUrl?: string;
4
+ }
5
+ export declare function loadConfig(): DcliConfig;
6
+ export declare function saveConfig(updates: Partial<DcliConfig>): void;
7
+ export declare function getToken(): string;
8
+ export declare function getApiUrl(): string;
package/dist/config.js ADDED
@@ -0,0 +1,38 @@
1
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
2
+ import { join } from "node:path";
3
+ import { homedir } from "node:os";
4
+ const CONFIG_DIR = join(homedir(), ".config", "dayofweek");
5
+ const CONFIG_FILE = join(CONFIG_DIR, "dcli.json");
6
+ export function loadConfig() {
7
+ try {
8
+ if (existsSync(CONFIG_FILE)) {
9
+ return JSON.parse(readFileSync(CONFIG_FILE, "utf-8"));
10
+ }
11
+ }
12
+ catch {
13
+ // Ignore malformed config
14
+ }
15
+ return {};
16
+ }
17
+ export function saveConfig(updates) {
18
+ mkdirSync(CONFIG_DIR, { recursive: true });
19
+ const existing = loadConfig();
20
+ const merged = { ...existing, ...updates };
21
+ writeFileSync(CONFIG_FILE, JSON.stringify(merged, null, 2), "utf-8");
22
+ }
23
+ export function getToken() {
24
+ const token = process.env.DCLI_AUTH_TOKEN ??
25
+ process.env.DCLI_TOKEN ??
26
+ loadConfig().authToken;
27
+ if (!token) {
28
+ console.error("No auth token found.");
29
+ console.error("Set DCLI_AUTH_TOKEN or run: dcli auth login");
30
+ process.exit(1);
31
+ }
32
+ return token;
33
+ }
34
+ export function getApiUrl() {
35
+ return (process.env.DCLI_API_URL ??
36
+ loadConfig().apiUrl ??
37
+ "https://field.dayofweek.com/app/api/dcli");
38
+ }
package/package.json ADDED
@@ -0,0 +1,45 @@
1
+ {
2
+ "name": "@dayofweek/dcli",
3
+ "version": "1.0.0",
4
+ "description": "CLI for the Day of Week AgTech platform — read data and submit proposals for review",
5
+ "license": "MIT",
6
+ "type": "module",
7
+ "bin": {
8
+ "dcli": "dist/bin/dcli.js"
9
+ },
10
+ "files": [
11
+ "dist/"
12
+ ],
13
+ "engines": {
14
+ "node": ">=18"
15
+ },
16
+ "scripts": {
17
+ "build": "tsc",
18
+ "dev": "tsx src/bin/dcli.ts",
19
+ "prepublishOnly": "npm run build"
20
+ },
21
+ "dependencies": {
22
+ "commander": "^14.0.0",
23
+ "open": "^11.0.0"
24
+ },
25
+ "devDependencies": {
26
+ "tsx": "^4.21.0",
27
+ "typescript": "^5.9.0",
28
+ "@types/node": "^22.0.0"
29
+ },
30
+ "publishConfig": {
31
+ "access": "public"
32
+ },
33
+ "repository": {
34
+ "type": "git",
35
+ "url": "https://github.com/dayofweek/dcli.git"
36
+ },
37
+ "homepage": "https://github.com/dayofweek/dcli#readme",
38
+ "keywords": [
39
+ "dayofweek",
40
+ "agtech",
41
+ "cli",
42
+ "agent",
43
+ "food-supply-chain"
44
+ ]
45
+ }