@ornncompute/cli 0.2.24 → 1.0.34

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 (90) hide show
  1. package/README.md +351 -426
  2. package/package.json +12 -6
  3. package/src/admin-capacity-reservations.mjs +305 -0
  4. package/src/admin-command-handlers.mjs +57 -0
  5. package/src/admin-enrollment.mjs +681 -0
  6. package/src/admin-mutations.mjs +187 -0
  7. package/src/admin-node-diagnostics.mjs +412 -0
  8. package/src/admin-node-lifecycle.mjs +905 -0
  9. package/src/admin-shell.mjs +456 -0
  10. package/src/api-client.mjs +81 -28
  11. package/src/atomic-output.mjs +112 -0
  12. package/src/auth-store.mjs +182 -11
  13. package/src/capability-catalog.mjs +7 -0
  14. package/src/catalog-shell.mjs +1363 -0
  15. package/src/cli.mjs +123 -10266
  16. package/src/connect-mode-handlers.mjs +170 -0
  17. package/src/device-auth.mjs +179 -62
  18. package/src/evidence-commands.mjs +1354 -0
  19. package/src/external-bucket-commands.mjs +1374 -0
  20. package/src/gpu-catalog.mjs +21 -0
  21. package/src/identity-file.mjs +28 -0
  22. package/src/interaction-runtime.mjs +310 -0
  23. package/src/kubernetes-commands.mjs +2235 -0
  24. package/src/launch-mode-handlers.mjs +119 -0
  25. package/src/machine-reference.mjs +285 -0
  26. package/src/mount-commands.mjs +1148 -0
  27. package/src/session-commands.mjs +565 -0
  28. package/src/slurm-commands.mjs +274 -0
  29. package/src/startup.mjs +34 -11
  30. package/src/storage-commands.mjs +1164 -0
  31. package/src/update.mjs +172 -24
  32. package/src/vm-commands.mjs +4308 -0
  33. package/src/vpn-secret-output.mjs +150 -0
  34. package/src/wait-runtime.mjs +231 -0
  35. package/vendor/capabilities/capabilities/admin-capacity-reservations.js +96 -0
  36. package/vendor/capabilities/capabilities/admin-enrollment.js +321 -0
  37. package/vendor/capabilities/capabilities/admin-node-diagnostics.js +88 -0
  38. package/vendor/capabilities/capabilities/admin-nodes-lifecycle.js +235 -0
  39. package/vendor/capabilities/capabilities/admin.js +89 -0
  40. package/vendor/capabilities/capabilities/cli.js +156 -0
  41. package/vendor/capabilities/capabilities/clusters.js +150 -1
  42. package/vendor/capabilities/capabilities/commerce-market.js +330 -0
  43. package/vendor/capabilities/capabilities/compute.js +522 -0
  44. package/vendor/capabilities/capabilities/evidence.js +145 -0
  45. package/vendor/capabilities/capabilities/external-buckets.js +191 -0
  46. package/vendor/capabilities/capabilities/fleet.js +77 -25
  47. package/vendor/capabilities/capabilities/identity.js +114 -3
  48. package/vendor/capabilities/capabilities/listings.js +141 -144
  49. package/vendor/capabilities/capabilities/market-shared.js +44 -0
  50. package/vendor/capabilities/capabilities/markets.js +449 -0
  51. package/vendor/capabilities/capabilities/mounts.js +176 -0
  52. package/vendor/capabilities/capabilities/networks.js +1264 -0
  53. package/vendor/capabilities/capabilities/observability.js +38 -8
  54. package/vendor/capabilities/capabilities/tokens.js +32 -17
  55. package/vendor/capabilities/capabilities/volumes.js +178 -0
  56. package/vendor/capabilities/capability.js +21 -14
  57. package/vendor/capabilities/cli-catalog.js +67 -0
  58. package/vendor/capabilities/cli-migrations.js +261 -0
  59. package/vendor/capabilities/cli.js +7 -0
  60. package/vendor/capabilities/software-catalog.js +36 -0
  61. package/vendor/capabilities/spec-catalog.js +28 -5
  62. package/vendor/capabilities/supplier-directory.js +20 -0
  63. package/src/catalog-dispatch.mjs +0 -605
  64. package/src/color.mjs +0 -71
  65. package/vendor/capabilities/capabilities/clusters.d.ts +0 -2
  66. package/vendor/capabilities/capabilities/fleet.d.ts +0 -8
  67. package/vendor/capabilities/capabilities/identity.d.ts +0 -3
  68. package/vendor/capabilities/capabilities/listings.d.ts +0 -51
  69. package/vendor/capabilities/capabilities/observability.d.ts +0 -5
  70. package/vendor/capabilities/capabilities/staff-access.d.ts +0 -29
  71. package/vendor/capabilities/capabilities/staff-access.js +0 -278
  72. package/vendor/capabilities/capabilities/staff-commerce.d.ts +0 -160
  73. package/vendor/capabilities/capabilities/staff-commerce.js +0 -689
  74. package/vendor/capabilities/capabilities/staff-fleet.d.ts +0 -140
  75. package/vendor/capabilities/capabilities/staff-fleet.js +0 -538
  76. package/vendor/capabilities/capabilities/staff-gtm.d.ts +0 -142
  77. package/vendor/capabilities/capabilities/staff-gtm.js +0 -521
  78. package/vendor/capabilities/capabilities/staff-shared.d.ts +0 -47
  79. package/vendor/capabilities/capabilities/staff-shared.js +0 -98
  80. package/vendor/capabilities/capabilities/tokens.d.ts +0 -10
  81. package/vendor/capabilities/capabilities/users.d.ts +0 -2
  82. package/vendor/capabilities/capability.d.ts +0 -93
  83. package/vendor/capabilities/catalog.d.ts +0 -3
  84. package/vendor/capabilities/catalog.js +0 -26
  85. package/vendor/capabilities/index.d.ts +0 -16
  86. package/vendor/capabilities/index.js +0 -16
  87. package/vendor/capabilities/names.d.ts +0 -6
  88. package/vendor/capabilities/role.d.ts +0 -6
  89. package/vendor/capabilities/role.js +0 -17
  90. package/vendor/capabilities/spec-catalog.d.ts +0 -78
