@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 +21 -0
- package/README.md +92 -0
- package/dist/bin/dcli.d.ts +2 -0
- package/dist/bin/dcli.js +276 -0
- package/dist/client.d.ts +68 -0
- package/dist/client.js +153 -0
- package/dist/config.d.ts +8 -0
- package/dist/config.js +38 -0
- package/package.json +45 -0
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
|
package/dist/bin/dcli.js
ADDED
|
@@ -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
|
+
});
|
package/dist/client.d.ts
ADDED
|
@@ -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
|
+
}
|
package/dist/config.d.ts
ADDED
|
@@ -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
|
+
}
|