@dayofweek/dcli 1.2.0 → 1.3.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/dist/bin/dcli.js CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import { Command } from "commander";
3
3
  import { DayOfWeekClient } from "../client.js";
4
- import { getToken, getApiUrl, saveConfig } from "../config.js";
4
+ import { getToken, getApiUrl, saveConfig, loadConfig } from "../config.js";
5
5
  import { readFileSync, existsSync, mkdirSync, writeFileSync } from "node:fs";
6
6
  import { join, dirname } from "node:path";
7
7
  import { homedir } from "node:os";
@@ -57,6 +57,13 @@ auth
57
57
  try {
58
58
  const client = getClient();
59
59
  const result = await client.checkAuth();
60
+ // Persist isAdmin so the next CLI invocation can register admin
61
+ // subcommands in --help without a network round-trip. Stale cache
62
+ // is harmless: admin commands still reject non-admin tokens at the
63
+ // API boundary, and admin demotion is rare.
64
+ if (result.authenticated && typeof result.isAdmin === "boolean") {
65
+ saveConfig({ isAdmin: result.isAdmin, roleCachedAt: Date.now() });
66
+ }
60
67
  output(result);
61
68
  }
62
69
  catch (err) {
@@ -329,6 +336,59 @@ skill
329
336
  }
330
337
  console.log("\nTo update: dcli skill update");
331
338
  });
339
+ // ── Admin Commands ───────────────────────────────────────────────────────────
340
+ //
341
+ // These are admin-only. They're registered as hidden subcommands when the
342
+ // cached role from the last `dcli auth status` says the caller is admin —
343
+ // otherwise they're not advertised in --help at all and customers never
344
+ // learn the commands exist. The endpoints themselves enforce admin auth
345
+ // independently, so stale or absent cache can't grant access.
346
+ //
347
+ // New admin agents should run `dcli auth status` once after install to
348
+ // populate the cache; the admin skill bundle (ADMIN_MD) documents this.
349
+ function registerAdminCommands() {
350
+ const cfg = loadConfig();
351
+ if (!cfg.isAdmin)
352
+ return;
353
+ const admin = program
354
+ .command("admin", { hidden: true })
355
+ .description("Admin-only cross-org operations (DoW staff)");
356
+ admin
357
+ .command("entities")
358
+ .description("List entities across all orgs with admin filters")
359
+ .option("--type <entityType>", "Filter by entity type")
360
+ .option("--missing-location", "Only entities lacking metadata.places[].lat/lng")
361
+ .option("--search <query>", "Substring match on name")
362
+ .option("--org <slug>", "Restrict to a single org slug or ID")
363
+ .option("--limit <count>", "Max results (default 200)", parseInt)
364
+ .action(async (opts) => {
365
+ const client = getClient();
366
+ const result = await client.adminListEntities({
367
+ org: opts.org,
368
+ type: opts.type,
369
+ missingLocation: opts.missingLocation,
370
+ search: opts.search,
371
+ limit: opts.limit,
372
+ });
373
+ output(result);
374
+ });
375
+ admin
376
+ .command("proposals")
377
+ .description("List agent proposals across all orgs")
378
+ .option("--status <status>", "Filter: pending, approved, rejected, failed")
379
+ .option("--source-agent <name>", "Filter by sourceAgent identifier")
380
+ .option("--limit <count>", "Max results (default 100)", parseInt)
381
+ .action(async (opts) => {
382
+ const client = getClient();
383
+ const result = await client.adminListProposals({
384
+ status: opts.status,
385
+ sourceAgent: opts.sourceAgent,
386
+ limit: opts.limit,
387
+ });
388
+ output(result);
389
+ });
390
+ }
391
+ registerAdminCommands();
332
392
  // ── Helpers ──────────────────────────────────────────────────────────────────
333
393
  async function readStdin() {
334
394
  const chunks = [];
package/dist/client.d.ts CHANGED
@@ -13,6 +13,7 @@ export declare class DayOfWeekClient {
13
13
  checkAuth(): Promise<{
14
14
  status: string;
15
15
  authenticated: boolean;
16
+ isAdmin?: boolean;
16
17
  }>;
17
18
  listDevices(): Promise<Array<{
18
19
  _id: string;
@@ -61,6 +62,26 @@ export declare class DayOfWeekClient {
61
62
  sourceAgent?: string;
62
63
  proposals: any[];
63
64
  }): Promise<any>;
65
+ adminListEntities(opts?: {
66
+ org?: string;
67
+ type?: string;
68
+ missingLocation?: boolean;
69
+ search?: string;
70
+ limit?: number;
71
+ }): Promise<{
72
+ results: any[];
73
+ truncated: boolean;
74
+ total: number;
75
+ }>;
76
+ adminListProposals(opts?: {
77
+ status?: string;
78
+ sourceAgent?: string;
79
+ limit?: number;
80
+ }): Promise<{
81
+ results: any[];
82
+ truncated: boolean;
83
+ total: number;
84
+ }>;
64
85
  getSkillBundle(): Promise<{
65
86
  name: string;
66
87
  version: string;
package/dist/client.js CHANGED
@@ -103,6 +103,34 @@ export class DayOfWeekClient {
103
103
  async submitBatch(batch) {
104
104
  return this.post("/proposals/batch", batch);
105
105
  }
106
+ // ── Admin (cross-org) ─────────────────────────────────────────────────────
107
+ // These endpoints reject non-admin tokens with 401 "Admin access required".
108
+ async adminListEntities(opts) {
109
+ const params = new URLSearchParams();
110
+ if (opts?.org)
111
+ params.set("org", opts.org);
112
+ if (opts?.type)
113
+ params.set("type", opts.type);
114
+ if (opts?.missingLocation)
115
+ params.set("missing-location", "1");
116
+ if (opts?.search)
117
+ params.set("search", opts.search);
118
+ if (opts?.limit)
119
+ params.set("limit", String(opts.limit));
120
+ const qs = params.toString();
121
+ return this.get(`/admin/entities${qs ? `?${qs}` : ""}`);
122
+ }
123
+ async adminListProposals(opts) {
124
+ const params = new URLSearchParams();
125
+ if (opts?.status)
126
+ params.set("status", opts.status);
127
+ if (opts?.sourceAgent)
128
+ params.set("source-agent", opts.sourceAgent);
129
+ if (opts?.limit)
130
+ params.set("limit", String(opts.limit));
131
+ const qs = params.toString();
132
+ return this.get(`/admin/proposals${qs ? `?${qs}` : ""}`);
133
+ }
106
134
  // ── Skill ─────────────────────────────────────────────────────────────────
107
135
  async getSkillBundle() {
108
136
  return this.get("/skill");
package/dist/config.d.ts CHANGED
@@ -1,6 +1,15 @@
1
1
  export interface DcliConfig {
2
2
  authToken?: string;
3
3
  apiUrl?: string;
4
+ /**
5
+ * Cached admin status from the last `dcli auth status` call. Used to
6
+ * decide locally whether to expose admin-only subcommands in `--help`.
7
+ * Refreshed automatically when `dcli auth status` or `dcli auth login`
8
+ * runs; admin commands are hidden when this is unset or false.
9
+ */
10
+ isAdmin?: boolean;
11
+ /** Unix-ms timestamp of the last isAdmin refresh. */
12
+ roleCachedAt?: number;
4
13
  }
5
14
  export declare function loadConfig(): DcliConfig;
6
15
  export declare function saveConfig(updates: Partial<DcliConfig>): void;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dayofweek/dcli",
3
- "version": "1.2.0",
3
+ "version": "1.3.0",
4
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",