@dayofweek/dcli 1.1.3 → 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 +132 -29
- 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) {
|
|
@@ -233,14 +240,22 @@ agent
|
|
|
233
240
|
});
|
|
234
241
|
// ── Skill Commands ───────────────────────────────────────────────────────────
|
|
235
242
|
const skill = program.command("skill").description("Manage the Day of Week agent skill");
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
const
|
|
242
|
-
|
|
243
|
-
|
|
243
|
+
function resolveTargetDirs(target, bundleName, customDir) {
|
|
244
|
+
if (customDir)
|
|
245
|
+
return [customDir];
|
|
246
|
+
const agentsDir = join(homedir(), ".agents", "skills", bundleName);
|
|
247
|
+
const claudeDir = join(homedir(), ".claude", "skills", bundleName);
|
|
248
|
+
const claudeRootExists = existsSync(join(homedir(), ".claude"));
|
|
249
|
+
switch (target) {
|
|
250
|
+
case "agents":
|
|
251
|
+
return [agentsDir];
|
|
252
|
+
case "claude":
|
|
253
|
+
return [claudeDir];
|
|
254
|
+
case "all":
|
|
255
|
+
return claudeRootExists ? [agentsDir, claudeDir] : [agentsDir];
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
function writeBundle(bundle, targetDir) {
|
|
244
259
|
let filesWritten = 0;
|
|
245
260
|
for (const file of bundle.files) {
|
|
246
261
|
const filePath = join(targetDir, file.path);
|
|
@@ -248,44 +263,132 @@ skill
|
|
|
248
263
|
writeFileSync(filePath, file.content, "utf-8");
|
|
249
264
|
filesWritten++;
|
|
250
265
|
}
|
|
251
|
-
|
|
266
|
+
return filesWritten;
|
|
267
|
+
}
|
|
268
|
+
function parseTarget(value) {
|
|
269
|
+
const v = (value ?? "all").toLowerCase();
|
|
270
|
+
if (v !== "agents" && v !== "claude" && v !== "all") {
|
|
271
|
+
console.error(`Invalid --target: ${value}. Use agents, claude, or all.`);
|
|
272
|
+
process.exit(1);
|
|
273
|
+
}
|
|
274
|
+
return v;
|
|
275
|
+
}
|
|
276
|
+
skill
|
|
277
|
+
.command("install")
|
|
278
|
+
.description("Install the agent skill (requires valid auth)")
|
|
279
|
+
.option("--dir <path>", "Custom install directory (overrides --target)")
|
|
280
|
+
.option("--target <target>", "Install target: agents, claude, or all (default: all)")
|
|
281
|
+
.action(async (opts) => {
|
|
282
|
+
const client = getClient();
|
|
283
|
+
const bundle = await client.getSkillBundle();
|
|
284
|
+
const target = parseTarget(opts.target);
|
|
285
|
+
const dirs = resolveTargetDirs(target, bundle.name, opts.dir);
|
|
286
|
+
for (const dir of dirs) {
|
|
287
|
+
const count = writeBundle(bundle, dir);
|
|
288
|
+
console.log(`Installed ${count} files to ${dir}`);
|
|
289
|
+
}
|
|
252
290
|
console.log("Any compatible agent will discover the skill automatically.");
|
|
253
291
|
});
|
|
254
292
|
skill
|
|
255
293
|
.command("update")
|
|
256
294
|
.description("Update the skill to the latest version")
|
|
257
|
-
.option("--dir <path>", "Custom install directory")
|
|
295
|
+
.option("--dir <path>", "Custom install directory (overrides --target)")
|
|
296
|
+
.option("--target <target>", "Install target: agents, claude, or all (default: all)")
|
|
258
297
|
.action(async (opts) => {
|
|
259
|
-
// Same as install — overwrites
|
|
260
298
|
const client = getClient();
|
|
261
299
|
const bundle = await client.getSkillBundle();
|
|
262
|
-
const
|
|
263
|
-
|
|
264
|
-
for (const
|
|
265
|
-
const
|
|
266
|
-
|
|
267
|
-
writeFileSync(filePath, file.content, "utf-8");
|
|
268
|
-
filesWritten++;
|
|
300
|
+
const target = parseTarget(opts.target);
|
|
301
|
+
const dirs = resolveTargetDirs(target, bundle.name, opts.dir);
|
|
302
|
+
for (const dir of dirs) {
|
|
303
|
+
const count = writeBundle(bundle, dir);
|
|
304
|
+
console.log(`Updated ${count} files in ${dir}`);
|
|
269
305
|
}
|
|
270
|
-
console.log(`Updated ${filesWritten} files in ${targetDir}`);
|
|
271
306
|
});
|
|
272
307
|
skill
|
|
273
308
|
.command("status")
|
|
274
309
|
.description("Check if the skill is installed")
|
|
275
|
-
.option("--dir <path>", "Custom install directory")
|
|
310
|
+
.option("--dir <path>", "Custom install directory (overrides --target)")
|
|
311
|
+
.option("--target <target>", "Check target: agents, claude, or all (default: all)")
|
|
276
312
|
.action(async (opts) => {
|
|
277
|
-
const
|
|
278
|
-
const
|
|
279
|
-
|
|
280
|
-
|
|
313
|
+
const bundleName = "dayofweek-platform";
|
|
314
|
+
const target = parseTarget(opts.target);
|
|
315
|
+
const dirs = opts.dir
|
|
316
|
+
? [opts.dir]
|
|
317
|
+
: target === "all"
|
|
318
|
+
? [join(homedir(), ".agents", "skills", bundleName), join(homedir(), ".claude", "skills", bundleName)]
|
|
319
|
+
: resolveTargetDirs(target, bundleName);
|
|
320
|
+
let anyInstalled = false;
|
|
321
|
+
for (const dir of dirs) {
|
|
322
|
+
const skillPath = join(dir, "SKILL.md");
|
|
323
|
+
if (!existsSync(skillPath)) {
|
|
324
|
+
console.log(`Not installed at: ${dir}`);
|
|
325
|
+
continue;
|
|
326
|
+
}
|
|
327
|
+
anyInstalled = true;
|
|
328
|
+
const content = readFileSync(skillPath, "utf-8");
|
|
329
|
+
const versionMatch = content.match(/version:\s*"([^"]+)"/);
|
|
330
|
+
console.log(`Installed at: ${dir}`);
|
|
331
|
+
console.log(` Version: ${versionMatch?.[1] ?? "unknown"}`);
|
|
332
|
+
}
|
|
333
|
+
if (!anyInstalled) {
|
|
334
|
+
console.log("\nRun: dcli skill install");
|
|
281
335
|
process.exit(1);
|
|
282
336
|
}
|
|
283
|
-
const content = readFileSync(skillPath, "utf-8");
|
|
284
|
-
const versionMatch = content.match(/version:\s*"([^"]+)"/);
|
|
285
|
-
console.log(`Installed at: ${dir}`);
|
|
286
|
-
console.log(`Version: ${versionMatch?.[1] ?? "unknown"}`);
|
|
287
337
|
console.log("\nTo update: dcli skill update");
|
|
288
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();
|
|
289
392
|
// ── Helpers ──────────────────────────────────────────────────────────────────
|
|
290
393
|
async function readStdin() {
|
|
291
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;
|