@robbeverhelst/do 0.0.1 → 1.2.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.
Files changed (3) hide show
  1. package/README.md +6 -3
  2. package/dist/do.js +607 -59
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -1,12 +1,15 @@
1
- # @do/cli
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/cli help
9
- bunx @do/cli today
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 = ["json", "quiet", "verbose", "help", "version"];
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(),
@@ -20037,6 +20045,31 @@ var ingestSyncResultSchema = exports_external.object({
20037
20045
  skipped: exports_external.number().int().nonnegative(),
20038
20046
  resources: exports_external.array(ingestResourceResultSchema)
20039
20047
  });
20048
+ // ../../packages/schema/src/contracts/github.ts
20049
+ var GITHUB_RESOURCES = ["commits", "pull_requests", "issues"];
20050
+ var githubResourceSchema = exports_external.enum(GITHUB_RESOURCES);
20051
+ var githubBase = {
20052
+ repo: exports_external.string().min(1),
20053
+ url: exports_external.string().min(1)
20054
+ };
20055
+ var githubCommitValueSchema = exports_external.object({
20056
+ ...githubBase,
20057
+ sha: exports_external.string().min(1),
20058
+ message: exports_external.string().default("")
20059
+ }).strict();
20060
+ var githubPullRequestValueSchema = exports_external.object({
20061
+ ...githubBase,
20062
+ number: exports_external.number().int().positive(),
20063
+ title: exports_external.string().default(""),
20064
+ state: exports_external.string().min(1),
20065
+ merged: exports_external.boolean().default(false)
20066
+ }).strict();
20067
+ var githubIssueValueSchema = exports_external.object({
20068
+ ...githubBase,
20069
+ number: exports_external.number().int().positive(),
20070
+ title: exports_external.string().default(""),
20071
+ state: exports_external.string().min(1)
20072
+ }).strict();
20040
20073
  // ../../packages/schema/src/contracts/strava.ts
