@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 +61 -1
- package/dist/client.d.ts +21 -0
- package/dist/client.js +28 -0
- package/dist/config.d.ts +9 -0
- package/package.json +1 -1
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;
|