@robbeverhelst/do 0.0.1 → 1.1.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 +6 -3
- package/dist/do.js +582 -59
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,12 +1,15 @@
|
|
|
1
|
-
# @do
|
|
1
|
+
# @robbeverhelst/do
|
|
2
2
|
|
|
3
3
|
`do` — an agent-drivable command-line client over the **do** public API
|
|
4
4
|
(`/api/v1`). A thin HTTP client: per-tenant API-key auth, the same validation and
|
|
5
5
|
RLS wall as the web app, no direct database access.
|
|
6
6
|
|
|
7
|
+
> Published to npm as **`@robbeverhelst/do`** (the `@do` scope is taken); the
|
|
8
|
+
> installed command is `do`. The workspace package is named `@do/cli` internally.
|
|
9
|
+
|
|
7
10
|
```sh
|
|
8
|
-
bunx @do
|
|
9
|
-
bunx @do
|
|
11
|
+
bunx @robbeverhelst/do help
|
|
12
|
+
bunx @robbeverhelst/do today
|
|
10
13
|
```
|
|
11
14
|
|
|
12
15
|
## Scope (v1)
|
package/dist/do.js
CHANGED
|
@@ -19,7 +19,14 @@ var __export = (target, all) => {
|
|
|
19
19
|
import { homedir } from "os";
|
|
20
20
|
|
|
21
21
|
// src/args.ts
|
|
22
|
-
var GLOBAL_BOOLEAN_FLAGS = [
|
|
22
|
+
var GLOBAL_BOOLEAN_FLAGS = [
|
|
23
|
+
"json",
|
|
24
|
+
"quiet",
|
|
25
|
+
"verbose",
|
|
26
|
+
"help",
|
|
27
|
+
"version",
|
|
28
|
+
"allow-destructive"
|
|
29
|
+
];
|
|
23
30
|
var SHORT_ALIASES = {
|
|
24
31
|
h: "help",
|
|
25
32
|
q: "quiet",
|
|
@@ -4577,6 +4584,7 @@ var calendarEventLinks = pgTable("calendar_event_links", {
|
|
|
4577
4584
|
var apiKeys = pgTable("api_keys", {
|
|
4578
4585
|
id: text("id").primaryKey(),
|
|
4579
4586
|
tenantId: text("tenant_id").notNull().references(() => tenants.id),
|
|
4587
|
+
userId: text("user_id").references(() => users.id),
|
|
4580
4588
|
name: text("name").notNull(),
|
|
4581
4589
|
prefix: text("prefix").notNull(),
|
|
4582
4590
|
keyHash: text("key_hash").notNull().unique(),
|
|
@@ -20097,7 +20105,23 @@ var apiKeyScopeSchema = exports_external.enum([
|
|
|
20097
20105
|
"notes:read",
|
|
20098
20106
|
"notes:write",
|
|
20099
20107
|
"contacts:read",
|
|
20100
|
-
"contacts:write"
|
|
20108
|
+
"contacts:write",
|
|
20109
|
+
"projects:read",
|
|
20110
|
+
"projects:write",
|
|
20111
|
+
"routines:read",
|
|
20112
|
+
"routines:write",
|
|
20113
|
+
"settings:read",
|
|
20114
|
+
"settings:write",
|
|
20115
|
+
"calendar:read",
|
|
20116
|
+
"members:read",
|
|
20117
|
+
"members:write",
|
|
20118
|
+
"sharing:read",
|
|
20119
|
+
"sharing:write",
|
|
20120
|
+
"comments:read",
|
|
20121
|
+
"comments:write",
|
|
20122
|
+
"roles:read",
|
|
20123
|
+
"links:read",
|
|
20124
|
+
"links:write"
|
|
20101
20125
|
]);
|
|
20102
20126
|
var apiKeySchema = createSelectSchema(apiKeys, {
|
|
20103
20127
|
scopes: exports_external.array(apiKeyScopeSchema),
|
|
@@ -20115,6 +20139,7 @@ var createApiKeyResultSchema = apiKeyPublicSchema.extend({ key: exports_external
|
|
|
20115
20139
|
var apiKeyContextSchema = exports_external.object({
|
|
20116
20140
|
apiKeyId: exports_external.string(),
|
|
20117
20141
|
tenantId: exports_external.string(),
|
|
20142
|
+
userId: exports_external.string().nullable(),
|
|
20118
20143
|
scopes: exports_external.array(apiKeyScopeSchema)
|
|
20119
20144
|
});
|
|
20120
20145
|
// ../../packages/schema/src/contracts/webhooks.ts
|
|
@@ -20965,7 +20990,7 @@ function parseCapture(input) {
|
|
|
20965
20990
|
|
|
20966
20991
|
// src/meta.ts
|
|
20967
20992
|
var CLI_NAME = "do";
|
|
20968
|
-
var VERSION = "
|
|
20993
|
+
var VERSION = "1.1.0";
|
|
20969
20994
|
var API_PREFIX = "/api/v1";
|
|
20970
20995
|
var DEFAULT_API_URL = "http://localhost:3001";
|
|
20971
20996
|
|
|
@@ -20984,6 +21009,12 @@ class DoClient {
|
|
|
20984
21009
|
patch(path, body) {
|
|
20985
21010
|
return this.request("PATCH", path, { body });
|
|
20986
21011
|
}
|
|
21012
|
+
put(path, body) {
|
|
21013
|
+
return this.request("PUT", path, { body });
|
|
21014
|
+
}
|
|
21015
|
+
delete(path) {
|
|
21016
|
+
return this.request("DELETE", path, {});
|
|
21017
|
+
}
|
|
20987
21018
|
url(path, query) {
|
|
20988
21019
|
const origin = this.opts.apiUrl.replace(/\/+$/, "");
|
|
20989
21020
|
const url2 = new URL(`${origin}${API_PREFIX}${path}`);
|
|
@@ -21127,7 +21158,15 @@ var ENTITIES = [
|
|
|
21127
21158
|
canGet: true,
|
|
21128
21159
|
listFlags: [
|
|
21129
21160
|
{ name: "filter", description: "all (default) or unsorted (the GTD inbox)", query: "filter" }
|
|
21130
|
-
]
|
|
21161
|
+
],
|
|
21162
|
+
write: {
|
|
21163
|
+
scope: "tasks:write",
|
|
21164
|
+
createSchema: createTaskInputSchema,
|
|
21165
|
+
createInput: "CreateTaskInput",
|
|
21166
|
+
updateSchema: updateTaskInputSchema,
|
|
21167
|
+
updateInput: "UpdateTaskInput",
|
|
21168
|
+
canDelete: true
|
|
21169
|
+
}
|
|
21131
21170
|
},
|
|
21132
21171
|
{
|
|
21133
21172
|
name: "habits",
|
|
@@ -21135,22 +21174,79 @@ var ENTITIES = [
|
|
|
21135
21174
|
readScope: "habits:read",
|
|
21136
21175
|
returns: "HabitSummary",
|
|
21137
21176
|
canGet: true,
|
|
21138
|
-
listFlags: []
|
|
21177
|
+
listFlags: [],
|
|
21178
|
+
write: {
|
|
21179
|
+
scope: "habits:write",
|
|
21180
|
+
createSchema: createHabitInputSchema,
|
|
21181
|
+
createInput: "CreateHabitInput",
|
|
21182
|
+
updateSchema: updateHabitInputSchema,
|
|
21183
|
+
updateInput: "UpdateHabitInput",
|
|
21184
|
+
canDelete: true
|
|
21185
|
+
}
|
|
21139
21186
|
},
|
|
21140
21187
|
{
|
|
21141
|
-
name: "
|
|
21142
|
-
path: "/
|
|
21143
|
-
readScope: "
|
|
21144
|
-
returns: "
|
|
21145
|
-
canGet:
|
|
21146
|
-
listFlags: [
|
|
21147
|
-
|
|
21148
|
-
|
|
21149
|
-
|
|
21150
|
-
|
|
21151
|
-
|
|
21152
|
-
|
|
21153
|
-
|
|
21188
|
+
name: "projects",
|
|
21189
|
+
path: "/projects",
|
|
21190
|
+
readScope: "projects:read",
|
|
21191
|
+
returns: "ProjectView",
|
|
21192
|
+
canGet: true,
|
|
21193
|
+
listFlags: [],
|
|
21194
|
+
write: {
|
|
21195
|
+
scope: "projects:write",
|
|
21196
|
+
createSchema: createProjectInputSchema,
|
|
21197
|
+
createInput: "CreateProjectInput",
|
|
21198
|
+
updateSchema: updateProjectInputSchema,
|
|
21199
|
+
updateInput: "UpdateProjectInput",
|
|
21200
|
+
canDelete: true
|
|
21201
|
+
}
|
|
21202
|
+
},
|
|
21203
|
+
{
|
|
21204
|
+
name: "routines",
|
|
21205
|
+
path: "/routines",
|
|
21206
|
+
readScope: "routines:read",
|
|
21207
|
+
returns: "RoutineView",
|
|
21208
|
+
canGet: true,
|
|
21209
|
+
listFlags: [],
|
|
21210
|
+
write: {
|
|
21211
|
+
scope: "routines:write",
|
|
21212
|
+
createSchema: createRoutineInputSchema,
|
|
21213
|
+
createInput: "CreateRoutineInput",
|
|
21214
|
+
updateSchema: updateRoutineInputSchema,
|
|
21215
|
+
updateInput: "UpdateRoutineInput",
|
|
21216
|
+
canDelete: true
|
|
21217
|
+
}
|
|
21218
|
+
},
|
|
21219
|
+
{
|
|
21220
|
+
name: "notes",
|
|
21221
|
+
path: "/notes",
|
|
21222
|
+
readScope: "notes:read",
|
|
21223
|
+
returns: "Note",
|
|
21224
|
+
canGet: true,
|
|
21225
|
+
listFlags: [],
|
|
21226
|
+
write: {
|
|
21227
|
+
scope: "notes:write",
|
|
21228
|
+
createSchema: createNoteInputSchema,
|
|
21229
|
+
createInput: "CreateNoteInput",
|
|
21230
|
+
updateSchema: updateNoteInputSchema,
|
|
21231
|
+
updateInput: "UpdateNoteInput",
|
|
21232
|
+
canDelete: true
|
|
21233
|
+
}
|
|
21234
|
+
},
|
|
21235
|
+
{
|
|
21236
|
+
name: "contacts",
|
|
21237
|
+
path: "/contacts",
|
|
21238
|
+
readScope: "contacts:read",
|
|
21239
|
+
returns: "Contact",
|
|
21240
|
+
canGet: true,
|
|
21241
|
+
listFlags: [],
|
|
21242
|
+
write: {
|
|
21243
|
+
scope: "contacts:write",
|
|
21244
|
+
createSchema: createContactInputSchema,
|
|
21245
|
+
createInput: "CreateContactInput",
|
|
21246
|
+
updateSchema: updateContactInputSchema,
|
|
21247
|
+
updateInput: "UpdateContactInput",
|
|
21248
|
+
canDelete: true
|
|
21249
|
+
}
|
|
21154
21250
|
},
|
|
21155
21251
|
{
|
|
21156
21252
|
name: "shopping-lists",
|
|
@@ -21158,7 +21254,15 @@ var ENTITIES = [
|
|
|
21158
21254
|
readScope: "shopping:read",
|
|
21159
21255
|
returns: "ShoppingList",
|
|
21160
21256
|
canGet: true,
|
|
21161
|
-
listFlags: []
|
|
21257
|
+
listFlags: [],
|
|
21258
|
+
write: {
|
|
21259
|
+
scope: "shopping:write",
|
|
21260
|
+
createSchema: createShoppingListInputSchema,
|
|
21261
|
+
createInput: "CreateShoppingListInput",
|
|
21262
|
+
updateSchema: updateShoppingListInputSchema,
|
|
21263
|
+
updateInput: "UpdateShoppingListInput",
|
|
21264
|
+
canDelete: true
|
|
21265
|
+
}
|
|
21162
21266
|
},
|
|
21163
21267
|
{
|
|
21164
21268
|
name: "inventory-items",
|
|
@@ -21166,7 +21270,15 @@ var ENTITIES = [
|
|
|
21166
21270
|
readScope: "inventory:read",
|
|
21167
21271
|
returns: "InventoryItem",
|
|
21168
21272
|
canGet: true,
|
|
21169
|
-
listFlags: []
|
|
21273
|
+
listFlags: [],
|
|
21274
|
+
write: {
|
|
21275
|
+
scope: "inventory:write",
|
|
21276
|
+
createSchema: createInventoryItemInputSchema,
|
|
21277
|
+
createInput: "CreateInventoryItemInput",
|
|
21278
|
+
updateSchema: updateInventoryItemInputSchema,
|
|
21279
|
+
updateInput: "UpdateInventoryItemInput",
|
|
21280
|
+
canDelete: true
|
|
21281
|
+
}
|
|
21170
21282
|
},
|
|
21171
21283
|
{
|
|
21172
21284
|
name: "recipes",
|
|
@@ -21174,7 +21286,15 @@ var ENTITIES = [
|
|
|
21174
21286
|
readScope: "meals:read",
|
|
21175
21287
|
returns: "Recipe",
|
|
21176
21288
|
canGet: true,
|
|
21177
|
-
listFlags: []
|
|
21289
|
+
listFlags: [],
|
|
21290
|
+
write: {
|
|
21291
|
+
scope: "meals:write",
|
|
21292
|
+
createSchema: createRecipeInputSchema,
|
|
21293
|
+
createInput: "CreateRecipeInput",
|
|
21294
|
+
updateSchema: updateRecipeInputSchema,
|
|
21295
|
+
updateInput: "UpdateRecipeInput",
|
|
21296
|
+
canDelete: true
|
|
21297
|
+
}
|
|
21178
21298
|
},
|
|
21179
21299
|
{
|
|
21180
21300
|
name: "meal-plans",
|
|
@@ -21182,23 +21302,30 @@ var ENTITIES = [
|
|
|
21182
21302
|
readScope: "meals:read",
|
|
21183
21303
|
returns: "MealPlan",
|
|
21184
21304
|
canGet: true,
|
|
21185
|
-
listFlags: []
|
|
21186
|
-
|
|
21187
|
-
|
|
21188
|
-
|
|
21189
|
-
|
|
21190
|
-
|
|
21191
|
-
|
|
21192
|
-
|
|
21193
|
-
|
|
21305
|
+
listFlags: [],
|
|
21306
|
+
write: {
|
|
21307
|
+
scope: "meals:write",
|
|
21308
|
+
createSchema: createMealPlanInputSchema,
|
|
21309
|
+
createInput: "CreateMealPlanInput",
|
|
21310
|
+
updateSchema: updateMealPlanInputSchema,
|
|
21311
|
+
updateInput: "UpdateMealPlanInput",
|
|
21312
|
+
canDelete: true
|
|
21313
|
+
}
|
|
21194
21314
|
},
|
|
21195
21315
|
{
|
|
21196
|
-
name: "
|
|
21197
|
-
path: "/
|
|
21198
|
-
readScope: "
|
|
21199
|
-
returns: "
|
|
21200
|
-
canGet:
|
|
21201
|
-
listFlags: [
|
|
21316
|
+
name: "occurrences",
|
|
21317
|
+
path: "/occurrences",
|
|
21318
|
+
readScope: "occurrences:read",
|
|
21319
|
+
returns: "Occurrence",
|
|
21320
|
+
canGet: false,
|
|
21321
|
+
listFlags: [
|
|
21322
|
+
{
|
|
21323
|
+
name: "template-id",
|
|
21324
|
+
description: "ULID of the recurring template (required)",
|
|
21325
|
+
required: true,
|
|
21326
|
+
query: "templateId"
|
|
21327
|
+
}
|
|
21328
|
+
]
|
|
21202
21329
|
},
|
|
21203
21330
|
{
|
|
21204
21331
|
name: "webhooks",
|
|
@@ -21210,6 +21337,18 @@ var ENTITIES = [
|
|
|
21210
21337
|
}
|
|
21211
21338
|
];
|
|
21212
21339
|
var entityByName = (name) => ENTITIES.find((e) => e.name === name);
|
|
21340
|
+
function entityOperations(e) {
|
|
21341
|
+
const ops = ["list"];
|
|
21342
|
+
if (e.canGet)
|
|
21343
|
+
ops.push("get");
|
|
21344
|
+
if (e.write?.createSchema)
|
|
21345
|
+
ops.push("create");
|
|
21346
|
+
if (e.write?.updateSchema)
|
|
21347
|
+
ops.push("update");
|
|
21348
|
+
if (e.write?.canDelete)
|
|
21349
|
+
ops.push("delete");
|
|
21350
|
+
return ops;
|
|
21351
|
+
}
|
|
21213
21352
|
|
|
21214
21353
|
// src/registry.ts
|
|
21215
21354
|
var GLOBAL_FLAGS = [
|
|
@@ -21227,7 +21366,17 @@ var GLOBAL_FLAGS = [
|
|
|
21227
21366
|
description: "API origin; overrides DO_API_URL and the config file."
|
|
21228
21367
|
},
|
|
21229
21368
|
{ name: "help", type: "boolean", description: "Show help for the command." },
|
|
21230
|
-
{ name: "version", type: "boolean", description: "Print the CLI version." }
|
|
21369
|
+
{ name: "version", type: "boolean", description: "Print the CLI version." },
|
|
21370
|
+
{
|
|
21371
|
+
name: "data",
|
|
21372
|
+
type: "string",
|
|
21373
|
+
description: `JSON object body for create/update (e.g. --data '{"title":"Buy milk"}').`
|
|
21374
|
+
},
|
|
21375
|
+
{
|
|
21376
|
+
name: "allow-destructive",
|
|
21377
|
+
type: "boolean",
|
|
21378
|
+
description: "Permit a destructive verb (delete/revoke/remove-member). Off by default \u2014 a guardrail for autonomous agents."
|
|
21379
|
+
}
|
|
21231
21380
|
];
|
|
21232
21381
|
var COMMAND_SPECS = [
|
|
21233
21382
|
{
|
|
@@ -21288,6 +21437,199 @@ var COMMAND_SPECS = [
|
|
|
21288
21437
|
scopes: ["habits:write"],
|
|
21289
21438
|
returns: "HabitSummary"
|
|
21290
21439
|
},
|
|
21440
|
+
{
|
|
21441
|
+
path: ["settings", "get"],
|
|
21442
|
+
summary: "Show the caller's settings for the Space.",
|
|
21443
|
+
args: [],
|
|
21444
|
+
flags: [],
|
|
21445
|
+
scopes: ["settings:read"],
|
|
21446
|
+
returns: "UserSettings",
|
|
21447
|
+
notes: "Needs a key minted while logged in (it carries the user identity)."
|
|
21448
|
+
},
|
|
21449
|
+
{
|
|
21450
|
+
path: ["settings", "update"],
|
|
21451
|
+
summary: "Patch the caller's settings.",
|
|
21452
|
+
args: [],
|
|
21453
|
+
flags: [{ name: "data", type: "string", description: "JSON of fields to change." }],
|
|
21454
|
+
scopes: ["settings:write"],
|
|
21455
|
+
returns: "UserSettings"
|
|
21456
|
+
},
|
|
21457
|
+
{
|
|
21458
|
+
path: ["invite", "create"],
|
|
21459
|
+
summary: "Create a Space invite (email/link/guest).",
|
|
21460
|
+
args: [],
|
|
21461
|
+
flags: [
|
|
21462
|
+
{
|
|
21463
|
+
name: "data",
|
|
21464
|
+
type: "string",
|
|
21465
|
+
description: 'JSON CreateInviteInput, e.g. {"type":"link"} or {"type":"email","email":"a@b.c"}.'
|
|
21466
|
+
}
|
|
21467
|
+
],
|
|
21468
|
+
scopes: ["members:write"],
|
|
21469
|
+
returns: "Invite"
|
|
21470
|
+
},
|
|
21471
|
+
{
|
|
21472
|
+
path: ["invite", "list"],
|
|
21473
|
+
summary: "List the Space's invites.",
|
|
21474
|
+
args: [],
|
|
21475
|
+
flags: [],
|
|
21476
|
+
scopes: ["members:read"],
|
|
21477
|
+
returns: "Invite"
|
|
21478
|
+
},
|
|
21479
|
+
{
|
|
21480
|
+
path: ["invite", "revoke"],
|
|
21481
|
+
summary: "Revoke an invite (destructive).",
|
|
21482
|
+
args: [{ name: "id", required: true, description: "Invite ULID." }],
|
|
21483
|
+
flags: [
|
|
21484
|
+
{ name: "allow-destructive", type: "boolean", description: "Required to actually revoke." }
|
|
21485
|
+
],
|
|
21486
|
+
scopes: ["members:write"],
|
|
21487
|
+
returns: "None",
|
|
21488
|
+
notes: "Destructive \u2014 gated behind --allow-destructive + a members:write key."
|
|
21489
|
+
},
|
|
21490
|
+
{
|
|
21491
|
+
path: ["member", "list"],
|
|
21492
|
+
summary: "List the Space's members.",
|
|
21493
|
+
args: [],
|
|
21494
|
+
flags: [],
|
|
21495
|
+
scopes: ["members:read"],
|
|
21496
|
+
returns: "MemberView"
|
|
21497
|
+
},
|
|
21498
|
+
{
|
|
21499
|
+
path: ["member", "role"],
|
|
21500
|
+
summary: "Set a member's role.",
|
|
21501
|
+
args: [{ name: "id", required: true, description: "Member ULID (tenant_members.id)." }],
|
|
21502
|
+
flags: [
|
|
21503
|
+
{ name: "role", type: "string", description: "Base role: owner|admin|member|guest." },
|
|
21504
|
+
{ name: "data", type: "string", description: "JSON SetMemberRoleInput (overrides --role)." }
|
|
21505
|
+
],
|
|
21506
|
+
scopes: ["members:write"],
|
|
21507
|
+
returns: "MemberView"
|
|
21508
|
+
},
|
|
21509
|
+
{
|
|
21510
|
+
path: ["member", "remove"],
|
|
21511
|
+
summary: "Remove a member from the Space (destructive).",
|
|
21512
|
+
args: [{ name: "id", required: true, description: "Member ULID." }],
|
|
21513
|
+
flags: [
|
|
21514
|
+
{ name: "allow-destructive", type: "boolean", description: "Required to actually remove." }
|
|
21515
|
+
],
|
|
21516
|
+
scopes: ["members:write"],
|
|
21517
|
+
returns: "None",
|
|
21518
|
+
notes: "Destructive \u2014 gated behind --allow-destructive + a members:write key."
|
|
21519
|
+
},
|
|
21520
|
+
{
|
|
21521
|
+
path: ["share"],
|
|
21522
|
+
summary: "Share a node with a member or role.",
|
|
21523
|
+
args: [{ name: "node", required: true, description: "Node ULID to share." }],
|
|
21524
|
+
flags: [
|
|
21525
|
+
{ name: "to", type: "string", description: "Subject ULID (member id, or role id)." },
|
|
21526
|
+
{
|
|
21527
|
+
name: "cap",
|
|
21528
|
+
type: "string",
|
|
21529
|
+
description: "Capability: view|comment|edit|admin.",
|
|
21530
|
+
default: "view"
|
|
21531
|
+
},
|
|
21532
|
+
{
|
|
21533
|
+
name: "subject-type",
|
|
21534
|
+
type: "string",
|
|
21535
|
+
description: "member (default) or role.",
|
|
21536
|
+
default: "member"
|
|
21537
|
+
}
|
|
21538
|
+
],
|
|
21539
|
+
scopes: ["sharing:write"],
|
|
21540
|
+
returns: "NodeGrantView"
|
|
21541
|
+
},
|
|
21542
|
+
{
|
|
21543
|
+
path: ["share", "list"],
|
|
21544
|
+
summary: "List the grants on a node.",
|
|
21545
|
+
args: [{ name: "node", required: true, description: "Node ULID." }],
|
|
21546
|
+
flags: [],
|
|
21547
|
+
scopes: ["sharing:read"],
|
|
21548
|
+
returns: "NodeGrantView"
|
|
21549
|
+
},
|
|
21550
|
+
{
|
|
21551
|
+
path: ["share", "revoke"],
|
|
21552
|
+
summary: "Revoke a node grant (destructive).",
|
|
21553
|
+
args: [
|
|
21554
|
+
{ name: "node", required: true, description: "Node ULID." },
|
|
21555
|
+
{ name: "grant", required: true, description: "Grant ULID." }
|
|
21556
|
+
],
|
|
21557
|
+
flags: [
|
|
21558
|
+
{ name: "allow-destructive", type: "boolean", description: "Required to actually revoke." }
|
|
21559
|
+
],
|
|
21560
|
+
scopes: ["sharing:write"],
|
|
21561
|
+
returns: "None",
|
|
21562
|
+
notes: "Destructive \u2014 gated behind --allow-destructive + a sharing:write key."
|
|
21563
|
+
},
|
|
21564
|
+
{
|
|
21565
|
+
path: ["assign"],
|
|
21566
|
+
summary: "Assign/delegate a node to a member.",
|
|
21567
|
+
args: [{ name: "node", required: true, description: "Node ULID to assign." }],
|
|
21568
|
+
flags: [
|
|
21569
|
+
{ name: "to", type: "string", description: "Member ULID to assign to." },
|
|
21570
|
+
{ name: "cap", type: "string", description: "Capability (default edit).", default: "edit" }
|
|
21571
|
+
],
|
|
21572
|
+
scopes: ["sharing:write"],
|
|
21573
|
+
returns: "NodeGrantView"
|
|
21574
|
+
},
|
|
21575
|
+
{
|
|
21576
|
+
path: ["comment"],
|
|
21577
|
+
summary: "Post a comment on a node.",
|
|
21578
|
+
args: [
|
|
21579
|
+
{ name: "node", required: true, description: "Node ULID." },
|
|
21580
|
+
{ name: "body", required: true, description: "Comment text." }
|
|
21581
|
+
],
|
|
21582
|
+
flags: [],
|
|
21583
|
+
scopes: ["comments:write"],
|
|
21584
|
+
returns: "CommentView"
|
|
21585
|
+
},
|
|
21586
|
+
{
|
|
21587
|
+
path: ["comment", "list"],
|
|
21588
|
+
summary: "List a node's comment thread.",
|
|
21589
|
+
args: [{ name: "node", required: true, description: "Node ULID." }],
|
|
21590
|
+
flags: [],
|
|
21591
|
+
scopes: ["comments:read"],
|
|
21592
|
+
returns: "CommentView"
|
|
21593
|
+
},
|
|
21594
|
+
{
|
|
21595
|
+
path: ["role", "list"],
|
|
21596
|
+
summary: "List the Space's custom roles.",
|
|
21597
|
+
args: [],
|
|
21598
|
+
flags: [],
|
|
21599
|
+
scopes: ["roles:read"],
|
|
21600
|
+
returns: "RoleView"
|
|
21601
|
+
},
|
|
21602
|
+
{
|
|
21603
|
+
path: ["link", "create"],
|
|
21604
|
+
summary: "Mint a public share-link to a node.",
|
|
21605
|
+
args: [{ name: "node", required: true, description: "Node ULID to link." }],
|
|
21606
|
+
flags: [
|
|
21607
|
+
{ name: "cap", type: "string", description: "Capability (default view).", default: "view" },
|
|
21608
|
+
{ name: "expires", type: "string", description: "Expiry (ISO date/time)." }
|
|
21609
|
+
],
|
|
21610
|
+
scopes: ["links:write"],
|
|
21611
|
+
returns: "CreateShareLinkResult",
|
|
21612
|
+
notes: "The signed token is returned once, in the create response."
|
|
21613
|
+
},
|
|
21614
|
+
{
|
|
21615
|
+
path: ["link", "list"],
|
|
21616
|
+
summary: "List the share-links for a node.",
|
|
21617
|
+
args: [{ name: "node", required: true, description: "Node ULID." }],
|
|
21618
|
+
flags: [],
|
|
21619
|
+
scopes: ["links:read"],
|
|
21620
|
+
returns: "ShareLinkPublic"
|
|
21621
|
+
},
|
|
21622
|
+
{
|
|
21623
|
+
path: ["link", "revoke"],
|
|
21624
|
+
summary: "Revoke a share-link (destructive).",
|
|
21625
|
+
args: [{ name: "id", required: true, description: "Share-link ULID." }],
|
|
21626
|
+
flags: [
|
|
21627
|
+
{ name: "allow-destructive", type: "boolean", description: "Required to actually revoke." }
|
|
21628
|
+
],
|
|
21629
|
+
scopes: ["links:write"],
|
|
21630
|
+
returns: "ShareLinkPublic",
|
|
21631
|
+
notes: "Destructive \u2014 gated behind --allow-destructive + a links:write key."
|
|
21632
|
+
},
|
|
21291
21633
|
{
|
|
21292
21634
|
path: ["whoami"],
|
|
21293
21635
|
summary: "Show the tenant and scopes the configured key resolves to.",
|
|
@@ -21402,9 +21744,16 @@ function buildManifest() {
|
|
|
21402
21744
|
})),
|
|
21403
21745
|
entities: ENTITIES.map((e) => ({
|
|
21404
21746
|
name: e.name,
|
|
21405
|
-
operations: e
|
|
21747
|
+
operations: entityOperations(e),
|
|
21406
21748
|
path: `${API_PREFIX}${e.path}`,
|
|
21407
21749
|
readScope: e.readScope,
|
|
21750
|
+
...e.write ? { writeScope: e.write.scope } : {},
|
|
21751
|
+
...e.write?.createInput || e.write?.updateInput ? {
|
|
21752
|
+
inputs: {
|
|
21753
|
+
...e.write.createInput ? { create: e.write.createInput } : {},
|
|
21754
|
+
...e.write.updateInput ? { update: e.write.updateInput } : {}
|
|
21755
|
+
}
|
|
21756
|
+
} : {},
|
|
21408
21757
|
returns: e.returns,
|
|
21409
21758
|
listFlags: e.listFlags.map((f) => ({
|
|
21410
21759
|
name: f.name,
|
|
@@ -21691,30 +22040,57 @@ async function dispatch(positionals, flags, io, mode, config2) {
|
|
|
21691
22040
|
}
|
|
21692
22041
|
const entity = entityByName(positionals[0] ?? "");
|
|
21693
22042
|
if (entity) {
|
|
21694
|
-
|
|
21695
|
-
const rest = positionals.slice(2);
|
|
21696
|
-
const client = requireClient();
|
|
21697
|
-
if (op === "list") {
|
|
21698
|
-
const query = {};
|
|
21699
|
-
for (const f of entity.listFlags) {
|
|
21700
|
-
const value = flagString(flags, f.name);
|
|
21701
|
-
if (value !== undefined)
|
|
21702
|
-
query[f.query ?? f.name] = value;
|
|
21703
|
-
else if (f.required)
|
|
21704
|
-
throw usageError(`\`${entity.name} list\` requires --${f.name}`);
|
|
21705
|
-
}
|
|
21706
|
-
return { data: await client.get(entity.path, { query }), render: renderList };
|
|
21707
|
-
}
|
|
21708
|
-
if (op === "get") {
|
|
21709
|
-
if (!entity.canGet)
|
|
21710
|
-
throw usageError(`\`${entity.name}\` has no get-by-id; use \`${entity.name} list\``);
|
|
21711
|
-
const id = requireArg(rest, 0, "id");
|
|
21712
|
-
return { data: await client.get(`${entity.path}/${id}`), render: renderRecord };
|
|
21713
|
-
}
|
|
21714
|
-
throw usageError(`unknown operation for ${entity.name}: \`${op ?? ""}\` (use list or get)`);
|
|
22043
|
+
return dispatchEntity(entity, positionals.slice(1), flags, requireClient);
|
|
21715
22044
|
}
|
|
21716
22045
|
throw usageError(`unknown command: \`${positionals.join(" ")}\` \u2014 run \`do help\``);
|
|
21717
22046
|
}
|
|
22047
|
+
async function dispatchEntity(entity, rest, flags, requireClient) {
|
|
22048
|
+
const op = rest[0];
|
|
22049
|
+
const args = rest.slice(1);
|
|
22050
|
+
const client = requireClient();
|
|
22051
|
+
if (op === "list") {
|
|
22052
|
+
const query = {};
|
|
22053
|
+
for (const f of entity.listFlags) {
|
|
22054
|
+
const value = flagString(flags, f.name);
|
|
22055
|
+
if (value !== undefined)
|
|
22056
|
+
query[f.query ?? f.name] = value;
|
|
22057
|
+
else if (f.required)
|
|
22058
|
+
throw usageError(`\`${entity.name} list\` requires --${f.name}`);
|
|
22059
|
+
}
|
|
22060
|
+
return { data: await client.get(entity.path, { query }), render: renderList };
|
|
22061
|
+
}
|
|
22062
|
+
if (op === "get") {
|
|
22063
|
+
if (!entity.canGet)
|
|
22064
|
+
throw usageError(`\`${entity.name}\` has no get-by-id; use \`${entity.name} list\``);
|
|
22065
|
+
const id = requireArg(args, 0, "id");
|
|
22066
|
+
return { data: await client.get(`${entity.path}/${id}`), render: renderRecord };
|
|
22067
|
+
}
|
|
22068
|
+
if (op === "create") {
|
|
22069
|
+
const write = requireWrite(entity, "create");
|
|
22070
|
+
if (!write.createSchema)
|
|
22071
|
+
throw usageError(`\`${entity.name}\` cannot be created`);
|
|
22072
|
+
const body = parseInput(write.createSchema, { id: ulid3(), ...readDataFlag(flags) });
|
|
22073
|
+
return { data: await client.post(entity.path, body), render: renderRecord };
|
|
22074
|
+
}
|
|
22075
|
+
if (op === "update") {
|
|
22076
|
+
const write = requireWrite(entity, "update");
|
|
22077
|
+
if (!write.updateSchema)
|
|
22078
|
+
throw usageError(`\`${entity.name}\` cannot be updated`);
|
|
22079
|
+
const id = requireArg(args, 0, "id");
|
|
22080
|
+
const body = parseInput(write.updateSchema, readDataFlag(flags));
|
|
22081
|
+
return { data: await client.patch(`${entity.path}/${id}`, body), render: renderRecord };
|
|
22082
|
+
}
|
|
22083
|
+
if (op === "delete") {
|
|
22084
|
+
const write = requireWrite(entity, "delete");
|
|
22085
|
+
if (!write.canDelete)
|
|
22086
|
+
throw usageError(`\`${entity.name}\` cannot be deleted`);
|
|
22087
|
+
const id = requireArg(args, 0, "id");
|
|
22088
|
+
assertDestructiveAllowed(flags, `delete ${entity.name} ${id}`);
|
|
22089
|
+
await client.delete(`${entity.path}/${id}`);
|
|
22090
|
+
return { data: { ok: true, deleted: id }, render: renderRecord };
|
|
22091
|
+
}
|
|
22092
|
+
throw usageError(`unknown operation for ${entity.name}: \`${op ?? ""}\` (use ${entityOperations(entity).join(", ")})`);
|
|
22093
|
+
}
|
|
21718
22094
|
function runFixed(spec, ctx) {
|
|
21719
22095
|
const key = spec.path.join(" ");
|
|
21720
22096
|
const handler = HANDLERS[key];
|
|
@@ -21775,6 +22151,121 @@ var HANDLERS = {
|
|
|
21775
22151
|
render: renderHabit
|
|
21776
22152
|
};
|
|
21777
22153
|
},
|
|
22154
|
+
"settings get": async ({ requireClient }) => ({
|
|
22155
|
+
data: await requireClient().get("/settings"),
|
|
22156
|
+
render: renderRecord
|
|
22157
|
+
}),
|
|
22158
|
+
"settings update": async ({ flags, requireClient }) => {
|
|
22159
|
+
const body = parseInput(updateUserSettingsInputSchema, readDataFlag(flags));
|
|
22160
|
+
return { data: await requireClient().patch("/settings", body), render: renderRecord };
|
|
22161
|
+
},
|
|
22162
|
+
"invite create": async ({ flags, requireClient }) => {
|
|
22163
|
+
const body = parseInput(createInviteInputSchema, { id: ulid3(), ...readDataFlag(flags) });
|
|
22164
|
+
return { data: await requireClient().post("/invites", body), render: renderRecord };
|
|
22165
|
+
},
|
|
22166
|
+
"invite list": async ({ requireClient }) => ({
|
|
22167
|
+
data: await requireClient().get("/invites"),
|
|
22168
|
+
render: renderList
|
|
22169
|
+
}),
|
|
22170
|
+
"invite revoke": async ({ args, flags, requireClient }) => {
|
|
22171
|
+
const id = requireArg(args, 0, "invite id");
|
|
22172
|
+
assertDestructiveAllowed(flags, `revoke invite ${id}`);
|
|
22173
|
+
await requireClient().delete(`/invites/${id}`);
|
|
22174
|
+
return { data: { ok: true, revoked: id }, render: renderRecord };
|
|
22175
|
+
},
|
|
22176
|
+
"member list": async ({ requireClient }) => ({
|
|
22177
|
+
data: await requireClient().get("/members"),
|
|
22178
|
+
render: renderList
|
|
22179
|
+
}),
|
|
22180
|
+
"member role": async ({ args, flags, requireClient }) => {
|
|
22181
|
+
const id = requireArg(args, 0, "member id");
|
|
22182
|
+
const role = flagString(flags, "role");
|
|
22183
|
+
const raw = flagString(flags, "data") ? readDataFlag(flags) : role ? { role } : {};
|
|
22184
|
+
const body = parseInput(setMemberRoleInputSchema, raw);
|
|
22185
|
+
return { data: await requireClient().patch(`/members/${id}/role`, body), render: renderRecord };
|
|
22186
|
+
},
|
|
22187
|
+
"member remove": async ({ args, flags, requireClient }) => {
|
|
22188
|
+
const id = requireArg(args, 0, "member id");
|
|
22189
|
+
assertDestructiveAllowed(flags, `remove member ${id}`);
|
|
22190
|
+
await requireClient().delete(`/members/${id}`);
|
|
22191
|
+
return { data: { ok: true, removed: id }, render: renderRecord };
|
|
22192
|
+
},
|
|
22193
|
+
share: async ({ args, flags, requireClient }) => {
|
|
22194
|
+
const node = requireArg(args, 0, "node id");
|
|
22195
|
+
const subjectId = requireFlag(flags, "to", "share needs --to <member-or-role-id>");
|
|
22196
|
+
const body = parseInput(createNodeGrantInputSchema, {
|
|
22197
|
+
id: ulid3(),
|
|
22198
|
+
subjectType: flagString(flags, "subject-type") ?? "member",
|
|
22199
|
+
subjectId,
|
|
22200
|
+
capability: flagString(flags, "cap") ?? "view"
|
|
22201
|
+
});
|
|
22202
|
+
return {
|
|
22203
|
+
data: await requireClient().post(`/sharing/nodes/${node}/grants`, body),
|
|
22204
|
+
render: renderRecord
|
|
22205
|
+
};
|
|
22206
|
+
},
|
|
22207
|
+
"share list": async ({ args, requireClient }) => {
|
|
22208
|
+
const node = requireArg(args, 0, "node id");
|
|
22209
|
+
return { data: await requireClient().get(`/sharing/nodes/${node}/grants`), render: renderList };
|
|
22210
|
+
},
|
|
22211
|
+
"share revoke": async ({ args, flags, requireClient }) => {
|
|
22212
|
+
const node = requireArg(args, 0, "node id");
|
|
22213
|
+
const grant = requireArg(args, 1, "grant id");
|
|
22214
|
+
assertDestructiveAllowed(flags, `revoke grant ${grant}`);
|
|
22215
|
+
await requireClient().delete(`/sharing/nodes/${node}/grants/${grant}`);
|
|
22216
|
+
return { data: { ok: true, revoked: grant }, render: renderRecord };
|
|
22217
|
+
},
|
|
22218
|
+
assign: async ({ args, flags, requireClient }) => {
|
|
22219
|
+
const node = requireArg(args, 0, "node id");
|
|
22220
|
+
const memberId = requireFlag(flags, "to", "assign needs --to <member-id>");
|
|
22221
|
+
const body = parseInput(assignNodeInputSchema, {
|
|
22222
|
+
id: ulid3(),
|
|
22223
|
+
memberId,
|
|
22224
|
+
...flagString(flags, "cap") ? { capability: flagString(flags, "cap") } : {}
|
|
22225
|
+
});
|
|
22226
|
+
return {
|
|
22227
|
+
data: await requireClient().put(`/sharing/nodes/${node}/assignee`, body),
|
|
22228
|
+
render: renderRecord
|
|
22229
|
+
};
|
|
22230
|
+
},
|
|
22231
|
+
comment: async ({ args, requireClient }) => {
|
|
22232
|
+
const node = requireArg(args, 0, "node id");
|
|
22233
|
+
const body = args.slice(1).join(" ").trim();
|
|
22234
|
+
if (body.length === 0)
|
|
22235
|
+
throw usageError('comment needs text, e.g. `do comment <node> "Nice!"`');
|
|
22236
|
+
const input = parseInput(createCommentInputSchema, { id: ulid3(), nodeId: node, body });
|
|
22237
|
+
return { data: await requireClient().post("/comments", input), render: renderRecord };
|
|
22238
|
+
},
|
|
22239
|
+
"comment list": async ({ args, requireClient }) => {
|
|
22240
|
+
const node = requireArg(args, 0, "node id");
|
|
22241
|
+
return {
|
|
22242
|
+
data: await requireClient().get("/comments", { query: { nodeId: node } }),
|
|
22243
|
+
render: renderList
|
|
22244
|
+
};
|
|
22245
|
+
},
|
|
22246
|
+
"role list": async ({ requireClient }) => ({
|
|
22247
|
+
data: await requireClient().get("/roles"),
|
|
22248
|
+
render: renderList
|
|
22249
|
+
}),
|
|
22250
|
+
"link create": async ({ args, flags, requireClient }) => {
|
|
22251
|
+
const node = requireArg(args, 0, "node id");
|
|
22252
|
+
const body = parseInput(createShareLinkInputSchema, {
|
|
22253
|
+
id: ulid3(),
|
|
22254
|
+
nodeId: node,
|
|
22255
|
+
...flagString(flags, "cap") ? { capability: flagString(flags, "cap") } : {},
|
|
22256
|
+
...flagString(flags, "expires") ? { expiresAt: flagString(flags, "expires") } : {}
|
|
22257
|
+
});
|
|
22258
|
+
return { data: await requireClient().post("/share-links", body), render: renderRecord };
|
|
22259
|
+
},
|
|
22260
|
+
"link list": async ({ args, requireClient }) => {
|
|
22261
|
+
const node = requireArg(args, 0, "node id");
|
|
22262
|
+
return { data: await requireClient().get(`/share-links/node/${node}`), render: renderList };
|
|
22263
|
+
},
|
|
22264
|
+
"link revoke": async ({ args, flags, requireClient }) => {
|
|
22265
|
+
const id = requireArg(args, 0, "share-link id");
|
|
22266
|
+
assertDestructiveAllowed(flags, `revoke share-link ${id}`);
|
|
22267
|
+
return { data: await requireClient().delete(`/share-links/${id}`), render: renderRecord };
|
|
22268
|
+
},
|
|
21778
22269
|
whoami: async ({ requireClient }) => ({
|
|
21779
22270
|
data: await requireClient().get("/whoami"),
|
|
21780
22271
|
render: renderWhoami
|
|
@@ -21849,6 +22340,12 @@ function requireArg(args, index2, name) {
|
|
|
21849
22340
|
}
|
|
21850
22341
|
return value;
|
|
21851
22342
|
}
|
|
22343
|
+
function requireFlag(flags, name, hint) {
|
|
22344
|
+
const value = flagString(flags, name);
|
|
22345
|
+
if (value === undefined || value.length === 0)
|
|
22346
|
+
throw usageError(hint);
|
|
22347
|
+
return value;
|
|
22348
|
+
}
|
|
21852
22349
|
function assertConfigKey(key) {
|
|
21853
22350
|
if (!CONFIG_KEYS.includes(key)) {
|
|
21854
22351
|
throw usageError(`unknown config key \`${key}\` \u2014 valid keys: ${CONFIG_KEYS.join(", ")}`);
|
|
@@ -21882,6 +22379,32 @@ function redact(secret) {
|
|
|
21882
22379
|
return "********";
|
|
21883
22380
|
return `${secret.slice(0, 6)}\u2026${secret.slice(-2)}`;
|
|
21884
22381
|
}
|
|
22382
|
+
function requireWrite(entity, op) {
|
|
22383
|
+
if (!entity.write) {
|
|
22384
|
+
throw usageError(`\`${entity.name}\` is read-only \u2014 \`${op}\` is not available`);
|
|
22385
|
+
}
|
|
22386
|
+
return entity.write;
|
|
22387
|
+
}
|
|
22388
|
+
function readDataFlag(flags) {
|
|
22389
|
+
const raw = flagString(flags, "data");
|
|
22390
|
+
if (raw === undefined)
|
|
22391
|
+
return {};
|
|
22392
|
+
let parsed;
|
|
22393
|
+
try {
|
|
22394
|
+
parsed = JSON.parse(raw);
|
|
22395
|
+
} catch (err) {
|
|
22396
|
+
throw usageError(`--data is not valid JSON: ${err.message}`);
|
|
22397
|
+
}
|
|
22398
|
+
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
22399
|
+
throw usageError("--data must be a JSON object");
|
|
22400
|
+
}
|
|
22401
|
+
return parsed;
|
|
22402
|
+
}
|
|
22403
|
+
function assertDestructiveAllowed(flags, action) {
|
|
22404
|
+
if (!flagBool(flags, "allow-destructive")) {
|
|
22405
|
+
throw new CliError("destructive_blocked", `refusing to ${action} without --allow-destructive (a guardrail for autonomous agents)`, EXIT.USAGE);
|
|
22406
|
+
}
|
|
22407
|
+
}
|
|
21885
22408
|
|
|
21886
22409
|
// src/run.ts
|
|
21887
22410
|
async function run(argv, io) {
|