20041
20074
  var stravaActivitySchema = exports_external.object({
20042
20075
  id: exports_external.number(),
@@ -20097,7 +20130,23 @@ var apiKeyScopeSchema = exports_external.enum([
20097
20130
  "notes:read",
20098
20131
  "notes:write",
20099
20132
  "contacts:read",
20100
- "contacts:write"
20133
+ "contacts:write",
20134
+ "projects:read",
20135
+ "projects:write",
20136
+ "routines:read",
20137
+ "routines:write",
20138
+ "settings:read",
20139
+ "settings:write",
20140
+ "calendar:read",
20141
+ "members:read",
20142
+ "members:write",
20143
+ "sharing:read",
20144
+ "sharing:write",
20145
+ "comments:read",
20146
+ "comments:write",
20147
+ "roles:read",
20148
+ "links:read",
20149
+ "links:write"
20101
20150
  ]);
20102
20151
  var apiKeySchema = createSelectSchema(apiKeys, {
20103
20152
  scopes: exports_external.array(apiKeyScopeSchema),
@@ -20115,6 +20164,7 @@ var createApiKeyResultSchema = apiKeyPublicSchema.extend({ key: exports_external
20115
20164
  var apiKeyContextSchema = exports_external.object({
20116
20165
  apiKeyId: exports_external.string(),
20117
20166
  tenantId: exports_external.string(),
20167
+ userId: exports_external.string().nullable(),
20118
20168
  scopes: exports_external.array(apiKeyScopeSchema)
20119
20169
  });
20120
20170
  // ../../packages/schema/src/contracts/webhooks.ts
@@ -20965,7 +21015,7 @@ function parseCapture(input) {
20965
21015
 
20966
21016
  // src/meta.ts
20967
21017
  var CLI_NAME = "do";
20968
- var VERSION = "0.0.0";
21018
+ var VERSION = "1.2.0";
20969
21019
  var API_PREFIX = "/api/v1";
20970
21020
  var DEFAULT_API_URL = "http://localhost:3001";
20971
21021
 
@@ -20984,6 +21034,12 @@ class DoClient {
20984
21034
  patch(path, body) {
20985
21035
  return this.request("PATCH", path, { body });
20986
21036
  }
21037
+ put(path, body) {
21038
+ return this.request("PUT", path, { body });
21039
+ }
21040
+ delete(path) {
21041
+ return this.request("DELETE", path, {});
21042
+ }
20987
21043
  url(path, query) {
20988
21044
  const origin = this.opts.apiUrl.replace(/\/+$/, "");
20989
21045
  const url2 = new URL(`${origin}${API_PREFIX}${path}`);
@@ -21127,7 +21183,15 @@ var ENTITIES = [
21127
21183
  canGet: true,
21128
21184
  listFlags: [
21129
21185
  { name: "filter", description: "all (default) or unsorted (the GTD inbox)", query: "filter" }
21130
- ]
21186
+ ],
21187
+ write: {
21188
+ scope: "tasks:write",
21189
+ createSchema: createTaskInputSchema,
21190
+ createInput: "CreateTaskInput",
21191
+ updateSchema: updateTaskInputSchema,
21192
+ updateInput: "UpdateTaskInput",
21193
+ canDelete: true
21194
+ }
21131
21195
  },
21132
21196
  {
21133
21197
  name: "habits",
@@ -21135,22 +21199,79 @@ var ENTITIES = [
21135
21199
  readScope: "habits:read",
21136
21200
  returns: "HabitSummary",
21137
21201
  canGet: true,
21138
- listFlags: []
21202
+ listFlags: [],
21203
+ write: {
21204
+ scope: "habits:write",
21205
+ createSchema: createHabitInputSchema,
21206
+ createInput: "CreateHabitInput",
21207
+ updateSchema: updateHabitInputSchema,
21208
+ updateInput: "UpdateHabitInput",
21209
+ canDelete: true
21210
+ }
21139
21211
  },
21140
21212
  {
21141
- name: "occurrences",
21142
- path: "/occurrences",
21143
- readScope: "occurrences:read",
21144
- returns: "Occurrence",
21145
- canGet: false,
21146
- listFlags: [
21147
- {
21148
- name: "template-id",
21149
- description: "ULID of the recurring template (required)",
21150
- required: true,
21151
- query: "templateId"
21152
- }
21153
- ]
21213
+ name: "projects",
21214
+ path: "/projects",
21215
+ readScope: "projects:read",
21216
+ returns: "ProjectView",
21217
+ canGet: true,
21218
+ listFlags: [],
21219
+ write: {
21220
+ scope: "projects:write",
21221
+ createSchema: createProjectInputSchema,
21222
+ createInput: "CreateProjectInput",
21223
+ updateSchema: updateProjectInputSchema,
21224
+ updateInput: "UpdateProjectInput",
21225
+ canDelete: true
21226
+ }
21227
+ },
21228
+ {
21229
+ name: "routines",
21230
+ path: "/routines",
21231
+ readScope: "routines:read",
21232
+ returns: "RoutineView",
21233
+ canGet: true,
21234
+ listFlags: [],
21235
+ write: {
21236
+ scope: "routines:write",
21237
+ createSchema: createRoutineInputSchema,
21238
+ createInput: "CreateRoutineInput",
21239
+ updateSchema: updateRoutineInputSchema,
21240
+ updateInput: "UpdateRoutineInput",
21241
+ canDelete: true
21242
+ }
21243
+ },
21244
+ {
21245
+ name: "notes",
21246
+ path: "/notes",
21247
+ readScope: "notes:read",
21248
+ returns: "Note",
21249
+ canGet: true,
21250
+ listFlags: [],
21251
+ write: {
21252
+ scope: "notes:write",
21253
+ createSchema: createNoteInputSchema,
21254
+ createInput: "CreateNoteInput",
21255
+ updateSchema: updateNoteInputSchema,
21256
+ updateInput: "UpdateNoteInput",
21257
+ canDelete: true
21258
+ }
21259
+ },
21260
+ {
21261
+ name: "contacts",
21262
+ path: "/contacts",
21263
+ readScope: "contacts:read",
21264
+ returns: "Contact",
21265
+ canGet: true,
21266
+ listFlags: [],
21267
+ write: {
21268
+ scope: "contacts:write",
21269
+ createSchema: createContactInputSchema,
21270
+ createInput: "CreateContactInput",
21271
+ updateSchema: updateContactInputSchema,
21272
+ updateInput: "UpdateContactInput",
21273
+ canDelete: true
21274
+ }
21154
21275
  },
21155
21276
  {
21156
21277
  name: "shopping-lists",
@@ -21158,7 +21279,15 @@ var ENTITIES = [
21158
21279
  readScope: "shopping:read",
21159
21280
  returns: "ShoppingList",
21160
21281
  canGet: true,
21161
- listFlags: []
21282
+ listFlags: [],
21283
+ write: {
21284
+ scope: "shopping:write",
21285
+ createSchema: createShoppingListInputSchema,
21286
+ createInput: "CreateShoppingListInput",
21287
+ updateSchema: updateShoppingListInputSchema,
21288
+ updateInput: "UpdateShoppingListInput",
21289
+ canDelete: true
21290
+ }
21162
21291
  },
21163
21292
  {
21164
21293
  name: "inventory-items",
@@ -21166,7 +21295,15 @@ var ENTITIES = [
21166
21295
  readScope: "inventory:read",
21167
21296
  returns: "InventoryItem",
21168
21297
  canGet: true,
21169
- listFlags: []
21298
+ listFlags: [],
21299
+ write: {
21300
+ scope: "inventory:write",
21301
+ createSchema: createInventoryItemInputSchema,
21302
+ createInput: "CreateInventoryItemInput",
21303
+ updateSchema: updateInventoryItemInputSchema,
21304
+ updateInput: "UpdateInventoryItemInput",
21305
+ canDelete: true
21306
+ }
21170
21307
  },
21171
21308
  {
21172
21309
  name: "recipes",
@@ -21174,7 +21311,15 @@ var ENTITIES = [
21174
21311
  readScope: "meals:read",
21175
21312
  returns: "Recipe",
21176
21313
  canGet: true,
21177
- listFlags: []
21314
+ listFlags: [],
21315
+ write: {
21316
+ scope: "meals:write",
21317
+ createSchema: createRecipeInputSchema,
21318
+ createInput: "CreateRecipeInput",
21319
+ updateSchema: updateRecipeInputSchema,
21320
+ updateInput: "UpdateRecipeInput",
21321
+ canDelete: true
21322
+ }
21178
21323
  },
21179
21324
  {
21180
21325
  name: "meal-plans",
@@ -21182,23 +21327,30 @@ var ENTITIES = [
21182
21327
  readScope: "meals:read",
21183
21328
  returns: "MealPlan",
21184
21329
  canGet: true,
21185
- listFlags: []
21186
- },
21187
- {
21188
- name: "notes",
21189
- path: "/notes",
21190
- readScope: "notes:read",
21191
- returns: "Note",
21192
- canGet: true,
21193
- listFlags: []
21330
+ listFlags: [],
21331
+ write: {
21332
+ scope: "meals:write",
21333
+ createSchema: createMealPlanInputSchema,
21334
+ createInput: "CreateMealPlanInput",
21335
+ updateSchema: updateMealPlanInputSchema,
21336
+ updateInput: "UpdateMealPlanInput",
21337
+ canDelete: true
21338
+ }
21194
21339
  },
21195
21340
  {
21196
- name: "contacts",
21197
- path: "/contacts",
21198
- readScope: "contacts:read",
21199
- returns: "Contact",
21200
- canGet: true,
21201
- listFlags: []
21341
+ name: "occurrences",
21342
+ path: "/occurrences",
21343
+ readScope: "occurrences:read",
21344
+ returns: "Occurrence",
21345
+ canGet: false,
21346
+ listFlags: [
21347
+ {
21348
+ name: "template-id",
21349
+ description: "ULID of the recurring template (required)",
21350
+ required: true,
21351
+ query: "templateId"
21352
+ }
21353
+ ]
21202
21354
  },
21203
21355
  {
21204
21356
  name: "webhooks",
@@ -21210,6 +21362,18 @@ var ENTITIES = [
21210
21362
  }
21211
21363
  ];
21212
21364
  var entityByName = (name) => ENTITIES.find((e) => e.name === name);
21365
+ function entityOperations(e) {
21366
+ const ops = ["list"];
21367
+ if (e.canGet)
21368
+ ops.push("get");
21369
+ if (e.write?.createSchema)
21370
+ ops.push("create");
21371
+ if (e.write?.updateSchema)
21372
+ ops.push("update");
21373
+ if (e.write?.canDelete)
21374
+ ops.push("delete");
21375
+ return ops;
21376
+ }
21213
21377
 
21214
21378
  // src/registry.ts
21215
21379
  var GLOBAL_FLAGS = [
@@ -21227,7 +21391,17 @@ var GLOBAL_FLAGS = [
21227
21391
  description: "API origin; overrides DO_API_URL and the config file."
21228
21392
  },
21229
21393
  { name: "help", type: "boolean", description: "Show help for the command." },
21230
- { name: "version", type: "boolean", description: "Print the CLI version." }
21394
+ { name: "version", type: "boolean", description: "Print the CLI version." },
21395
+ {
21396
+ name: "data",
21397
+ type: "string",
21398
+ description: `JSON object body for create/update (e.g. --data '{"title":"Buy milk"}').`
21399
+ },
21400
+ {
21401
+ name: "allow-destructive",
21402
+ type: "boolean",
21403
+ description: "Permit a destructive verb (delete/revoke/remove-member). Off by default \u2014 a guardrail for autonomous agents."
21404
+ }
21231
21405
  ];
21232
21406
  var COMMAND_SPECS = [
21233
21407
  {
@@ -21288,6 +21462,199 @@ var COMMAND_SPECS = [
21288
21462
  scopes: ["habits:write"],
21289
21463
  returns: "HabitSummary"
21290
21464
  },
21465
+ {
21466
+ path: ["settings", "get"],
21467
+ summary: "Show the caller's settings for the Space.",
21468
+ args: [],
21469
+ flags: [],
21470
+ scopes: ["settings:read"],
21471
+ returns: "UserSettings",
21472
+ notes: "Needs a key minted while logged in (it carries the user identity)."
21473
+ },
21474
+ {
21475
+ path: ["settings", "update"],
21476
+ summary: "Patch the caller's settings.",
21477
+ args: [],
21478
+ flags: [{ name: "data", type: "string", description: "JSON of fields to change." }],
21479
+ scopes: ["settings:write"],
21480
+ returns: "UserSettings"
21481
+ },
21482
+ {
21483
+ path: ["invite", "create"],
21484
+ summary: "Create a Space invite (email/link/guest).",
21485
+ args: [],
21486
+ flags: [
21487
+ {
21488
+ name: "data",
21489
+ type: "string",
21490
+ description: 'JSON CreateInviteInput, e.g. {"type":"link"} or {"type":"email","email":"a@b.c"}.'
21491
+ }
21492
+ ],
21493
+ scopes: ["members:write"],
21494
+ returns: "Invite"
21495
+ },
21496
+ {
21497
+ path: ["invite", "list"],
21498
+ summary: "List the Space's invites.",
21499
+ args: [],
21500
+ flags: [],
21501
+ scopes: ["members:read"],
21502
+ returns: "Invite"
21503
+ },
21504
+ {
21505
+ path: ["invite", "revoke"],
21506
+ summary: "Revoke an invite (destructive).",
21507
+ args: [{ name: "id", required: true, description: "Invite ULID." }],
21508
+ flags: [
21509
+ { name: "allow-destructive", type: "boolean", description: "Required to actually revoke." }
21510
+ ],
21511
+ scopes: ["members:write"],
21512
+ returns: "None",
21513
+ notes: "Destructive \u2014 gated behind --allow-destructive + a members:write key."
21514
+ },
21515
+ {
21516
+ path: ["member", "list"],
21517
+ summary: "List the Space's members.",
21518
+ args: [],
21519
+ flags: [],
21520
+ scopes: ["members:read"],
21521
+ returns: "MemberView"
21522
+ },
21523
+ {
21524
+ path: ["member", "role"],
21525
+ summary: "Set a member's role.",
21526
+ args: [{ name: "id", required: true, description: "Member ULID (tenant_members.id)." }],
21527
+ flags: [
21528
+ { name: "role", type: "string", description: "Base role: owner|admin|member|guest." },
21529
+ { name: "data", type: "string", description: "JSON SetMemberRoleInput (overrides --role)." }
21530
+ ],
21531
+ scopes: ["members:write"],
21532
+ returns: "MemberView"
21533
+ },
21534
+ {
21535
+ path: ["member", "remove"],
21536
+ summary: "Remove a member from the Space (destructive).",
21537
+ args: [{ name: "id", required: true, description: "Member ULID." }],
21538
+ flags: [
21539
+ { name: "allow-destructive", type: "boolean", description: "Required to actually remove." }
21540
+ ],
21541
+ scopes: ["members:write"],
21542
+ returns: "None",
21543
+ notes: "Destructive \u2014 gated behind --allow-destructive + a members:write key."
21544
+ },
21545
+ {
21546
+ path: ["share"],
21547
+ summary: "Share a node with a member or role.",
21548
+ args: [{ name: "node", required: true, description: "Node ULID to share." }],
21549
+ flags: [
21550
+ { name: "to", type: "string", description: "Subject ULID (member id, or role id)." },
21551
+ {
21552
+ name: "cap",
21553
+ type: "string",
21554
+ description: "Capability: view|comment|edit|admin.",
21555
+ default: "view"
21556
+ },
21557
+ {
21558
+ name: "subject-type",
21559
+ type: "string",
21560
+ description: "member (default) or role.",
21561
+ default: "member"
21562
+ }
21563
+ ],
21564
+ scopes: ["sharing:write"],
21565
+ returns: "NodeGrantView"
21566
+ },
21567
+ {
21568
+ path: ["share", "list"],
21569
+ summary: "List the grants on a node.",
21570
+ args: [{ name: "node", required: true, description: "Node ULID." }],
21571
+ flags: [],
21572
+ scopes: ["sharing:read"],
21573
+ returns: "NodeGrantView"
21574
+ },
21575
+ {
21576
+ path: ["share", "revoke"],
21577
+ summary: "Revoke a node grant (destructive).",
21578
+ args: [
21579
+ { name: "node", required: true, description: "Node ULID." },
21580
+ { name: "grant", required: true, description: "Grant ULID." }
21581
+ ],
21582
+ flags: [
21583
+ { name: "allow-destructive", type: "boolean", description: "Required to actually revoke." }
21584
+ ],
21585
+ scopes: ["sharing:write"],
21586
+ returns: "None",
21587
+ notes: "Destructive \u2014 gated behind --allow-destructive + a sharing:write key."
21588
+ },
21589
+ {
21590
+ path: ["assign"],
21591
+ summary: "Assign/delegate a node to a member.",
21592
+ args: [{ name: "node", required: true, description: "Node ULID to assign." }],
21593
+ flags: [
21594
+ { name: "to", type: "string", description: "Member ULID to assign to." },
21595
+ { name: "cap", type: "string", description: "Capability (default edit).", default: "edit" }
21596
+ ],
21597
+ scopes: ["sharing:write"],
21598
+ returns: "NodeGrantView"
21599
+ },
21600
+ {
21601
+ path: ["comment"],
21602
+ summary: "Post a comment on a node.",
21603
+ args: [
21604
+ { name: "node", required: true, description: "Node ULID." },
21605
+ { name: "body", required: true, description: "Comment text." }
21606
+ ],
21607
+ flags: [],
21608
+ scopes: ["comments:write"],
21609
+ returns: "CommentView"
21610
+ },
21611
+ {
21612
+ path: ["comment", "list"],
21613
+ summary: "List a node's comment thread.",
21614
+ args: [{ name: "node", required: true, description: "Node ULID." }],
21615
+ flags: [],
21616
+ scopes: ["comments:read"],
21617
+ returns: "CommentView"
21618
+ },
21619
+ {
21620
+ path: ["role", "list"],
21621
+ summary: "List the Space's custom roles.",
21622
+ args: [],
21623
+ flags: [],
21624
+ scopes: ["roles:read"],
21625
+ returns: "RoleView"
21626
+ },
21627
+ {
21628
+ path: ["link", "create"],
21629
+ summary: "Mint a public share-link to a node.",
21630
+ args: [{ name: "node", required: true, description: "Node ULID to link." }],
21631
+ flags: [
21632
+ { name: "cap", type: "string", description: "Capability (default view).", default: "view" },
21633
+ { name: "expires", type: "string", description: "Expiry (ISO date/time)." }
21634
+ ],
21635
+ scopes: ["links:write"],
21636
+ returns: "CreateShareLinkResult",
21637
+ notes: "The signed token is returned once, in the create response."
21638
+ },
21639
+ {
21640
+ path: ["link", "list"],
21641
+ summary: "List the share-links for a node.",
21642
+ args: [{ name: "node", required: true, description: "Node ULID." }],
21643
+ flags: [],
21644
+ scopes: ["links:read"],
21645
+ returns: "ShareLinkPublic"
21646
+ },
21647
+ {
21648
+ path: ["link", "revoke"],
21649
+ summary: "Revoke a share-link (destructive).",
21650
+ args: [{ name: "id", required: true, description: "Share-link ULID." }],
21651
+ flags: [
21652
+ { name: "allow-destructive", type: "boolean", description: "Required to actually revoke." }
21653
+ ],
21654
+ scopes: ["links:write"],
21655
+ returns: "ShareLinkPublic",
21656
+ notes: "Destructive \u2014 gated behind --allow-destructive + a links:write key."
21657
+ },
21291
21658
  {
21292
21659
  path: ["whoami"],
21293
21660
  summary: "Show the tenant and scopes the configured key resolves to.",
@@ -21402,9 +21769,16 @@ function buildManifest() {
21402
21769
  })),
21403
21770
  entities: ENTITIES.map((e) => ({
21404
21771
  name: e.name,
21405
- operations: e.canGet ? ["list", "get"] : ["list"],
21772
+ operations: entityOperations(e),
21406
21773
  path: `${API_PREFIX}${e.path}`,
21407
21774
  readScope: e.readScope,
21775
+ ...e.write ? { writeScope: e.write.scope } : {},
21776
+ ...e.write?.createInput || e.write?.updateInput ? {
21777
+ inputs: {
21778
+ ...e.write.createInput ? { create: e.write.createInput } : {},
21779
+ ...e.write.updateInput ? { update: e.write.updateInput } : {}
21780
+ }
21781
+ } : {},
21408
21782
  returns: e.returns,
21409
21783
  listFlags: e.listFlags.map((f) => ({
21410
21784
  name: f.name,
@@ -21691,30 +22065,57 @@ async function dispatch(positionals, flags, io, mode, config2) {
21691
22065
  }
21692
22066
  const entity = entityByName(positionals[0] ?? "");
21693
22067
  if (entity) {
21694
- const op = positionals[1];
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)`);
22068
+ return dispatchEntity(entity, positionals.slice(1), flags, requireClient);
21715
22069
  }
21716
22070
  throw usageError(`unknown command: \`${positionals.join(" ")}\` \u2014 run \`do help\``);
21717
22071
  }
22072
+ async function dispatchEntity(entity, rest, flags, requireClient) {
22073
+ const op = rest[0];
22074
+ const args = rest.slice(1);
22075
+ const client = requireClient();
22076
+ if (op === "list") {
22077
+ const query = {};
22078
+ for (const f of entity.listFlags) {
22079
+ const value = flagString(flags, f.name);
22080
+ if (value !== undefined)
22081
+ query[f.query ?? f.name] = value;
22082
+ else if (f.required)
22083
+ throw usageError(`\`${entity.name} list\` requires --${f.name}`);
22084
+ }
22085
+ return { data: await client.get(entity.path, { query }), render: renderList };
22086
+ }
22087
+ if (op === "get") {
22088
+ if (!entity.canGet)
22089
+ throw usageError(`\`${entity.name}\` has no get-by-id; use \`${entity.name} list\``);
22090
+ const id = requireArg(args, 0, "id");
22091
+ return { data: await client.get(`${entity.path}/${id}`), render: renderRecord };
22092
+ }
22093
+ if (op === "create") {
22094
+ const write = requireWrite(entity, "create");
22095
+ if (!write.createSchema)
22096
+ throw usageError(`\`${entity.name}\` cannot be created`);
22097
+ const body = parseInput(write.createSchema, { id: ulid3(), ...readDataFlag(flags) });
22098
+ return { data: await client.post(entity.path, body), render: renderRecord };
22099
+ }
22100
+ if (op === "update") {
22101
+ const write = requireWrite(entity, "update");
22102
+ if (!write.updateSchema)
22103
+ throw usageError(`\`${entity.name}\` cannot be updated`);
22104
+ const id = requireArg(args, 0, "id");
22105
+ const body = parseInput(write.updateSchema, readDataFlag(flags));
22106
+ return { data: await client.patch(`${entity.path}/${id}`, body), render: renderRecord };
22107
+ }
22108
+ if (op === "delete") {
22109
+ const write = requireWrite(entity, "delete");
22110
+ if (!write.canDelete)
22111
+ throw usageError(`\`${entity.name}\` cannot be deleted`);
22112
+ const id = requireArg(args, 0, "id");
22113
+ assertDestructiveAllowed(flags, `delete ${entity.name} ${id}`);
22114
+ await client.delete(`${entity.path}/${id}`);
22115
+ return { data: { ok: true, deleted: id }, render: renderRecord };
22116
+ }
22117
+ throw usageError(`unknown operation for ${entity.name}: \`${op ?? ""}\` (use ${entityOperations(entity).join(", ")})`);
22118
+ }
21718
22119
  function runFixed(spec, ctx) {
21719
22120
  const key = spec.path.join(" ");
21720
22121
  const handler = HANDLERS[key];
@@ -21775,6 +22176,121 @@ var HANDLERS = {
21775
22176
  render: renderHabit
21776
22177
  };
21777
22178
  },
22179
+ "settings get": async ({ requireClient }) => ({
22180
+ data: await requireClient().get("/settings"),
22181
+ render: renderRecord
22182
+ }),
22183
+ "settings update": async ({ flags, requireClient }) => {
22184
+ const body = parseInput(updateUserSettingsInputSchema, readDataFlag(flags));
22185
+ return { data: await requireClient().patch("/settings", body), render: renderRecord };
22186
+ },
22187
+ "invite create": async ({ flags, requireClient }) => {
22188
+ const body = parseInput(createInviteInputSchema, { id: ulid3(), ...readDataFlag(flags) });
22189
+ return { data: await requireClient().post("/invites", body), render: renderRecord };
22190
+ },
22191
+ "invite list": async ({ requireClient }) => ({
22192
+ data: await requireClient().get("/invites"),
22193
+ render: renderList
22194
+ }),
22195
+ "invite revoke": async ({ args, flags, requireClient }) => {
22196
+ const id = requireArg(args, 0, "invite id");
22197
+ assertDestructiveAllowed(flags, `revoke invite ${id}`);
22198
+ await requireClient().delete(`/invites/${id}`);
22199
+ return { data: { ok: true, revoked: id }, render: renderRecord };
22200
+ },
22201
+ "member list": async ({ requireClient }) => ({
22202
+ data: await requireClient().get("/members"),
22203
+ render: renderList
22204
+ }),
22205
+ "member role": async ({ args, flags, requireClient }) => {
22206
+ const id = requireArg(args, 0, "member id");
22207
+ const role = flagString(flags, "role");
22208
+ const raw = flagString(flags, "data") ? readDataFlag(flags) : role ? { role } : {};
22209
+ const body = parseInput(setMemberRoleInputSchema, raw);
22210
+ return { data: await requireClient().patch(`/members/${id}/role`, body), render: renderRecord };
22211
+ },
22212
+ "member remove": async ({ args, flags, requireClient }) => {
22213
+ const id = requireArg(args, 0, "member id");
22214
+ assertDestructiveAllowed(flags, `remove member ${id}`);
22215
+ await requireClient().delete(`/members/${id}`);
22216
+ return { data: { ok: true, removed: id }, render: renderRecord };
22217
+ },
22218
+ share: async ({ args, flags, requireClient }) => {
22219
+ const node = requireArg(args, 0, "node id");
22220
+ const subjectId = requireFlag(flags, "to", "share needs --to <member-or-role-id>");
22221
+ const body = parseInput(createNodeGrantInputSchema, {
22222
+ id: ulid3(),
22223
+ subjectType: flagString(flags, "subject-type") ?? "member",
22224
+ subjectId,
22225
+ capability: flagString(flags, "cap") ?? "view"
22226
+ });
22227
+ return {
22228
+ data: await requireClient().post(`/sharing/nodes/${node}/grants`, body),
22229
+ render: renderRecord
22230
+ };
22231
+ },
22232
+ "share list": async ({ args, requireClient }) => {
22233
+ const node = requireArg(args, 0, "node id");
22234
+ return { data: await requireClient().get(`/sharing/nodes/${node}/grants`), render: renderList };
22235
+ },
22236
+ "share revoke": async ({ args, flags, requireClient }) => {
22237
+ const node = requireArg(args, 0, "node id");
22238
+ const grant = requireArg(args, 1, "grant id");
22239
+ assertDestructiveAllowed(flags, `revoke grant ${grant}`);
22240
+ await requireClient().delete(`/sharing/nodes/${node}/grants/${grant}`);
22241
+ return { data: { ok: true, revoked: grant }, render: renderRecord };
22242
+ },
22243
+ assign: async ({ args, flags, requireClient }) => {
22244
+ const node = requireArg(args, 0, "node id");
22245
+ const memberId = requireFlag(flags, "to", "assign needs --to <member-id>");
22246
+ const body = parseInput(assignNodeInputSchema, {
22247
+ id: ulid3(),
22248
+ memberId,
22249
+ ...flagString(flags, "cap") ? { capability: flagString(flags, "cap") } : {}
22250
+ });
22251
+ return {
22252
+ data: await requireClient().put(`/sharing/nodes/${node}/assignee`, body),
22253
+ render: renderRecord
22254
+ };
22255
+ },
22256
+ comment: async ({ args, requireClient }) => {
22257
+ const node = requireArg(args, 0, "node id");
22258
+ const body = args.slice(1).join(" ").trim();
22259
+ if (body.length === 0)
22260
+ throw usageError('comment needs text, e.g. `do comment <node> "Nice!"`');
22261
+ const input = parseInput(createCommentInputSchema, { id: ulid3(), nodeId: node, body });
22262
+ return { data: await requireClient().post("/comments", input), render: renderRecord };
22263
+ },
22264
+ "comment list": async ({ args, requireClient }) => {
22265
+ const node = requireArg(args, 0, "node id");
22266
+ return {
22267
+ data: await requireClient().get("/comments", { query: { nodeId: node } }),
22268
+ render: renderList
22269
+ };
22270
+ },
22271
+ "role list": async ({ requireClient }) => ({
22272
+ data: await requireClient().get("/roles"),
22273
+ render: renderList
22274
+ }),
22275
+ "link create": async ({ args, flags, requireClient }) => {
22276
+ const node = requireArg(args, 0, "node id");
22277
+ const body = parseInput(createShareLinkInputSchema, {
22278
+ id: ulid3(),
22279
+ nodeId: node,
22280
+ ...flagString(flags, "cap") ? { capability: flagString(flags, "cap") } : {},
22281
+ ...flagString(flags, "expires") ? { expiresAt: flagString(flags, "expires") } : {}
22282
+ });
22283
+ return { data: await requireClient().post("/share-links", body), render: renderRecord };
22284
+ },
22285
+ "link list": async ({ args, requireClient }) => {
22286
+ const node = requireArg(args, 0, "node id");
22287
+ return { data: await requireClient().get(`/share-links/node/${node}`), render: renderList };
22288
+ },
22289
+ "link revoke": async ({ args, flags, requireClient }) => {
22290
+ const id = requireArg(args, 0, "share-link id");
22291
+ assertDestructiveAllowed(flags, `revoke share-link ${id}`);
22292
+ return { data: await requireClient().delete(`/share-links/${id}`), render: renderRecord };
22293
+ },
21778
22294
  whoami: async ({ requireClient }) => ({
21779
22295
  data: await requireClient().get("/whoami"),
21780
22296
  render: renderWhoami
@@ -21849,6 +22365,12 @@ function requireArg(args, index2, name) {
21849
22365
  }
21850
22366
  return value;
21851
22367
  }
22368
+ function requireFlag(flags, name, hint) {
22369
+ const value = flagString(flags, name);
22370
+ if (value === undefined || value.length === 0)
22371
+ throw usageError(hint);
22372
+ return value;
22373
+ }
21852
22374
  function assertConfigKey(key) {
21853
22375
  if (!CONFIG_KEYS.includes(key)) {
21854
22376
  throw usageError(`unknown config key \`${key}\` \u2014 valid keys: ${CONFIG_KEYS.join(", ")}`);
@@ -21882,6 +22404,32 @@ function redact(secret) {
21882
22404
  return "********";
21883
22405
  return `${secret.slice(0, 6)}\u2026${secret.slice(-2)}`;
21884
22406
  }
22407
+ function requireWrite(entity, op) {
22408
+ if (!entity.write) {
22409
+ throw usageError(`\`${entity.name}\` is read-only \u2014 \`${op}\` is not available`);
22410
+ }
22411
+ return entity.write;
22412
+ }
22413
+ function readDataFlag(flags) {
22414
+ const raw = flagString(flags, "data");
22415
+ if (raw === undefined)
22416
+ return {};
22417
+ let parsed;
22418
+ try {
22419
+ parsed = JSON.parse(raw);
22420
+ } catch (err) {
22421
+ throw usageError(`--data is not valid JSON: ${err.message}`);
22422
+ }
22423
+ if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
22424
+ throw usageError("--data must be a JSON object");
22425
+ }
22426
+ return parsed;
22427
+ }
22428
+ function assertDestructiveAllowed(flags, action) {
22429
+ if (!flagBool(flags, "allow-destructive")) {
22430
+ throw new CliError("destructive_blocked", `refusing to ${action} without --allow-destructive (a guardrail for autonomous agents)`, EXIT.USAGE);
22431
+ }
22432
+ }
21885
22433
 
21886
22434
  // src/run.ts
21887
22435
  async function run(argv, io) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@robbeverhelst/do",
3
- "version": "0.0.1",
3
+ "version": "1.2.0",
4
4
  "private": false,
5
5
  "description": "do — the agent-drivable CLI over the public /api/v1 surface",
6
6
  "type": "module",