@@ -0,0 +1,191 @@
1
+ import { z } from "zod";
2
+ import { defineCapability, } from "../capability.js";
3
+ const pageInput = z
4
+ .object({
5
+ $positionals: z.array(z.string()).optional(),
6
+ cursor: z.string().optional(),
7
+ limit: z.union([z.string(), z.number()]).optional(),
8
+ })
9
+ .strict();
10
+ const showInput = z
11
+ .object({
12
+ $positionals: z.array(z.string()).optional(),
13
+ })
14
+ .strict();
15
+ const verificationInput = z
16
+ .object({
17
+ $positionals: z.array(z.string()).optional(),
18
+ idempotencyKey: z.string().optional(),
19
+ })
20
+ .strict();
21
+ const disconnectInput = z
22
+ .object({
23
+ $positionals: z.array(z.string()).optional(),
24
+ idempotencyKey: z.string().optional(),
25
+ yes: z.boolean().optional(),
26
+ })
27
+ .strict();
28
+ const connectInput = z
29
+ .object({
30
+ $positionals: z.array(z.string()).optional(),
31
+ accessKeyId: z.string().optional(),
32
+ accountId: z.string().optional(),
33
+ bucket: z.string().optional(),
34
+ endpointUrl: z.string().optional(),
35
+ idempotencyKey: z.string().optional(),
36
+ name: z.string().optional(),
37
+ prefix: z.string().optional(),
38
+ readOnly: z.boolean().optional(),
39
+ readWrite: z.boolean().optional(),
40
+ region: z.string().optional(),
41
+ secretAccessKeyFile: z.string().optional(),
42
+ url: z.string().optional(),
43
+ yes: z.boolean().optional(),
44
+ })
45
+ .strict();
46
+ const credentialInput = z
47
+ .object({
48
+ $positionals: z.array(z.string()).optional(),
49
+ accessKeyId: z.string().optional(),
50
+ idempotencyKey: z.string().optional(),
51
+ secretAccessKeyFile: z.string().optional(),
52
+ yes: z.boolean().optional(),
53
+ })
54
+ .strict();
55
+ const importInput = z
56
+ .object({
57
+ $positionals: z.array(z.string()).optional(),
58
+ idempotencyKey: z.string().optional(),
59
+ noWait: z.boolean().optional(),
60
+ reservation: z.string().optional(),
61
+ timeout: z.string().optional(),
62
+ wait: z.boolean().optional(),
63
+ yes: z.boolean().optional(),
64
+ })
65
+ .strict();
66
+ function externalBucketExecutor(context) {
67
+ const interaction = context.cli?.interaction;
68
+ if (!interaction?.externalBuckets) {
69
+ throw new Error("This capability must execute through the Ornn CLI external-bucket runtime.");
70
+ }
71
+ return interaction.externalBuckets.bind(interaction);
72
+ }
73
+ function externalBucketCapability({ acceptedReferences = [], automation, command, description, human, id, input, mutation, outcome, related = [], resumableStates = [], review, }) {
74
+ return defineCapability({
75
+ id,
76
+ domain: "storage",
77
+ roles: ["user"],
78
+ description,
79
+ mutation,
80
+ input,
81
+ cli: {
82
+ command,
83
+ vnext: {
84
+ arguments: "semantic",
85
+ audience: "customer",
86
+ category: "resource",
87
+ outcome,
88
+ human,
89
+ automation,
90
+ acceptedReferences,
91
+ related,
92
+ resumableStates,
93
+ ...(review ? { review } : {}),
94
+ },
95
+ },
96
+ mcp: false,
97
+ slack: false,
98
+ execute: async (context, commandInput) => externalBucketExecutor(context)(id, commandInput),
99
+ });
100
+ }
101
+ const gcsImportGuidance = "GCS requires bucket-level read access for Ornn's Storage Transfer service agent, granted with gcloud. CLI 1.x GCS imports are currently blocked by unavailable source verification. Setup and version-specific steps: https://docs.ornn.com/cli#gcs-imports-and-storage-transfer-service";
102
+ export const externalBucketCapabilities = [
103
+ externalBucketCapability({
104
+ id: "storage.external-buckets.list",
105
+ command: ["external-buckets", "list"],
106
+ description: "List connected GCS, S3, and R2 buckets in one bounded page.",
107
+ mutation: "read",
108
+ input: pageInput,
109
+ outcome: "List external bucket connections without exposing stored credentials.",
110
+ human: "ornn external-buckets list [--limit <1-100>] [--cursor <cursor>]",
111
+ automation: "ornn external-buckets list --limit 50 --format json",
112
+ related: ["external-buckets show", "external-buckets connect"],
113
+ }),
114
+ externalBucketCapability({
115
+ id: "storage.external-buckets.show",
116
+ command: ["external-buckets", "show"],
117
+ description: "Inspect one connected external bucket by canonical ID or exact unique name.",
118
+ mutation: "read",
119
+ input: showInput,
120
+ outcome: "Inspect one credential-safe external bucket connection.",
121
+ human: "ornn external-buckets show <external-bucket>",
122
+ automation: "ornn external-buckets show <external-bucket-id> --format json",
123
+ acceptedReferences: ["canonical external bucket ID", "exact unique external bucket name"],
124
+ related: ["external-buckets verify", "external-buckets import"],
125
+ }),
126
+ externalBucketCapability({
127
+ id: "storage.external-buckets.connect",
128
+ command: ["external-buckets", "connect"],
129
+ description: "Connect a typed GCS, AWS S3, or Cloudflare R2 source without verifying it implicitly.",
130
+ mutation: "preview-confirm",
131
+ input: connectInput,
132
+ outcome: "Create or rediscover one external bucket connection with verification kept separate.",
133
+ human: "ornn external-buckets connect <gcs|s3|r2> --bucket <bucket> [--name <name>] [--prefix <prefix>] [--region <region>] [--account-id <id>|--endpoint-url <url>] [--access-key-id <id> --secret-access-key-file <path>] [--read-only|--read-write]",
134
+ automation: "ornn external-buckets connect <gcs|s3|r2> --bucket <bucket> <provider-inputs> --yes --idempotency-key <key> --format json",
135
+ review: `Review the exact source, access mode, and credential presence before connecting it. ${gcsImportGuidance}`,
136
+ related: ["external-buckets verify", "external-buckets import"],
137
+ }),
138
+ externalBucketCapability({
139
+ id: "storage.external-buckets.update-credentials",
140
+ command: ["external-buckets", "update-credentials"],
141
+ description: "Replace scoped S3 or R2 credentials without returning credential bytes.",
142
+ mutation: "preview-confirm",
143
+ input: credentialInput,
144
+ outcome: "Update one connection's credentials and require a separate verification.",
145
+ human: "ornn external-buckets update-credentials <external-bucket> --access-key-id <id> --secret-access-key-file <path>",
146
+ automation: "ornn external-buckets update-credentials <external-bucket-id> --access-key-id <id> --secret-access-key-file <path> --yes --idempotency-key <key> --format json",
147
+ acceptedReferences: ["canonical external bucket ID", "exact unique external bucket name"],
148
+ review: "Credential changes invalidate the prior verification and require renewed review.",
149
+ related: ["external-buckets verify"],
150
+ }),
151
+ externalBucketCapability({
152
+ id: "storage.external-buckets.verify",
153
+ command: ["external-buckets", "verify"],
154
+ description: "Verify current S3 or R2 source access as a separate operation.",
155
+ mutation: "preview-confirm",
156
+ input: verificationInput,
157
+ outcome: "Record current source-access verification without exposing credential material.",
158
+ human: "ornn external-buckets verify <external-bucket>",
159
+ automation: "ornn external-buckets verify <external-bucket-id> --idempotency-key <key> --format json",
160
+ acceptedReferences: ["canonical external bucket ID", "exact unique external bucket name"],
161
+ review: gcsImportGuidance,
162
+ related: ["external-buckets import", "external-buckets update-credentials"],
163
+ }),
164
+ externalBucketCapability({
165
+ id: "storage.external-buckets.import",
166
+ command: ["external-buckets", "import"],
167
+ description: "Import a verified GCS or S3 source into its isolated managed working volume.",
168
+ mutation: "preview-confirm",
169
+ input: importInput,
170
+ outcome: "Create or replay exactly one managed working copy with source writeback disabled.",
171
+ human: "ornn external-buckets import <gcs|s3|r2> <external-bucket> --reservation <canonical-reservation-id> [--no-wait] [--timeout <duration>]",
172
+ automation: "ornn external-buckets import <gcs|s3> <external-bucket-id> --reservation <canonical-reservation-id> --yes --idempotency-key <key> [--wait --timeout <duration>] --format json",
173
+ acceptedReferences: ["canonical external bucket ID", "exact unique external bucket name"],
174
+ review: `Review the isolated destination and explicit no-writeback guarantee before importing. ${gcsImportGuidance}`,
175
+ resumableStates: ["pending", "working", "partial", "failed", "interrupted"],
176
+ related: ["external-buckets show", "storage status", "volumes files list"],
177
+ }),
178
+ externalBucketCapability({
179
+ id: "storage.external-buckets.disconnect",
180
+ command: ["external-buckets", "disconnect"],
181
+ description: "Disconnect an external bucket record after its working volume is detached.",
182
+ mutation: "preview-confirm",
183
+ input: disconnectInput,
184
+ outcome: "Remove one external source connection without writing to its source bucket.",
185
+ human: "ornn external-buckets disconnect <external-bucket>",
186
+ automation: "ornn external-buckets disconnect <external-bucket-id> --yes --idempotency-key <key> --format json",
187
+ acceptedReferences: ["canonical external bucket ID", "exact unique external bucket name"],
188
+ review: "Disconnecting reduces access to the source and is blocked while mounts remain.",
189
+ related: ["storage detach", "external-buckets show"],
190
+ }),
191
+ ];
@@ -1,3 +1,4 @@
1
+ import { listSupplierOrganizations, listSupplierOrganizationPage } from "../supplier-directory.js";
1
2
  import { z } from "zod";
