@dayofweek/dcli 1.10.0 → 1.12.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/README.md +61 -0
- package/dist/bin/dcli.js +213 -7
- package/dist/bundle/dcli.cjs +234 -10
- package/dist/client.d.ts +93 -7
- package/dist/client.js +134 -3
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -153,6 +153,35 @@ dcli data get <dataset> --limit 100 --json
|
|
|
153
153
|
Responses are `{ dataset, total, truncated, rows }`. The CLI has no built-in
|
|
154
154
|
dataset names; new datasets appear in the listing without a CLI update.
|
|
155
155
|
|
|
156
|
+
## Time tracking (staff)
|
|
157
|
+
|
|
158
|
+
Hours registered against time projects. The server owns every rule — project
|
|
159
|
+
and participation periods, hour frames, self-financing, the reportable basis,
|
|
160
|
+
who may approve — and the CLI only carries JSON.
|
|
161
|
+
|
|
162
|
+
```bash
|
|
163
|
+
dcli time projects list --json
|
|
164
|
+
dcli time projects show <projectId> --json
|
|
165
|
+
dcli time projects upsert --file project.json # create, or update with projectId
|
|
166
|
+
|
|
167
|
+
dcli time entries list --project <id> --from 2026-06-01 --to 2026-09-30 --json
|
|
168
|
+
dcli time entries import --project <id> --file entries.json --dry-run
|
|
169
|
+
dcli time entries import --project <id> --file entries.json --approved
|
|
170
|
+
|
|
171
|
+
dcli time months list --json
|
|
172
|
+
dcli time months submit --project <id> --month 2026-08
|
|
173
|
+
dcli time months approve --project <id> --month 2026-08 --user-email someone@example.com
|
|
174
|
+
dcli time months return --project <id> --month 2026-08 --user-email ... --comment "..."
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
`entries import` is idempotent: each entry carries an import key (explicit, or
|
|
178
|
+
derived from person + date + activity + description), so re-running a file
|
|
179
|
+
updates rather than duplicates. Approved rows are locked and reported as
|
|
180
|
+
`skip_locked`; any invalid entry aborts the whole batch with nothing written.
|
|
181
|
+
Because real rows are written, the command refuses to run without `--dry-run`
|
|
182
|
+
first and `--approved` afterwards. Payload shapes are documented in the
|
|
183
|
+
`time-tracking` reference that `dcli skill install` fetches for staff.
|
|
184
|
+
|
|
156
185
|
## Feedback backlog
|
|
157
186
|
|
|
158
187
|
The customer feedback backlog that humans and coding agents work together.
|
|
@@ -188,6 +217,38 @@ customer is irreversible and outward-facing, so it cannot happen as a side
|
|
|
188
217
|
effect of reading the inbox — a person approves the exact text first, and
|
|
189
218
|
`--approved` records that they did.
|
|
190
219
|
|
|
220
|
+
## Cross-org reads (staff)
|
|
221
|
+
|
|
222
|
+
Admin tokens can read across every organization. Both listings are
|
|
223
|
+
cursor-paged: filters apply within each scanned page, so a page can be short
|
|
224
|
+
or empty while more rows remain — `--all` walks the pages for you.
|
|
225
|
+
|
|
226
|
+
```bash
|
|
227
|
+
dcli admin entities --category supply --all --json # every producer, every org
|
|
228
|
+
dcli admin entities --role producer --with-roles --json # first page, roles attached
|
|
229
|
+
dcli admin entities --search huseby --json
|
|
230
|
+
dcli admin entities --limit 500 --cursor <nextCursor> --json
|
|
231
|
+
|
|
232
|
+
dcli admin market-intel stats --json # the market intelligence register, counted
|
|
233
|
+
dcli admin market-intel list --type farm --region Agder --all --json
|
|
234
|
+
dcli admin market-intel list --bbox 57.95,6.35,59.70,9.40 --json
|
|
235
|
+
dcli admin market-intel get <id> --json
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
Entity rows carry `typeName` and `typeCategory` resolved from the entity's
|
|
239
|
+
type — count on those, not on the legacy `entityType` string. The market
|
|
240
|
+
intelligence register is a separate table from the entity hierarchy (every row
|
|
241
|
+
has coordinates; `linkedEntity` names the hierarchy entity it points at, if
|
|
242
|
+
any), so "how many producers do we know of" reads both.
|
|
243
|
+
|
|
244
|
+
An area's actors can be tied to the platform entity they are:
|
|
245
|
+
|
|
246
|
+
```bash
|
|
247
|
+
dcli brain actors list --area <areaId> --match --json # unlinked actors get name-matched candidates (admins)
|
|
248
|
+
dcli brain actors link --area <areaId> --actor <actorId> --entity <entityId>
|
|
249
|
+
dcli brain actors link --area <areaId> --actor <actorId> --unlink
|
|
250
|
+
```
|
|
251
|
+
|
|
191
252
|
## Legacy platform commands
|
|
192
253
|
|
|
193
254
|
Existing read/proposal workflows remain compatible:
|
package/dist/bin/dcli.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { Command } from "commander";
|
|
3
|
-
import { ApiError, DayOfWeekClient, toArrayBuffer } from "../client.js";
|
|
3
|
+
import { ApiError, DayOfWeekClient, toArrayBuffer, collectAdminPages } from "../client.js";
|
|
4
4
|
import { getToken, getApiUrl, saveConfig, loadConfig, saveCredential, deleteCredential } from "../config.js";
|
|
5
5
|
import { browserLogin } from "../auth/login.js";
|
|
6
6
|
import { parseBrainResource } from "../uri.js";
|
|
@@ -326,8 +326,29 @@ brainActors
|
|
|
326
326
|
.command("list")
|
|
327
327
|
.description("List an area's actors (active, sorted by name)")
|
|
328
328
|
.requiredOption("--area <areaId>", "Area id (from `brain list`)")
|
|
329
|
+
.option("--match", "Suggest matching platform entities for unlinked actors (admins)")
|
|
329
330
|
.action(async (opts) => {
|
|
330
|
-
output(await getClient().listBrainActors(opts.area));
|
|
331
|
+
output(await getClient().listBrainActors(opts.area, { match: opts.match }));
|
|
332
|
+
});
|
|
333
|
+
brainActors
|
|
334
|
+
.command("link")
|
|
335
|
+
.description("Link an actor to the platform entity it is (or --unlink)")
|
|
336
|
+
.requiredOption("--area <areaId>", "Area id (from `brain list`)")
|
|
337
|
+
.requiredOption("--actor <actorId>", "Actor id (from `brain actors list`)")
|
|
338
|
+
.option("--entity <entityId>", "Platform entity id (from `admin entities` or `read entities`)")
|
|
339
|
+
.option("--unlink", "Remove the current link")
|
|
340
|
+
.action(async (opts) => {
|
|
341
|
+
if (!opts.entity && !opts.unlink) {
|
|
342
|
+
throw new Error("Provide --entity <entityId> to link, or --unlink to remove the link");
|
|
343
|
+
}
|
|
344
|
+
if (opts.entity && opts.unlink) {
|
|
345
|
+
throw new Error("--entity and --unlink are mutually exclusive");
|
|
346
|
+
}
|
|
347
|
+
output(await getClient().linkBrainActor({
|
|
348
|
+
areaId: opts.area,
|
|
349
|
+
actorId: opts.actor,
|
|
350
|
+
entityId: opts.unlink ? null : opts.entity,
|
|
351
|
+
}));
|
|
331
352
|
});
|
|
332
353
|
brainActors
|
|
333
354
|
.command("add")
|
|
@@ -1330,6 +1351,127 @@ feedback
|
|
|
1330
1351
|
rejectedReason: opts.rejectedReason,
|
|
1331
1352
|
}));
|
|
1332
1353
|
});
|
|
1354
|
+
// ── Time tracking ────────────────────────────────────────────────────────────
|
|
1355
|
+
//
|
|
1356
|
+
// Hours per project. The server owns the rules (project and participation
|
|
1357
|
+
// periods, hour frames, self-financing, the reportable basis, approvals);
|
|
1358
|
+
// this group parses flags, reads JSON files and prints the response. The
|
|
1359
|
+
// only local rule is the irreversibility gate on import: real rows are
|
|
1360
|
+
// written, so --dry-run first, then --approved.
|
|
1361
|
+
const time = program.command("time").description("Hours per project: projects, entries, monthly sheets");
|
|
1362
|
+
const timeProjects = time.command("projects").description("Time projects: setup and overview");
|
|
1363
|
+
timeProjects
|
|
1364
|
+
.command("list")
|
|
1365
|
+
.description("List time projects with totals and frame usage")
|
|
1366
|
+
.option("--org <org>", "Organization slug or id (admin only)")
|
|
1367
|
+
.action(async (opts) => {
|
|
1368
|
+
output(await getClient().listTimeProjects(opts.org));
|
|
1369
|
+
});
|
|
1370
|
+
timeProjects
|
|
1371
|
+
.command("show <projectId>")
|
|
1372
|
+
.description("One project: setup, person×activity×month matrix, entries with computed split, monthly sheets")
|
|
1373
|
+
.option("--org <org>", "Organization slug or id (admin only)")
|
|
1374
|
+
.action(async (projectId, opts) => {
|
|
1375
|
+
output(await getClient().getTimeProject(projectId, opts.org));
|
|
1376
|
+
});
|
|
1377
|
+
timeProjects
|
|
1378
|
+
.command("upsert")
|
|
1379
|
+
.description("Create or update a time project from a JSON file (include projectId to update)")
|
|
1380
|
+
.requiredOption("--file <path>", "JSON payload with the project setup (- for stdin)")
|
|
1381
|
+
.option("--org <org>", "Organization slug or id (admin only)")
|
|
1382
|
+
.action(async (opts) => {
|
|
1383
|
+
const raw = opts.file === "-" ? readFileSync(0, "utf8") : readFileSync(opts.file, "utf8");
|
|
1384
|
+
const payload = JSON.parse(raw);
|
|
1385
|
+
if (!payload || typeof payload !== "object" || Array.isArray(payload)) {
|
|
1386
|
+
throw new Error("Payload must be a JSON object with the project setup");
|
|
1387
|
+
}
|
|
1388
|
+
output(await getClient().upsertTimeProject(payload, opts.org));
|
|
1389
|
+
});
|
|
1390
|
+
const timeEntries = time.command("entries").description("Registered hours");
|
|
1391
|
+
timeEntries
|
|
1392
|
+
.command("list")
|
|
1393
|
+
.description("Flat rows with the computed split (counted, self-financed, reportable, reasons)")
|
|
1394
|
+
.option("--project <projectId>", "Only this project")
|
|
1395
|
+
.option("--user <userId>", "Only this person (owners and admins)")
|
|
1396
|
+
.option("--from <YYYY-MM-DD>", "First work date")
|
|
1397
|
+
.option("--to <YYYY-MM-DD>", "Last work date")
|
|
1398
|
+
.option("--status <status>", "draft | submitted | approved")
|
|
1399
|
+
.option("--org <org>", "Organization slug or id (admin only)")
|
|
1400
|
+
.action(async (opts) => {
|
|
1401
|
+
output(await getClient().listTimeEntries({
|
|
1402
|
+
project: opts.project,
|
|
1403
|
+
user: opts.user,
|
|
1404
|
+
from: opts.from,
|
|
1405
|
+
to: opts.to,
|
|
1406
|
+
status: opts.status,
|
|
1407
|
+
org: opts.org,
|
|
1408
|
+
}));
|
|
1409
|
+
});
|
|
1410
|
+
timeEntries
|
|
1411
|
+
.command("import")
|
|
1412
|
+
.description("Idempotent batch import of hours into one project. Requires --dry-run or --approved")
|
|
1413
|
+
.requiredOption("--project <projectId>", "Target time project")
|
|
1414
|
+
.requiredOption("--file <path>", "JSON: { entries: [...] } or a bare array (- for stdin)")
|
|
1415
|
+
.option("--dry-run", "Validate and report what would happen, write nothing")
|
|
1416
|
+
.option("--approved", "The operator approved the dry-run result")
|
|
1417
|
+
.option("--org <org>", "Organization slug or id (admin only)")
|
|
1418
|
+
.action(async (opts) => {
|
|
1419
|
+
if (!opts.dryRun && !opts.approved) {
|
|
1420
|
+
throw new Error("Refusing to write: run with --dry-run first, show the operator the summary, " +
|
|
1421
|
+
"then repeat with --approved. This writes real hours, not proposals.");
|
|
1422
|
+
}
|
|
1423
|
+
const raw = opts.file === "-" ? readFileSync(0, "utf8") : readFileSync(opts.file, "utf8");
|
|
1424
|
+
const payload = JSON.parse(raw);
|
|
1425
|
+
const entries = Array.isArray(payload) ? payload : payload?.entries;
|
|
1426
|
+
if (!Array.isArray(entries) || entries.length === 0) {
|
|
1427
|
+
throw new Error("Payload needs { entries: [...] } with at least one entry");
|
|
1428
|
+
}
|
|
1429
|
+
output(await getClient().importTimeEntries({
|
|
1430
|
+
projectId: opts.project,
|
|
1431
|
+
entries,
|
|
1432
|
+
dryRun: Boolean(opts.dryRun),
|
|
1433
|
+
org: opts.org,
|
|
1434
|
+
}));
|
|
1435
|
+
});
|
|
1436
|
+
const timeMonths = time.command("months").description("Monthly sheets: submit, approve, return, reopen");
|
|
1437
|
+
timeMonths
|
|
1438
|
+
.command("list")
|
|
1439
|
+
.description("Months waiting for your approval (project owner), or every submitted month (admin)")
|
|
1440
|
+
.option("--org <org>", "Organization slug or id (admin only)")
|
|
1441
|
+
.action(async (opts) => {
|
|
1442
|
+
output(await getClient().listPendingTimeMonths(opts.org));
|
|
1443
|
+
});
|
|
1444
|
+
for (const action of ["submit", "approve", "return", "reopen"]) {
|
|
1445
|
+
const descriptions = {
|
|
1446
|
+
submit: "Submit a month for approval (your own; admins may pass --user-email)",
|
|
1447
|
+
approve: "Approve a submitted month — locks its rows (project owner or admin)",
|
|
1448
|
+
return: "Return a submitted month as drafts, --comment required (project owner or admin)",
|
|
1449
|
+
reopen: "Reopen an approved month for correction; it needs a new approval",
|
|
1450
|
+
};
|
|
1451
|
+
timeMonths
|
|
1452
|
+
.command(action)
|
|
1453
|
+
.description(descriptions[action])
|
|
1454
|
+
.requiredOption("--project <projectId>", "Time project")
|
|
1455
|
+
.requiredOption("--month <YYYY-MM>", "Month")
|
|
1456
|
+
.option("--user-email <email>", "Whose month (defaults to yourself)")
|
|
1457
|
+
.option("--user <userId>", "Whose month, by user id")
|
|
1458
|
+
.option("--comment <text>", "Comment (required for return)")
|
|
1459
|
+
.option("--org <org>", "Organization slug or id (admin only)")
|
|
1460
|
+
.action(async (opts) => {
|
|
1461
|
+
if (action === "return" && !opts.comment) {
|
|
1462
|
+
throw new Error("--comment is required when returning a month");
|
|
1463
|
+
}
|
|
1464
|
+
output(await getClient().actOnTimeMonth({
|
|
1465
|
+
projectId: opts.project,
|
|
1466
|
+
month: opts.month,
|
|
1467
|
+
action,
|
|
1468
|
+
userId: opts.user,
|
|
1469
|
+
userEmail: opts.userEmail,
|
|
1470
|
+
comment: opts.comment,
|
|
1471
|
+
org: opts.org,
|
|
1472
|
+
}));
|
|
1473
|
+
});
|
|
1474
|
+
}
|
|
1333
1475
|
// ── Admin Commands ───────────────────────────────────────────────────────────
|
|
1334
1476
|
//
|
|
1335
1477
|
// These are admin-only. They're registered as hidden subcommands when the
|
|
@@ -1349,22 +1491,86 @@ function registerAdminCommands() {
|
|
|
1349
1491
|
.description("Admin-only cross-org operations (DoW staff)");
|
|
1350
1492
|
admin
|
|
1351
1493
|
.command("entities")
|
|
1352
|
-
.description("List entities across all orgs with admin filters")
|
|
1353
|
-
.option("--type <entityType>", "Filter by
|
|
1494
|
+
.description("List entities across all orgs with admin filters (cursor-paged)")
|
|
1495
|
+
.option("--type <entityType>", "Filter by type name (farm, producer, restaurant, …)")
|
|
1496
|
+
.option("--category <category>", "Filter by type category (supply, demand, partner, financial, …)")
|
|
1497
|
+
.option("--role <role>", "Only entities holding an active role: customer, partner, investor, producer, market_intel")
|
|
1498
|
+
.option("--with-roles", "Attach each entity's active roles")
|
|
1354
1499
|
.option("--missing-location", "Only entities lacking metadata.places[].lat/lng")
|
|
1355
1500
|
.option("--search <query>", "Substring match on name")
|
|
1356
1501
|
.option("--org <slug>", "Restrict to a single org slug or ID")
|
|
1357
|
-
.option("--limit <count>", "
|
|
1502
|
+
.option("--limit <count>", "Rows scanned per page (default 200, max 2000)", parseInt)
|
|
1503
|
+
.option("--cursor <cursor>", "Continue from a previous page's nextCursor")
|
|
1504
|
+
.option("--all", "Walk every page and return all matches")
|
|
1358
1505
|
.action(async (opts) => {
|
|
1359
1506
|
const client = getClient();
|
|
1360
|
-
const
|
|
1507
|
+
const fetchPage = (cursor) => client.adminListEntities({
|
|
1361
1508
|
org: opts.org,
|
|
1362
1509
|
type: opts.type,
|
|
1510
|
+
category: opts.category,
|
|
1511
|
+
role: opts.role,
|
|
1512
|
+
withRoles: opts.withRoles,
|
|
1363
1513
|
missingLocation: opts.missingLocation,
|
|
1364
1514
|
search: opts.search,
|
|
1365
1515
|
limit: opts.limit,
|
|
1516
|
+
cursor,
|
|
1366
1517
|
});
|
|
1367
|
-
output(
|
|
1518
|
+
output(opts.all ? await collectAdminPages(fetchPage, opts.cursor) : await fetchPage(opts.cursor));
|
|
1519
|
+
});
|
|
1520
|
+
// The market intelligence register is a separate table from the entity
|
|
1521
|
+
// hierarchy: businesses recorded for research and prospecting, each with
|
|
1522
|
+
// coordinates. A platform-wide "how many do we know of" reads both.
|
|
1523
|
+
const marketIntel = admin
|
|
1524
|
+
.command("market-intel")
|
|
1525
|
+
.description("The market intelligence register — businesses recorded for research and prospecting");
|
|
1526
|
+
marketIntel
|
|
1527
|
+
.command("list")
|
|
1528
|
+
.description("List register rows (cursor-paged, newest first)")
|
|
1529
|
+
.option("--type <entityType>", "farm, restaurant, farm_shop, retail_store, distributor, processor, market, cooperative, supplier, customer, competitor, other")
|
|
1530
|
+
.option("--relationship <type>", "prospect, active_customer, supplier, partner, competitor, market_research, other")
|
|
1531
|
+
.option("--priority <priority>", "critical, high, medium, low, watching")
|
|
1532
|
+
.option("--stage <stage>", "Opportunity stage (research, qualified_lead, negotiation, …)")
|
|
1533
|
+
.option("--region <region>", "Exact match on region (case-insensitive)")
|
|
1534
|
+
.option("--search <query>", "Substring match on name")
|
|
1535
|
+
.option("--bbox <latMin,lngMin,latMax,lngMax>", "Only rows inside a lat/lng box")
|
|
1536
|
+
.option("--active", "Only active rows")
|
|
1537
|
+
.option("--inactive", "Only inactive rows")
|
|
1538
|
+
.option("--org <slug>", "Restrict to the owning org")
|
|
1539
|
+
.option("--limit <count>", "Rows scanned per page (default 200, max 2000)", parseInt)
|
|
1540
|
+
.option("--cursor <cursor>", "Continue from a previous page's nextCursor")
|
|
1541
|
+
.option("--all", "Walk every page and return all matches")
|
|
1542
|
+
.action(async (opts) => {
|
|
1543
|
+
if (opts.active && opts.inactive) {
|
|
1544
|
+
throw new Error("--active and --inactive are mutually exclusive");
|
|
1545
|
+
}
|
|
1546
|
+
const client = getClient();
|
|
1547
|
+
const fetchPage = (cursor) => client.adminListMarketIntel({
|
|
1548
|
+
org: opts.org,
|
|
1549
|
+
type: opts.type,
|
|
1550
|
+
relationship: opts.relationship,
|
|
1551
|
+
priority: opts.priority,
|
|
1552
|
+
stage: opts.stage,
|
|
1553
|
+
region: opts.region,
|
|
1554
|
+
search: opts.search,
|
|
1555
|
+
bbox: opts.bbox,
|
|
1556
|
+
active: opts.active ? true : opts.inactive ? false : undefined,
|
|
1557
|
+
limit: opts.limit,
|
|
1558
|
+
cursor,
|
|
1559
|
+
});
|
|
1560
|
+
output(opts.all ? await collectAdminPages(fetchPage, opts.cursor) : await fetchPage(opts.cursor));
|
|
1561
|
+
});
|
|
1562
|
+
marketIntel
|
|
1563
|
+
.command("get <id>")
|
|
1564
|
+
.description("One register record with its latest interactions")
|
|
1565
|
+
.action(async (id) => {
|
|
1566
|
+
output(await getClient().adminGetMarketIntel(id));
|
|
1567
|
+
});
|
|
1568
|
+
marketIntel
|
|
1569
|
+
.command("stats")
|
|
1570
|
+
.description("Counts by type, relationship, priority, stage, region and country")
|
|
1571
|
+
.option("--org <slug>", "Restrict to the owning org")
|
|
1572
|
+
.action(async (opts) => {
|
|
1573
|
+
output(await getClient().adminMarketIntelStats(opts.org));
|
|
1368
1574
|
});
|
|
1369
1575
|
admin
|
|
1370
1576
|
.command("proposals")
|
package/dist/bundle/dcli.cjs
CHANGED
|
@@ -4209,6 +4209,21 @@ var ApiError = class extends Error {
|
|
|
4209
4209
|
this.name = "ApiError";
|
|
4210
4210
|
}
|
|
4211
4211
|
};
|
|
4212
|
+
async function collectAdminPages(fetchPage, startCursor) {
|
|
4213
|
+
const results = [];
|
|
4214
|
+
let cursor = startCursor;
|
|
4215
|
+
let scanned = 0;
|
|
4216
|
+
let pages = 0;
|
|
4217
|
+
for (; ; ) {
|
|
4218
|
+
const page = await fetchPage(cursor);
|
|
4219
|
+
results.push(...page.results);
|
|
4220
|
+
scanned += page.scanned ?? page.results.length;
|
|
4221
|
+
pages += 1;
|
|
4222
|
+
if (page.isDone || !page.nextCursor) break;
|
|
4223
|
+
cursor = page.nextCursor;
|
|
4224
|
+
}
|
|
4225
|
+
return { results, total: results.length, scanned, isDone: true, nextCursor: null, truncated: false, pages };
|
|
4226
|
+
}
|
|
4212
4227
|
var DayOfWeekClient = class {
|
|
4213
4228
|
baseUrl;
|
|
4214
4229
|
token;
|
|
@@ -4427,12 +4442,43 @@ var DayOfWeekClient = class {
|
|
|
4427
4442
|
const params = new URLSearchParams();
|
|
4428
4443
|
if (opts?.org) params.set("org", opts.org);
|
|
4429
4444
|
if (opts?.type) params.set("type", opts.type);
|
|
4445
|
+
if (opts?.category) params.set("category", opts.category);
|
|
4446
|
+
if (opts?.role) params.set("role", opts.role);
|
|
4447
|
+
if (opts?.withRoles) params.set("with-roles", "1");
|
|
4430
4448
|
if (opts?.missingLocation) params.set("missing-location", "1");
|
|
4431
4449
|
if (opts?.search) params.set("search", opts.search);
|
|
4432
4450
|
if (opts?.limit) params.set("limit", String(opts.limit));
|
|
4451
|
+
if (opts?.cursor) params.set("cursor", opts.cursor);
|
|
4433
4452
|
const qs = params.toString();
|
|
4434
4453
|
return this.get(`/admin/entities${qs ? `?${qs}` : ""}`);
|
|
4435
4454
|
}
|
|
4455
|
+
// The market intelligence register: businesses recorded for research and
|
|
4456
|
+
// prospecting, each with coordinates — a separate table from the entity
|
|
4457
|
+
// hierarchy, so a platform-wide count has to read both.
|
|
4458
|
+
async adminListMarketIntel(opts) {
|
|
4459
|
+
const params = new URLSearchParams();
|
|
4460
|
+
if (opts?.org) params.set("org", opts.org);
|
|
4461
|
+
if (opts?.type) params.set("type", opts.type);
|
|
4462
|
+
if (opts?.relationship) params.set("relationship", opts.relationship);
|
|
4463
|
+
if (opts?.priority) params.set("priority", opts.priority);
|
|
4464
|
+
if (opts?.stage) params.set("stage", opts.stage);
|
|
4465
|
+
if (opts?.region) params.set("region", opts.region);
|
|
4466
|
+
if (opts?.search) params.set("search", opts.search);
|
|
4467
|
+
if (opts?.bbox) params.set("bbox", opts.bbox);
|
|
4468
|
+
if (opts?.active !== void 0) params.set("active", opts.active ? "1" : "0");
|
|
4469
|
+
if (opts?.limit) params.set("limit", String(opts.limit));
|
|
4470
|
+
if (opts?.cursor) params.set("cursor", opts.cursor);
|
|
4471
|
+
const qs = params.toString();
|
|
4472
|
+
return this.get(`/admin/market-intel${qs ? `?${qs}` : ""}`);
|
|
4473
|
+
}
|
|
4474
|
+
/** One register record in full, with its latest interactions. */
|
|
4475
|
+
async adminGetMarketIntel(id) {
|
|
4476
|
+
return this.get(`/admin/market-intel/${encodeURIComponent(id)}`);
|
|
4477
|
+
}
|
|
4478
|
+
/** Counts over the whole register — by type, relationship, priority, stage, region, country. */
|
|
4479
|
+
async adminMarketIntelStats(org) {
|
|
4480
|
+
return this.get(`/admin/market-intel/stats${org ? `?org=${encodeURIComponent(org)}` : ""}`);
|
|
4481
|
+
}
|
|
4436
4482
|
async adminListProposals(opts) {
|
|
4437
4483
|
const params = new URLSearchParams();
|
|
4438
4484
|
if (opts?.status) params.set("status", opts.status);
|
|
@@ -4472,9 +4518,19 @@ var DayOfWeekClient = class {
|
|
|
4472
4518
|
async listBrainSources(areaId) {
|
|
4473
4519
|
return this.get(`/brain/sources?area=${encodeURIComponent(areaId)}`);
|
|
4474
4520
|
}
|
|
4475
|
-
/**
|
|
4476
|
-
|
|
4477
|
-
|
|
4521
|
+
/**
|
|
4522
|
+
* An area's actors (people and organizations, the Actors tab). Active only.
|
|
4523
|
+
* With `match`, admins also get `candidates` on unlinked actors — the
|
|
4524
|
+
* platform entities whose name matches — to review before linking.
|
|
4525
|
+
*/
|
|
4526
|
+
async listBrainActors(areaId, opts) {
|
|
4527
|
+
const params = new URLSearchParams({ area: areaId });
|
|
4528
|
+
if (opts?.match) params.set("match", "1");
|
|
4529
|
+
return this.get(`/brain/actors?${params.toString()}`);
|
|
4530
|
+
}
|
|
4531
|
+
/** Link an actor to the platform entity it is; `entityId: null` unlinks. */
|
|
4532
|
+
async linkBrainActor(input) {
|
|
4533
|
+
return this.post("/brain/actors/link", input);
|
|
4478
4534
|
}
|
|
4479
4535
|
/**
|
|
4480
4536
|
* Create an actor in an area. Idempotent on (area, name): an existing active
|
|
@@ -4717,6 +4773,52 @@ var DayOfWeekClient = class {
|
|
|
4717
4773
|
async archiveSharedSkill(skillId) {
|
|
4718
4774
|
return this.delete(`/brain/skills/${encodeURIComponent(skillId)}`);
|
|
4719
4775
|
}
|
|
4776
|
+
// ── Time tracking ─────────────────────────────────────────────────────────
|
|
4777
|
+
//
|
|
4778
|
+
// Hours registered against time projects. Every rule (periods, frames,
|
|
4779
|
+
// self-financing, the reportable basis, who may approve) is evaluated by
|
|
4780
|
+
// the server; the client only carries JSON and prints what comes back.
|
|
4781
|
+
async listTimeProjects(org) {
|
|
4782
|
+
const qs = org ? `?org=${encodeURIComponent(org)}` : "";
|
|
4783
|
+
return this.get(`/time/projects${qs}`);
|
|
4784
|
+
}
|
|
4785
|
+
async getTimeProject(projectId, org) {
|
|
4786
|
+
const params = new URLSearchParams({ id: projectId });
|
|
4787
|
+
if (org) params.set("org", org);
|
|
4788
|
+
return this.get(`/time/projects?${params.toString()}`);
|
|
4789
|
+
}
|
|
4790
|
+
async upsertTimeProject(payload, org) {
|
|
4791
|
+
const qs = org ? `?org=${encodeURIComponent(org)}` : "";
|
|
4792
|
+
return this.post(`/time/projects${qs}`, payload);
|
|
4793
|
+
}
|
|
4794
|
+
async listTimeEntries(opts) {
|
|
4795
|
+
const params = new URLSearchParams();
|
|
4796
|
+
if (opts?.project) params.set("project", opts.project);
|
|
4797
|
+
if (opts?.user) params.set("user", opts.user);
|
|
4798
|
+
if (opts?.from) params.set("from", opts.from);
|
|
4799
|
+
if (opts?.to) params.set("to", opts.to);
|
|
4800
|
+
if (opts?.status) params.set("status", opts.status);
|
|
4801
|
+
if (opts?.org) params.set("org", opts.org);
|
|
4802
|
+
const qs = params.toString();
|
|
4803
|
+
return this.get(`/time/entries${qs ? `?${qs}` : ""}`);
|
|
4804
|
+
}
|
|
4805
|
+
async importTimeEntries(input) {
|
|
4806
|
+
const qs = input.org ? `?org=${encodeURIComponent(input.org)}` : "";
|
|
4807
|
+
return this.post(`/time/entries${qs}`, {
|
|
4808
|
+
projectId: input.projectId,
|
|
4809
|
+
dryRun: input.dryRun,
|
|
4810
|
+
entries: input.entries
|
|
4811
|
+
});
|
|
4812
|
+
}
|
|
4813
|
+
async listPendingTimeMonths(org) {
|
|
4814
|
+
const qs = org ? `?org=${encodeURIComponent(org)}` : "";
|
|
4815
|
+
return this.get(`/time/months${qs}`);
|
|
4816
|
+
}
|
|
4817
|
+
async actOnTimeMonth(input) {
|
|
4818
|
+
const { org, ...body } = input;
|
|
4819
|
+
const qs = org ? `?org=${encodeURIComponent(org)}` : "";
|
|
4820
|
+
return this.post(`/time/months${qs}`, body);
|
|
4821
|
+
}
|
|
4720
4822
|
// ── Schema ────────────────────────────────────────────────────────────────
|
|
4721
4823
|
async getSchema() {
|
|
4722
4824
|
return this.get("/schema");
|
|
@@ -5165,7 +5267,7 @@ var import_promises4 = require("node:readline/promises");
|
|
|
5165
5267
|
// package.json
|
|
5166
5268
|
var package_default = {
|
|
5167
5269
|
name: "@dayofweek/dcli",
|
|
5168
|
-
version: "1.
|
|
5270
|
+
version: "1.12.0",
|
|
5169
5271
|
description: "CLI for the Day of Week AgTech platform \u2014 read data and submit proposals for review",
|
|
5170
5272
|
license: "MIT",
|
|
5171
5273
|
type: "module",
|
|
@@ -5496,8 +5598,21 @@ brainSource.command("download <uri>").description("Download exact original bytes
|
|
|
5496
5598
|
}));
|
|
5497
5599
|
});
|
|
5498
5600
|
var brainActors = brain.command("actors").description("Work with an area's actors (people and organizations)");
|
|
5499
|
-
brainActors.command("list").description("List an area's actors (active, sorted by name)").requiredOption("--area <areaId>", "Area id (from `brain list`)").action(async (opts) => {
|
|
5500
|
-
output(await getClient().listBrainActors(opts.area));
|
|
5601
|
+
brainActors.command("list").description("List an area's actors (active, sorted by name)").requiredOption("--area <areaId>", "Area id (from `brain list`)").option("--match", "Suggest matching platform entities for unlinked actors (admins)").action(async (opts) => {
|
|
5602
|
+
output(await getClient().listBrainActors(opts.area, { match: opts.match }));
|
|
5603
|
+
});
|
|
5604
|
+
brainActors.command("link").description("Link an actor to the platform entity it is (or --unlink)").requiredOption("--area <areaId>", "Area id (from `brain list`)").requiredOption("--actor <actorId>", "Actor id (from `brain actors list`)").option("--entity <entityId>", "Platform entity id (from `admin entities` or `read entities`)").option("--unlink", "Remove the current link").action(async (opts) => {
|
|
5605
|
+
if (!opts.entity && !opts.unlink) {
|
|
5606
|
+
throw new Error("Provide --entity <entityId> to link, or --unlink to remove the link");
|
|
5607
|
+
}
|
|
5608
|
+
if (opts.entity && opts.unlink) {
|
|
5609
|
+
throw new Error("--entity and --unlink are mutually exclusive");
|
|
5610
|
+
}
|
|
5611
|
+
output(await getClient().linkBrainActor({
|
|
5612
|
+
areaId: opts.area,
|
|
5613
|
+
actorId: opts.actor,
|
|
5614
|
+
entityId: opts.unlink ? null : opts.entity
|
|
5615
|
+
}));
|
|
5501
5616
|
});
|
|
5502
5617
|
brainActors.command("add").description("Add an actor to an area (idempotent on name)").requiredOption("--area <areaId>", "Area id (from `brain list`)").requiredOption("--name <name>", "Actor name (person or organization)").option("--kind <kind>", "person | organization", "organization").option("--role <text>", "Role or relationship in the project").option("--description <text>", "Longer free-text description").action(async (opts) => {
|
|
5503
5618
|
if (opts.kind !== "person" && opts.kind !== "organization") {
|
|
@@ -6150,20 +6265,129 @@ feedback.command("status <itemId>").description("Set status and/or priority on a
|
|
|
6150
6265
|
rejectedReason: opts.rejectedReason
|
|
6151
6266
|
}));
|
|
6152
6267
|
});
|
|
6268
|
+
var time = program2.command("time").description("Hours per project: projects, entries, monthly sheets");
|
|
6269
|
+
var timeProjects = time.command("projects").description("Time projects: setup and overview");
|
|
6270
|
+
timeProjects.command("list").description("List time projects with totals and frame usage").option("--org <org>", "Organization slug or id (admin only)").action(async (opts) => {
|
|
6271
|
+
output(await getClient().listTimeProjects(opts.org));
|
|
6272
|
+
});
|
|
6273
|
+
timeProjects.command("show <projectId>").description("One project: setup, person\xD7activity\xD7month matrix, entries with computed split, monthly sheets").option("--org <org>", "Organization slug or id (admin only)").action(async (projectId, opts) => {
|
|
6274
|
+
output(await getClient().getTimeProject(projectId, opts.org));
|
|
6275
|
+
});
|
|
6276
|
+
timeProjects.command("upsert").description("Create or update a time project from a JSON file (include projectId to update)").requiredOption("--file <path>", "JSON payload with the project setup (- for stdin)").option("--org <org>", "Organization slug or id (admin only)").action(async (opts) => {
|
|
6277
|
+
const raw = opts.file === "-" ? (0, import_node_fs8.readFileSync)(0, "utf8") : (0, import_node_fs8.readFileSync)(opts.file, "utf8");
|
|
6278
|
+
const payload = JSON.parse(raw);
|
|
6279
|
+
if (!payload || typeof payload !== "object" || Array.isArray(payload)) {
|
|
6280
|
+
throw new Error("Payload must be a JSON object with the project setup");
|
|
6281
|
+
}
|
|
6282
|
+
output(await getClient().upsertTimeProject(payload, opts.org));
|
|
6283
|
+
});
|
|
6284
|
+
var timeEntries = time.command("entries").description("Registered hours");
|
|
6285
|
+
timeEntries.command("list").description("Flat rows with the computed split (counted, self-financed, reportable, reasons)").option("--project <projectId>", "Only this project").option("--user <userId>", "Only this person (owners and admins)").option("--from <YYYY-MM-DD>", "First work date").option("--to <YYYY-MM-DD>", "Last work date").option("--status <status>", "draft | submitted | approved").option("--org <org>", "Organization slug or id (admin only)").action(async (opts) => {
|
|
6286
|
+
output(
|
|
6287
|
+
await getClient().listTimeEntries({
|
|
6288
|
+
project: opts.project,
|
|
6289
|
+
user: opts.user,
|
|
6290
|
+
from: opts.from,
|
|
6291
|
+
to: opts.to,
|
|
6292
|
+
status: opts.status,
|
|
6293
|
+
org: opts.org
|
|
6294
|
+
})
|
|
6295
|
+
);
|
|
6296
|
+
});
|
|
6297
|
+
timeEntries.command("import").description("Idempotent batch import of hours into one project. Requires --dry-run or --approved").requiredOption("--project <projectId>", "Target time project").requiredOption("--file <path>", "JSON: { entries: [...] } or a bare array (- for stdin)").option("--dry-run", "Validate and report what would happen, write nothing").option("--approved", "The operator approved the dry-run result").option("--org <org>", "Organization slug or id (admin only)").action(async (opts) => {
|
|
6298
|
+
if (!opts.dryRun && !opts.approved) {
|
|
6299
|
+
throw new Error(
|
|
6300
|
+
"Refusing to write: run with --dry-run first, show the operator the summary, then repeat with --approved. This writes real hours, not proposals."
|
|
6301
|
+
);
|
|
6302
|
+
}
|
|
6303
|
+
const raw = opts.file === "-" ? (0, import_node_fs8.readFileSync)(0, "utf8") : (0, import_node_fs8.readFileSync)(opts.file, "utf8");
|
|
6304
|
+
const payload = JSON.parse(raw);
|
|
6305
|
+
const entries = Array.isArray(payload) ? payload : payload?.entries;
|
|
6306
|
+
if (!Array.isArray(entries) || entries.length === 0) {
|
|
6307
|
+
throw new Error("Payload needs { entries: [...] } with at least one entry");
|
|
6308
|
+
}
|
|
6309
|
+
output(
|
|
6310
|
+
await getClient().importTimeEntries({
|
|
6311
|
+
projectId: opts.project,
|
|
6312
|
+
entries,
|
|
6313
|
+
dryRun: Boolean(opts.dryRun),
|
|
6314
|
+
org: opts.org
|
|
6315
|
+
})
|
|
6316
|
+
);
|
|
6317
|
+
});
|
|
6318
|
+
var timeMonths = time.command("months").description("Monthly sheets: submit, approve, return, reopen");
|
|
6319
|
+
timeMonths.command("list").description("Months waiting for your approval (project owner), or every submitted month (admin)").option("--org <org>", "Organization slug or id (admin only)").action(async (opts) => {
|
|
6320
|
+
output(await getClient().listPendingTimeMonths(opts.org));
|
|
6321
|
+
});
|
|
6322
|
+
for (const action of ["submit", "approve", "return", "reopen"]) {
|
|
6323
|
+
const descriptions = {
|
|
6324
|
+
submit: "Submit a month for approval (your own; admins may pass --user-email)",
|
|
6325
|
+
approve: "Approve a submitted month \u2014 locks its rows (project owner or admin)",
|
|
6326
|
+
return: "Return a submitted month as drafts, --comment required (project owner or admin)",
|
|
6327
|
+
reopen: "Reopen an approved month for correction; it needs a new approval"
|
|
6328
|
+
};
|
|
6329
|
+
timeMonths.command(action).description(descriptions[action]).requiredOption("--project <projectId>", "Time project").requiredOption("--month <YYYY-MM>", "Month").option("--user-email <email>", "Whose month (defaults to yourself)").option("--user <userId>", "Whose month, by user id").option("--comment <text>", "Comment (required for return)").option("--org <org>", "Organization slug or id (admin only)").action(async (opts) => {
|
|
6330
|
+
if (action === "return" && !opts.comment) {
|
|
6331
|
+
throw new Error("--comment is required when returning a month");
|
|
6332
|
+
}
|
|
6333
|
+
output(
|
|
6334
|
+
await getClient().actOnTimeMonth({
|
|
6335
|
+
projectId: opts.project,
|
|
6336
|
+
month: opts.month,
|
|
6337
|
+
action,
|
|
6338
|
+
userId: opts.user,
|
|
6339
|
+
userEmail: opts.userEmail,
|
|
6340
|
+
comment: opts.comment,
|
|
6341
|
+
org: opts.org
|
|
6342
|
+
})
|
|
6343
|
+
);
|
|
6344
|
+
});
|
|
6345
|
+
}
|
|
6153
6346
|
function registerAdminCommands() {
|
|
6154
6347
|
const cfg = loadConfig();
|
|
6155
6348
|
if (!cfg.isAdmin) return;
|
|
6156
6349
|
const admin = program2.command("admin", { hidden: true }).description("Admin-only cross-org operations (DoW staff)");
|
|
6157
|
-
admin.command("entities").description("List entities across all orgs with admin filters").option("--type <entityType>", "Filter by
|
|
6350
|
+
admin.command("entities").description("List entities across all orgs with admin filters (cursor-paged)").option("--type <entityType>", "Filter by type name (farm, producer, restaurant, \u2026)").option("--category <category>", "Filter by type category (supply, demand, partner, financial, \u2026)").option("--role <role>", "Only entities holding an active role: customer, partner, investor, producer, market_intel").option("--with-roles", "Attach each entity's active roles").option("--missing-location", "Only entities lacking metadata.places[].lat/lng").option("--search <query>", "Substring match on name").option("--org <slug>", "Restrict to a single org slug or ID").option("--limit <count>", "Rows scanned per page (default 200, max 2000)", parseInt).option("--cursor <cursor>", "Continue from a previous page's nextCursor").option("--all", "Walk every page and return all matches").action(async (opts) => {
|
|
6158
6351
|
const client = getClient();
|
|
6159
|
-
const
|
|
6352
|
+
const fetchPage = (cursor) => client.adminListEntities({
|
|
6160
6353
|
org: opts.org,
|
|
6161
6354
|
type: opts.type,
|
|
6355
|
+
category: opts.category,
|
|
6356
|
+
role: opts.role,
|
|
6357
|
+
withRoles: opts.withRoles,
|
|
6162
6358
|
missingLocation: opts.missingLocation,
|
|
6163
6359
|
search: opts.search,
|
|
6164
|
-
limit: opts.limit
|
|
6360
|
+
limit: opts.limit,
|
|
6361
|
+
cursor
|
|
6165
6362
|
});
|
|
6166
|
-
output(
|
|
6363
|
+
output(opts.all ? await collectAdminPages(fetchPage, opts.cursor) : await fetchPage(opts.cursor));
|
|
6364
|
+
});
|
|
6365
|
+
const marketIntel = admin.command("market-intel").description("The market intelligence register \u2014 businesses recorded for research and prospecting");
|
|
6366
|
+
marketIntel.command("list").description("List register rows (cursor-paged, newest first)").option("--type <entityType>", "farm, restaurant, farm_shop, retail_store, distributor, processor, market, cooperative, supplier, customer, competitor, other").option("--relationship <type>", "prospect, active_customer, supplier, partner, competitor, market_research, other").option("--priority <priority>", "critical, high, medium, low, watching").option("--stage <stage>", "Opportunity stage (research, qualified_lead, negotiation, \u2026)").option("--region <region>", "Exact match on region (case-insensitive)").option("--search <query>", "Substring match on name").option("--bbox <latMin,lngMin,latMax,lngMax>", "Only rows inside a lat/lng box").option("--active", "Only active rows").option("--inactive", "Only inactive rows").option("--org <slug>", "Restrict to the owning org").option("--limit <count>", "Rows scanned per page (default 200, max 2000)", parseInt).option("--cursor <cursor>", "Continue from a previous page's nextCursor").option("--all", "Walk every page and return all matches").action(async (opts) => {
|
|
6367
|
+
if (opts.active && opts.inactive) {
|
|
6368
|
+
throw new Error("--active and --inactive are mutually exclusive");
|
|
6369
|
+
}
|
|
6370
|
+
const client = getClient();
|
|
6371
|
+
const fetchPage = (cursor) => client.adminListMarketIntel({
|
|
6372
|
+
org: opts.org,
|
|
6373
|
+
type: opts.type,
|
|
6374
|
+
relationship: opts.relationship,
|
|
6375
|
+
priority: opts.priority,
|
|
6376
|
+
stage: opts.stage,
|
|
6377
|
+
region: opts.region,
|
|
6378
|
+
search: opts.search,
|
|
6379
|
+
bbox: opts.bbox,
|
|
6380
|
+
active: opts.active ? true : opts.inactive ? false : void 0,
|
|
6381
|
+
limit: opts.limit,
|
|
6382
|
+
cursor
|
|
6383
|
+
});
|
|
6384
|
+
output(opts.all ? await collectAdminPages(fetchPage, opts.cursor) : await fetchPage(opts.cursor));
|
|
6385
|
+
});
|
|
6386
|
+
marketIntel.command("get <id>").description("One register record with its latest interactions").action(async (id) => {
|
|
6387
|
+
output(await getClient().adminGetMarketIntel(id));
|
|
6388
|
+
});
|
|
6389
|
+
marketIntel.command("stats").description("Counts by type, relationship, priority, stage, region and country").option("--org <slug>", "Restrict to the owning org").action(async (opts) => {
|
|
6390
|
+
output(await getClient().adminMarketIntelStats(opts.org));
|
|
6167
6391
|
});
|
|
6168
6392
|
admin.command("proposals").description("List agent proposals across all orgs").option("--status <status>", "Filter: pending, approved, rejected, failed").option("--source-agent <name>", "Filter by sourceAgent identifier").option("--limit <count>", "Max results (default 100)", parseInt).action(async (opts) => {
|
|
6169
6393
|
const client = getClient();
|
package/dist/client.d.ts
CHANGED
|
@@ -13,6 +13,27 @@ export declare class ApiError extends Error {
|
|
|
13
13
|
requestId?: string | undefined;
|
|
14
14
|
constructor(status: number, message: string, code?: string | undefined, requestId?: string | undefined);
|
|
15
15
|
}
|
|
16
|
+
/**
|
|
17
|
+
* One page of a cursor-paged admin read. Filters apply within the scanned
|
|
18
|
+
* page, so `results` can be shorter than the page size — even empty — while
|
|
19
|
+
* `isDone` is still false. `nextCursor` is the only way forward.
|
|
20
|
+
*/
|
|
21
|
+
export type AdminPage<T = any> = {
|
|
22
|
+
results: T[];
|
|
23
|
+
total: number;
|
|
24
|
+
scanned: number;
|
|
25
|
+
isDone: boolean;
|
|
26
|
+
nextCursor: string | null;
|
|
27
|
+
truncated: boolean;
|
|
28
|
+
};
|
|
29
|
+
/**
|
|
30
|
+
* Walk a cursor-paged admin read to the end and return every match as one
|
|
31
|
+
* page. This is the loop behind `--all`; without it every caller re-invents
|
|
32
|
+
* it, or reaches for a giant `--limit` and still misses rows.
|
|
33
|
+
*/
|
|
34
|
+
export declare function collectAdminPages<T>(fetchPage: (cursor?: string) => Promise<AdminPage<T>>, startCursor?: string): Promise<AdminPage<T> & {
|
|
35
|
+
pages: number;
|
|
36
|
+
}>;
|
|
16
37
|
export declare class DayOfWeekClient {
|
|
17
38
|
private baseUrl;
|
|
18
39
|
private token;
|
|
@@ -146,14 +167,31 @@ export declare class DayOfWeekClient {
|
|
|
146
167
|
adminListEntities(opts?: {
|
|
147
168
|
org?: string;
|
|
148
169
|
type?: string;
|
|
170
|
+
category?: string;
|
|
171
|
+
role?: string;
|
|
172
|
+
withRoles?: boolean;
|
|
149
173
|
missingLocation?: boolean;
|
|
150
174
|
search?: string;
|
|
151
175
|
limit?: number;
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
176
|
+
cursor?: string;
|
|
177
|
+
}): Promise<AdminPage>;
|
|
178
|
+
adminListMarketIntel(opts?: {
|
|
179
|
+
org?: string;
|
|
180
|
+
type?: string;
|
|
181
|
+
relationship?: string;
|
|
182
|
+
priority?: string;
|
|
183
|
+
stage?: string;
|
|
184
|
+
region?: string;
|
|
185
|
+
search?: string;
|
|
186
|
+
bbox?: string;
|
|
187
|
+
active?: boolean;
|
|
188
|
+
limit?: number;
|
|
189
|
+
cursor?: string;
|
|
190
|
+
}): Promise<AdminPage>;
|
|
191
|
+
/** One register record in full, with its latest interactions. */
|
|
192
|
+
adminGetMarketIntel(id: string): Promise<any>;
|
|
193
|
+
/** Counts over the whole register — by type, relationship, priority, stage, region, country. */
|
|
194
|
+
adminMarketIntelStats(org?: string): Promise<any>;
|
|
157
195
|
adminListProposals(opts?: {
|
|
158
196
|
status?: string;
|
|
159
197
|
sourceAgent?: string;
|
|
@@ -199,8 +237,20 @@ export declare class DayOfWeekClient {
|
|
|
199
237
|
}): Promise<any>;
|
|
200
238
|
/** Area sources, metadata only, newest first — the freshness evidence. */
|
|
201
239
|
listBrainSources(areaId: string): Promise<any>;
|
|
202
|
-
/**
|
|
203
|
-
|
|
240
|
+
/**
|
|
241
|
+
* An area's actors (people and organizations, the Actors tab). Active only.
|
|
242
|
+
* With `match`, admins also get `candidates` on unlinked actors — the
|
|
243
|
+
* platform entities whose name matches — to review before linking.
|
|
244
|
+
*/
|
|
245
|
+
listBrainActors(areaId: string, opts?: {
|
|
246
|
+
match?: boolean;
|
|
247
|
+
}): Promise<any>;
|
|
248
|
+
/** Link an actor to the platform entity it is; `entityId: null` unlinks. */
|
|
249
|
+
linkBrainActor(input: {
|
|
250
|
+
areaId: string;
|
|
251
|
+
actorId: string;
|
|
252
|
+
entityId: string | null;
|
|
253
|
+
}): Promise<any>;
|
|
204
254
|
/**
|
|
205
255
|
* Create an actor in an area. Idempotent on (area, name): an existing active
|
|
206
256
|
* actor comes back with `created: false` instead of a duplicate, so imports
|
|
@@ -424,6 +474,42 @@ export declare class DayOfWeekClient {
|
|
|
424
474
|
}>;
|
|
425
475
|
}): Promise<SharedSkillSummary>;
|
|
426
476
|
archiveSharedSkill(skillId: string): Promise<SharedSkillSummary>;
|
|
477
|
+
listTimeProjects(org?: string): Promise<{
|
|
478
|
+
projects: any[];
|
|
479
|
+
}>;
|
|
480
|
+
getTimeProject(projectId: string, org?: string): Promise<any>;
|
|
481
|
+
upsertTimeProject(payload: Record<string, unknown>, org?: string): Promise<{
|
|
482
|
+
projectId: string;
|
|
483
|
+
}>;
|
|
484
|
+
listTimeEntries(opts?: {
|
|
485
|
+
project?: string;
|
|
486
|
+
user?: string;
|
|
487
|
+
from?: string;
|
|
488
|
+
to?: string;
|
|
489
|
+
status?: string;
|
|
490
|
+
org?: string;
|
|
491
|
+
}): Promise<{
|
|
492
|
+
total: number;
|
|
493
|
+
rows: any[];
|
|
494
|
+
}>;
|
|
495
|
+
importTimeEntries(input: {
|
|
496
|
+
projectId: string;
|
|
497
|
+
entries: unknown[];
|
|
498
|
+
dryRun: boolean;
|
|
499
|
+
org?: string;
|
|
500
|
+
}): Promise<any>;
|
|
501
|
+
listPendingTimeMonths(org?: string): Promise<{
|
|
502
|
+
approvals: any[];
|
|
503
|
+
}>;
|
|
504
|
+
actOnTimeMonth(input: {
|
|
505
|
+
projectId: string;
|
|
506
|
+
month: string;
|
|
507
|
+
action: "submit" | "approve" | "return" | "reopen";
|
|
508
|
+
userId?: string;
|
|
509
|
+
userEmail?: string;
|
|
510
|
+
comment?: string;
|
|
511
|
+
org?: string;
|
|
512
|
+
}): Promise<any>;
|
|
427
513
|
getSchema(): Promise<any>;
|
|
428
514
|
private get;
|
|
429
515
|
private post;
|
package/dist/client.js
CHANGED
|
@@ -29,6 +29,27 @@ export class ApiError extends Error {
|
|
|
29
29
|
this.name = "ApiError";
|
|
30
30
|
}
|
|
31
31
|
}
|
|
32
|
+
/**
|
|
33
|
+
* Walk a cursor-paged admin read to the end and return every match as one
|
|
34
|
+
* page. This is the loop behind `--all`; without it every caller re-invents
|
|
35
|
+
* it, or reaches for a giant `--limit` and still misses rows.
|
|
36
|
+
*/
|
|
37
|
+
export async function collectAdminPages(fetchPage, startCursor) {
|
|
38
|
+
const results = [];
|
|
39
|
+
let cursor = startCursor;
|
|
40
|
+
let scanned = 0;
|
|
41
|
+
let pages = 0;
|
|
42
|
+
for (;;) {
|
|
43
|
+
const page = await fetchPage(cursor);
|
|
44
|
+
results.push(...page.results);
|
|
45
|
+
scanned += page.scanned ?? page.results.length;
|
|
46
|
+
pages += 1;
|
|
47
|
+
if (page.isDone || !page.nextCursor)
|
|
48
|
+
break;
|
|
49
|
+
cursor = page.nextCursor;
|
|
50
|
+
}
|
|
51
|
+
return { results, total: results.length, scanned, isDone: true, nextCursor: null, truncated: false, pages };
|
|
52
|
+
}
|
|
32
53
|
export class DayOfWeekClient {
|
|
33
54
|
baseUrl;
|
|
34
55
|
token;
|
|
@@ -280,15 +301,61 @@ export class DayOfWeekClient {
|
|
|
280
301
|
params.set("org", opts.org);
|
|
281
302
|
if (opts?.type)
|
|
282
303
|
params.set("type", opts.type);
|
|
304
|
+
if (opts?.category)
|
|
305
|
+
params.set("category", opts.category);
|
|
306
|
+
if (opts?.role)
|
|
307
|
+
params.set("role", opts.role);
|
|
308
|
+
if (opts?.withRoles)
|
|
309
|
+
params.set("with-roles", "1");
|
|
283
310
|
if (opts?.missingLocation)
|
|
284
311
|
params.set("missing-location", "1");
|
|
285
312
|
if (opts?.search)
|
|
286
313
|
params.set("search", opts.search);
|
|
287
314
|
if (opts?.limit)
|
|
288
315
|
params.set("limit", String(opts.limit));
|
|
316
|
+
if (opts?.cursor)
|
|
317
|
+
params.set("cursor", opts.cursor);
|
|
289
318
|
const qs = params.toString();
|
|
290
319
|
return this.get(`/admin/entities${qs ? `?${qs}` : ""}`);
|
|
291
320
|
}
|
|
321
|
+
// The market intelligence register: businesses recorded for research and
|
|
322
|
+
// prospecting, each with coordinates — a separate table from the entity
|
|
323
|
+
// hierarchy, so a platform-wide count has to read both.
|
|
324
|
+
async adminListMarketIntel(opts) {
|
|
325
|
+
const params = new URLSearchParams();
|
|
326
|
+
if (opts?.org)
|
|
327
|
+
params.set("org", opts.org);
|
|
328
|
+
if (opts?.type)
|
|
329
|
+
params.set("type", opts.type);
|
|
330
|
+
if (opts?.relationship)
|
|
331
|
+
params.set("relationship", opts.relationship);
|
|
332
|
+
if (opts?.priority)
|
|
333
|
+
params.set("priority", opts.priority);
|
|
334
|
+
if (opts?.stage)
|
|
335
|
+
params.set("stage", opts.stage);
|
|
336
|
+
if (opts?.region)
|
|
337
|
+
params.set("region", opts.region);
|
|
338
|
+
if (opts?.search)
|
|
339
|
+
params.set("search", opts.search);
|
|
340
|
+
if (opts?.bbox)
|
|
341
|
+
params.set("bbox", opts.bbox);
|
|
342
|
+
if (opts?.active !== undefined)
|
|
343
|
+
params.set("active", opts.active ? "1" : "0");
|
|
344
|
+
if (opts?.limit)
|
|
345
|
+
params.set("limit", String(opts.limit));
|
|
346
|
+
if (opts?.cursor)
|
|
347
|
+
params.set("cursor", opts.cursor);
|
|
348
|
+
const qs = params.toString();
|
|
349
|
+
return this.get(`/admin/market-intel${qs ? `?${qs}` : ""}`);
|
|
350
|
+
}
|
|
351
|
+
/** One register record in full, with its latest interactions. */
|
|
352
|
+
async adminGetMarketIntel(id) {
|
|
353
|
+
return this.get(`/admin/market-intel/${encodeURIComponent(id)}`);
|
|
354
|
+
}
|
|
355
|
+
/** Counts over the whole register — by type, relationship, priority, stage, region, country. */
|
|
356
|
+
async adminMarketIntelStats(org) {
|
|
357
|
+
return this.get(`/admin/market-intel/stats${org ? `?org=${encodeURIComponent(org)}` : ""}`);
|
|
358
|
+
}
|
|
292
359
|
async adminListProposals(opts) {
|
|
293
360
|
const params = new URLSearchParams();
|
|
294
361
|
if (opts?.status)
|
|
@@ -332,9 +399,20 @@ export class DayOfWeekClient {
|
|
|
332
399
|
async listBrainSources(areaId) {
|
|
333
400
|
return this.get(`/brain/sources?area=${encodeURIComponent(areaId)}`);
|
|
334
401
|
}
|
|
335
|
-
/**
|
|
336
|
-
|
|
337
|
-
|
|
402
|
+
/**
|
|
403
|
+
* An area's actors (people and organizations, the Actors tab). Active only.
|
|
404
|
+
* With `match`, admins also get `candidates` on unlinked actors — the
|
|
405
|
+
* platform entities whose name matches — to review before linking.
|
|
406
|
+
*/
|
|
407
|
+
async listBrainActors(areaId, opts) {
|
|
408
|
+
const params = new URLSearchParams({ area: areaId });
|
|
409
|
+
if (opts?.match)
|
|
410
|
+
params.set("match", "1");
|
|
411
|
+
return this.get(`/brain/actors?${params.toString()}`);
|
|
412
|
+
}
|
|
413
|
+
/** Link an actor to the platform entity it is; `entityId: null` unlinks. */
|
|
414
|
+
async linkBrainActor(input) {
|
|
415
|
+
return this.post("/brain/actors/link", input);
|
|
338
416
|
}
|
|
339
417
|
/**
|
|
340
418
|
* Create an actor in an area. Idempotent on (area, name): an existing active
|
|
@@ -600,6 +678,59 @@ export class DayOfWeekClient {
|
|
|
600
678
|
async archiveSharedSkill(skillId) {
|
|
601
679
|
return this.delete(`/brain/skills/${encodeURIComponent(skillId)}`);
|
|
602
680
|
}
|
|
681
|
+
// ── Time tracking ─────────────────────────────────────────────────────────
|
|
682
|
+
//
|
|
683
|
+
// Hours registered against time projects. Every rule (periods, frames,
|
|
684
|
+
// self-financing, the reportable basis, who may approve) is evaluated by
|
|
685
|
+
// the server; the client only carries JSON and prints what comes back.
|
|
686
|
+
async listTimeProjects(org) {
|
|
687
|
+
const qs = org ? `?org=${encodeURIComponent(org)}` : "";
|
|
688
|
+
return this.get(`/time/projects${qs}`);
|
|
689
|
+
}
|
|
690
|
+
async getTimeProject(projectId, org) {
|
|
691
|
+
const params = new URLSearchParams({ id: projectId });
|
|
692
|
+
if (org)
|
|
693
|
+
params.set("org", org);
|
|
694
|
+
return this.get(`/time/projects?${params.toString()}`);
|
|
695
|
+
}
|
|
696
|
+
async upsertTimeProject(payload, org) {
|
|
697
|
+
const qs = org ? `?org=${encodeURIComponent(org)}` : "";
|
|
698
|
+
return this.post(`/time/projects${qs}`, payload);
|
|
699
|
+
}
|
|
700
|
+
async listTimeEntries(opts) {
|
|
701
|
+
const params = new URLSearchParams();
|
|
702
|
+
if (opts?.project)
|
|
703
|
+
params.set("project", opts.project);
|
|
704
|
+
if (opts?.user)
|
|
705
|
+
params.set("user", opts.user);
|
|
706
|
+
if (opts?.from)
|
|
707
|
+
params.set("from", opts.from);
|
|
708
|
+
if (opts?.to)
|
|
709
|
+
params.set("to", opts.to);
|
|
710
|
+
if (opts?.status)
|
|
711
|
+
params.set("status", opts.status);
|
|
712
|
+
if (opts?.org)
|
|
713
|
+
params.set("org", opts.org);
|
|
714
|
+
const qs = params.toString();
|
|
715
|
+
return this.get(`/time/entries${qs ? `?${qs}` : ""}`);
|
|
716
|
+
}
|
|
717
|
+
async importTimeEntries(input) {
|
|
718
|
+
const qs = input.org ? `?org=${encodeURIComponent(input.org)}` : "";
|
|
719
|
+
return this.post(`/time/entries${qs}`, {
|
|
720
|
+
projectId: input.projectId,
|
|
721
|
+
dryRun: input.dryRun,
|
|
722
|
+
entries: input.entries,
|
|
723
|
+
});
|
|
724
|
+
}
|
|
725
|
+
async listPendingTimeMonths(org) {
|
|
726
|
+
const qs = org ? `?org=${encodeURIComponent(org)}` : "";
|
|
727
|
+
return this.get(`/time/months${qs}`);
|
|
728
|
+
}
|
|
729
|
+
async actOnTimeMonth(input) {
|
|
730
|
+
const { org, ...body } = input;
|
|
731
|
+
const qs = org ? `?org=${encodeURIComponent(org)}` : "";
|
|
732
|
+
return this.post(`/time/months${qs}`, body);
|
|
733
|
+
}
|
|
603
734
|
// ── Schema ────────────────────────────────────────────────────────────────
|
|
604
735
|
async getSchema() {
|
|
605
736
|
return this.get("/schema");
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dayofweek/dcli",
|
|
3
|
-
"version": "1.
|
|
4
|
-
"description": "CLI for the Day of Week AgTech platform
|
|
3
|
+
"version": "1.12.0",
|
|
4
|
+
"description": "CLI for the Day of Week AgTech platform — read data and submit proposals for review",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"bin": {
|