2
3
  import { defineCapability, preview } from "../capability.js";
3
4
  function queryPath(path, params) {
@@ -9,18 +10,27 @@ function queryPath(path, params) {
9
10
  return parts.length ? `${path}?${parts.join("&")}` : path;
10
11
  }
11
12
  const emptyInput = z.object({});
13
+ const suppliersInput = z.object({
14
+ q: z.string().optional(),
15
+ offset: z.number().int().min(0).optional(),
16
+ limit: z.number().int().min(1).max(1000).optional(),
17
+ });
12
18
  const facilitiesInput = z.object({
13
- operator_id: z.string().optional(),
14
- operator: z.string().optional(),
19
+ organization_id: z.string().optional(),
20
+ organizationId: z.string().optional(),
21
+ cursor: z.string().optional(),
22
+ limit: z.number().int().min(1).max(500).optional(),
15
23
  });
16
24
  const nodesInput = z.object({
17
- operator_id: z.string().optional(),
18
- operatorId: z.string().optional(),
19
- operator: z.string().optional(),
25
+ organization_id: z.string().optional(),
26
+ organizationId: z.string().optional(),
20
27
  facility_id: z.string().optional(),
21
28
  facilityId: z.string().optional(),
22
29
  status: z.string().optional(),
23
30
  email: z.string().optional(),
31
+ cursor: z.string().optional(),
32
+ max: z.number().int().positive().max(5000).optional(),
33
+ limit: z.number().int().positive().max(5000).optional(),
24
34
  });
25
35
  const nodeLifecycleInput = z.object({
26
36
  node_id: z.string().min(1),
@@ -39,29 +49,53 @@ const nodeExecInput = z.object({
39
49
  function nodeIdOf(input) {
40
50
  return (input.node_id || input.nodeId || "").trim();
41
51
  }
42
- export const listOperatorsCapability = defineCapability({
43
- id: "operator.list",
52
+ export const listSuppliersCapability = defineCapability({
53
+ id: "supplier.list",
44
54
  domain: "fleet",
45
- roles: ["reviewer", "admin"],
46
- description: "List GPU operators (id, slug, display name, status). Resolve operator names with this before nodes.list.",
55
+ roles: ["admin"],
56
+ description: "List supplier organizations (id, name). Resolve supplier names to organization_id with this before nodes.list or facilities.list.",
47
57
  mutation: "read",
48
- http: { method: "GET", path: "/provisioning/operators" },
49
- slack: { parameters: { type: "object", properties: {} } },
50
- input: emptyInput,
51
- execute: async (ctx) => ctx.fetch("/provisioning/operators", { method: "GET" }),
58
+ http: { method: "GET", path: "/v1/organizations" },
59
+ slack: {
60
+ parameters: {
61
+ type: "object",
62
+ properties: {
63
+ q: { type: "string", description: "Search supplier names." },
64
+ offset: { type: "integer", description: "Use next_offset to fetch another page." },
65
+ limit: { type: "integer", description: "Page size (Slack maximum 10)." },
66
+ },
67
+ },
68
+ },
69
+ input: suppliersInput,
70
+ execute: async (ctx, input) => {
71
+ const parsed = suppliersInput.parse(input);
72
+ const paged = parsed.limit !== undefined || parsed.offset !== undefined || parsed.q !== undefined;
73
+ const limit = parsed.limit ?? 25;
74
+ const offset = parsed.offset ?? 0;
75
+ const rows = paged
76
+ ? await listSupplierOrganizationPage(ctx.fetch, { ...parsed, limit, offset })
77
+ : await listSupplierOrganizations(ctx.fetch);
78
+ return {
79
+ suppliers: rows.map((org) => ({ id: org.id, name: org.name })),
80
+ ...(paged && rows.length === limit ? { next_offset: offset + rows.length } : {}),
81
+ };
82
+ },
52
83
  });
53
84
  export const listFacilitiesCapability = defineCapability({
54
85
  id: "facilities.list",
55
86
  domain: "fleet",
56
- roles: ["reviewer", "admin"],
57
- description: "List facilities (id, display name, region, operator). Optional operator_id filter. Resolve facility names with this before nodes.list.",
87
+ roles: ["admin"],
88
+ description: "List facilities (id, display name, region, supplier organization id). Optional organization_id filter. Resolve facility names with this before nodes.list.",
58
89
  mutation: "read",
59
90
  http: { method: "GET", path: "/provisioning/facilities" },
60
91
  slack: {
61
92
  parameters: {
62
93
  type: "object",
63
94
  properties: {
64
- operator_id: { type: "string", description: "Commerce operator UUID." },
95
+ organization_id: {
96
+ type: "string",
97
+ description: "Optional supplier organization UUID from supplier.list.",
98
+ },
65
99
  },
66
100
  },
67
101
  },
@@ -69,49 +103,67 @@ export const listFacilitiesCapability = defineCapability({
69
103
  execute: async (ctx, input) => {
70
104
  const parsed = facilitiesInput.parse(input);
71
105
  return ctx.fetch(queryPath("/provisioning/facilities", {
72
- operator_id: parsed.operator_id ?? parsed.operator,
106
+ organizationId: parsed.organization_id ?? parsed.organizationId,
107
+ cursor: parsed.cursor,
108
+ limit: parsed.limit === undefined ? undefined : String(parsed.limit),
73
109
  }), { method: "GET" });
74
110
  },
75
111
  });
76
112
  export const listNodesCapability = defineCapability({
77
113
  id: "nodes.list",
78
114
  domain: "fleet",
79
- roles: ["user", "reviewer", "admin"],
80
- description: "List nodes. Users see their own reserved machines. Reviewers and admins see enrolled, ghost, and off-grid nodes. Call with no filters for the whole fleet. Optional email lists nodes for that user's organizations. Optional operator_id, facility_id, and status (ghost, off-grid, bare-metal) narrow the same list. Never invent nodes. Never print private keys.",
115
+ roles: ["user", "admin"],
116
+ description: "List nodes. Users see their own reserved machines. Admins see enrolled, ghost, and off-grid nodes. Call with no filters for the whole fleet. Optional email lists nodes for that user's organizations. Optional organization_id (supplier), facility_id, and status (ghost, off-grid, bare-metal) narrow the same list. Paginate large fleets with cursor + max: pass the response's next_cursor back as cursor to fetch the next page; next_cursor is omitted once the page is not full. Never invent nodes. Never print private keys.",
81
117
  mutation: "read",
82
118
  http: { method: "GET", path: "/nodes" },
83
119
  slack: {
84
120
  parameters: {
85
121
  type: "object",
86
122
  properties: {
87
- operator_id: {
123
+ organization_id: {
88
124
  type: "string",
89
- description: "Optional commerce operator UUID from operator.list.",
125
+ description: "Optional supplier organization UUID from supplier.list.",
90
126
  },
91
127
  facility_id: {
92
128
  type: "string",
93
129
  description: "Optional commerce facility UUID from facilities.list.",
94
130
  },
95
- email: { type: "string", description: "Optional. List nodes for this user's organizations." },
131
+ email: {
132
+ type: "string",
133
+ description: "Optional. List nodes for this user's organizations.",
134
+ },
96
135
  status: {
97
136
  type: "string",
98
137
  enum: ["ghost", "off-grid", "bare-metal"],
99
138
  description: "Optional orchestrator status filter. Default is all statuses.",
100
139
  },
140
+ cursor: {
141
+ type: "string",
142
+ description: "Optional. Pass the previous response's next_cursor to fetch the next page.",
143
+ },
144
+ max: {
145
+ type: "integer",
146
+ description: "Optional page size (1-5000). Orchestrator default is 500.",
147
+ },
101
148
  },
102
149
  },
103
150
  },
104
151
  input: nodesInput,
105
152
  execute: async (ctx, input) => {
106
153
  const parsed = nodesInput.parse(input);
154
+ const pagination = {
155
+ cursor: parsed.cursor,
156
+ limit: parsed.max != null || parsed.limit != null ? String(parsed.max ?? parsed.limit) : undefined,
157
+ };
107
158
  if (ctx.role === "user") {
108
- return ctx.fetch("/nodes", { method: "GET" });
159
+ return ctx.fetch(queryPath("/nodes", pagination), { method: "GET" });
109
160
  }
110
161
  return ctx.fetch(queryPath("/nodes", {
111
- operator_id: parsed.operator_id ?? parsed.operatorId ?? parsed.operator,
162
+ organization_id: parsed.organization_id ?? parsed.organizationId,
112
163
  facility_id: parsed.facility_id ?? parsed.facilityId,
113
164
  status: parsed.status,
114
165
  email: parsed.email,
166
+ ...pagination,
115
167
  }), { method: "GET" });
116
168
  },
117
169
  });
@@ -243,7 +295,7 @@ export const nodeTerminateCapability = defineCapability({
243
295
  },
244
296
  });
245
297
  export const fleetCapabilities = [
246
- listOperatorsCapability,
298
+ listSuppliersCapability,
247
299
  listFacilitiesCapability,
248
300
  listNodesCapability,
249
301
  nodeOffGridCapability,
@@ -1,10 +1,103 @@
1
1
  import { z } from "zod";
2
2
  import { defineCapability } from "../capability.js";
3
3
  const emptyInput = z.object({});
4
+ const contextInput = z.object({ organization: z.string().min(1).optional() });
5
+ const loginInput = z.object({
6
+ authBase: z.string().min(1).optional(),
7
+ noBrowser: z.boolean().optional(),
8
+ organization: z.string().min(1).optional(),
9
+ pollInterval: z.number().positive().optional(),
10
+ timeout: z.number().positive().optional(),
11
+ });
12
+ function executeSession(context, command, input) {
13
+ if (!context.cli?.session) {
14
+ throw new Error("This capability must execute through the Ornn CLI session runtime.");
15
+ }
16
+ return context.cli.session(command, input);
17
+ }
18
+ export const loginCapability = defineCapability({
19
+ id: "identity.login",
20
+ domain: "identity",
21
+ roles: ["user"],
22
+ description: "Authenticate the CLI with Ornn device authorization.",
23
+ mutation: "local",
24
+ http: { method: "POST", path: "/v1/cli/device/start" },
25
+ input: loginInput,
26
+ cli: {
27
+ command: ["login"],
28
+ vnext: {
29
+ audience: "customer",
30
+ category: "session",
31
+ arguments: "semantic",
32
+ outcome: "Authenticate in a browser and return to the terminal.",
33
+ human: "ornn login [--organization <exact-name>] [--no-browser] [--timeout <seconds>]",
34
+ automation: "ornn login --format json",
35
+ resumableStates: ["browser authorization"],
36
+ related: ["context", "logout"],
37
+ order: 1,
38
+ },
39
+ },
40
+ mcp: false,
41
+ slack: false,
42
+ execute: async (context, input) => executeSession(context, "login", input),
43
+ });
44
+ export const contextCapability = defineCapability({
45
+ id: "identity.context",
46
+ domain: "identity",
47
+ roles: ["user"],
48
+ description: "Show and select the current server-verified organization context.",
49
+ mutation: "read",
50
+ http: { method: "GET", path: "/v1/cli/session" },
51
+ input: contextInput,
52
+ cli: {
53
+ command: ["context"],
54
+ vnext: {
55
+ audience: "customer",
56
+ category: "session",
57
+ arguments: "semantic",
58
+ outcome: "Show the current user, selected organization, approval, and available organizations.",
59
+ human: "ornn context [--organization <exact-name-or-id>]",
60
+ automation: "ornn context --organization <canonical-id> --format json",
61
+ acceptedReferences: [
62
+ "exact organization name (human)",
63
+ "canonical organization ID (automation)",
64
+ ],
65
+ related: ["login", "logout"],
66
+ order: 2,
67
+ },
68
+ },
69
+ mcp: false,
70
+ slack: false,
71
+ execute: async (context, input) => executeSession(context, "context", input),
72
+ });
73
+ export const logoutCapability = defineCapability({
74
+ id: "identity.logout",
75
+ domain: "identity",
76
+ roles: ["user"],
77
+ description: "Revoke the remote CLI session and clear its local bearer.",
78
+ mutation: "local",
79
+ http: { method: "DELETE", path: "/v1/cli/session" },
80
+ input: emptyInput,
81
+ cli: {
82
+ command: ["logout"],
83
+ vnext: {
84
+ audience: "customer",
85
+ category: "session",
86
+ outcome: "End the remote session and always remove the local bearer.",
87
+ human: "ornn logout",
88
+ automation: "ornn logout --format json",
89
+ related: ["login", "context"],
90
+ order: 3,
91
+ },
92
+ },
93
+ mcp: false,
94
+ slack: false,
95
+ execute: async (context, input) => executeSession(context, "logout", input),
96
+ });
4
97
  export const whoamiCapability = defineCapability({
5
98
  id: "identity.whoami",
6
99
  domain: "identity",
7
- roles: ["user", "reviewer", "admin"],
100
+ roles: ["user", "admin"],
8
101
  description: "Show the signed-in Ornn user and organization.",
9
102
  mutation: "read",
10
103
  http: { method: "GET", path: "/v1/cli/session" },
@@ -14,11 +107,29 @@ export const whoamiCapability = defineCapability({
14
107
  export const statusCapability = defineCapability({
15
108
  id: "identity.status",
16
109
  domain: "identity",
17
- roles: ["user", "reviewer", "admin"],
110
+ roles: ["user", "admin"],
18
111
  description: "Show Ornn platform status.",
19
112
  mutation: "read",
20
113
  http: { method: "GET", path: "/v1/cli/status" },
21
114
  input: emptyInput,
22
115
  execute: async (ctx) => ctx.fetch("/v1/cli/status", { method: "GET" }),
116
+ cli: {
117
+ command: ["status"],
118
+ vnext: {
119
+ audience: "customer",
120
+ category: "platform",
121
+ outcome: "Check current Ornn platform availability.",
122
+ human: "ornn status",
123
+ automation: "ornn status --format json",
124
+ related: ["version", "resources"],
125
+ order: 10,
126
+ },
127
+ },
23
128
  });
24
- export const identityCapabilities = [whoamiCapability, statusCapability];
129
+ export const identityCapabilities = [
130
+ whoamiCapability,
131
+ loginCapability,
132
+ contextCapability,
133
+ logoutCapability,
134
+ statusCapability,
135
+ ];