@openma/cli 0.4.0 → 0.5.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/LICENSE +201 -21
  2. package/dist/index.js +419 -48
  3. package/package.json +2 -2
package/LICENSE CHANGED
@@ -1,21 +1,201 @@
1
- MIT License
2
-
3
- Copyright (c) 2025 Open Managed Agents Contributors
4
-
5
- Permission is hereby granted, free of charge, to any person obtaining a copy
6
- of this software and associated documentation files (the "Software"), to deal
7
- in the Software without restriction, including without limitation the rights
8
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
- copies of the Software, and to permit persons to whom the Software is
10
- furnished to do so, subject to the following conditions:
11
-
12
- The above copyright notice and this permission notice shall be included in all
13
- copies or substantial portions of the Software.
14
-
15
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
- SOFTWARE.
1
+ Apache License
2
+ Version 2.0, January 2004
3
+ http://www.apache.org/licenses/
4
+
5
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6
+
7
+ 1. Definitions.
8
+
9
+ "License" shall mean the terms and conditions for use, reproduction,
10
+ and distribution as defined by Sections 1 through 9 of this document.
11
+
12
+ "Licensor" shall mean the copyright owner or entity authorized by
13
+ the copyright owner that is granting the License.
14
+
15
+ "Legal Entity" shall mean the union of the acting entity and all
16
+ other entities that control, are controlled by, or are under common
17
+ control with that entity. For the purposes of this definition,
18
+ "control" means (i) the power, direct or indirect, to cause the
19
+ direction or management of such entity, whether by contract or
20
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
21
+ outstanding shares, or (iii) beneficial ownership of such entity.
22
+
23
+ "You" (or "Your") shall mean an individual or Legal Entity
24
+ exercising permissions granted by this License.
25
+
26
+ "Source" form shall mean the preferred form for making modifications,
27
+ including but not limited to software source code, documentation
28
+ source, and configuration files.
29
+
30
+ "Object" form shall mean any form resulting from mechanical
31
+ transformation or translation of a Source form, including but
32
+ not limited to compiled object code, generated documentation,
33
+ and conversions to other media types.
34
+
35
+ "Work" shall mean the work of authorship, whether in Source or
36
+ Object form, made available under the License, as indicated by a
37
+ copyright notice that is included in or attached to the work
38
+ (an example is provided in the Appendix below).
39
+
40
+ "Derivative Works" shall mean any work, whether in Source or Object
41
+ form, that is based on (or derived from) the Work and for which the
42
+ editorial revisions, annotations, elaborations, or other modifications
43
+ represent, as a whole, an original work of authorship. For the purposes
44
+ of this License, Derivative Works shall not include works that remain
45
+ separable from, or merely link (or bind by name) to the interfaces of,
46
+ the Work and Derivative Works thereof.
47
+
48
+ "Contribution" shall mean any work of authorship, including
49
+ the original version of the Work and any modifications or additions
50
+ to that Work or Derivative Works thereof, that is intentionally
51
+ submitted to Licensor for inclusion in the Work by the copyright owner
52
+ or by an individual or Legal Entity authorized to submit on behalf of
53
+ the copyright owner. For the purposes of this definition, "submitted"
54
+ means any form of electronic, verbal, or written communication sent
55
+ to the Licensor or its representatives, including but not limited to
56
+ communication on electronic mailing lists, source code control systems,
57
+ and issue tracking systems that are managed by, or on behalf of, the
58
+ Licensor for the purpose of discussing and improving the Work, but
59
+ excluding communication that is conspicuously marked or otherwise
60
+ designated in writing by the copyright owner as "Not a Contribution."
61
+
62
+ "Contributor" shall mean Licensor and any individual or Legal Entity
63
+ on behalf of whom a Contribution has been received by Licensor and
64
+ subsequently incorporated within the Work.
65
+
66
+ 2. Grant of Copyright License. Subject to the terms and conditions of
67
+ this License, each Contributor hereby grants to You a perpetual,
68
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69
+ copyright license to reproduce, prepare Derivative Works of,
70
+ publicly display, publicly perform, sublicense, and distribute the
71
+ Work and such Derivative Works in Source or Object form.
72
+
73
+ 3. Grant of Patent License. Subject to the terms and conditions of
74
+ this License, each Contributor hereby grants to You a perpetual,
75
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76
+ (except as stated in this section) patent license to make, have made,
77
+ use, offer to sell, sell, import, and otherwise transfer the Work,
78
+ where such license applies only to those patent claims licensable
79
+ by such Contributor that are necessarily infringed by their
80
+ Contribution(s) alone or by combination of their Contribution(s)
81
+ with the Work to which such Contribution(s) was submitted. If You
82
+ institute patent litigation against any entity (including a
83
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
84
+ or a Contribution incorporated within the Work constitutes direct
85
+ or contributory patent infringement, then any patent licenses
86
+ granted to You under this License for that Work shall terminate
87
+ as of the date such litigation is filed.
88
+
89
+ 4. Redistribution. You may reproduce and distribute copies of the
90
+ Work or Derivative Works thereof in any medium, with or without
91
+ modifications, and in Source or Object form, provided that You
92
+ meet the following conditions:
93
+
94
+ (a) You must give any other recipients of the Work or
95
+ Derivative Works a copy of this License; and
96
+
97
+ (b) You must cause any modified files to carry prominent notices
98
+ stating that You changed the files; and
99
+
100
+ (c) You must retain, in the Source form of any Derivative Works
101
+ that You distribute, all copyright, patent, trademark, and
102
+ attribution notices from the Source form of the Work,
103
+ excluding those notices that do not pertain to any part of
104
+ the Derivative Works; and
105
+
106
+ (d) If the Work includes a "NOTICE" text file as part of its
107
+ distribution, then any Derivative Works that You distribute must
108
+ include a readable copy of the attribution notices contained
109
+ within such NOTICE file, excluding those notices that do not
110
+ pertain to any part of the Derivative Works, in at least one
111
+ of the following places: within a NOTICE text file distributed
112
+ as part of the Derivative Works; within the Source form or
113
+ documentation, if provided along with the Derivative Works; or,
114
+ within a display generated by the Derivative Works, if and
115
+ wherever such third-party notices normally appear. The contents
116
+ of the NOTICE file are for informational purposes only and
117
+ do not modify the License. You may add Your own attribution
118
+ notices within Derivative Works that You distribute, alongside
119
+ or as an addendum to the NOTICE text from the Work, provided
120
+ that such additional attribution notices cannot be construed
121
+ as modifying the License.
122
+
123
+ You may add Your own copyright statement to Your modifications and
124
+ may provide additional or different license terms and conditions
125
+ for use, reproduction, or distribution of Your modifications, or
126
+ for any such Derivative Works as a whole, provided Your use,
127
+ reproduction, and distribution of the Work otherwise complies with
128
+ the conditions stated in this License.
129
+
130
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
131
+ any Contribution intentionally submitted for inclusion in the Work
132
+ by You to the Licensor shall be under the terms and conditions of
133
+ this License, without any additional terms or conditions.
134
+ Notwithstanding the above, nothing herein shall supersede or modify
135
+ the terms of any separate license agreement you may have executed
136
+ with Licensor regarding such Contributions.
137
+
138
+ 6. Trademarks. This License does not grant permission to use the trade
139
+ names, trademarks, service marks, or product names of the Licensor,
140
+ except as required for describing the origin of the Work and
141
+ reproducing the content of the NOTICE file.
142
+
143
+ 7. Disclaimer of Warranty. Unless required by applicable law or
144
+ agreed to in writing, Licensor provides the Work (and each
145
+ Contributor provides its Contributions) on an "AS IS" BASIS,
146
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147
+ implied, including, without limitation, any warranties or conditions
148
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149
+ PARTICULAR PURPOSE. You are solely responsible for determining the
150
+ appropriateness of using or redistributing the Work and assume any
151
+ risks associated with Your exercise of permissions under this License.
152
+
153
+ 8. Limitation of Liability. In no event and under no legal theory,
154
+ whether in tort (including negligence), contract, or otherwise,
155
+ unless required by applicable law (such as deliberate and grossly
156
+ negligent acts) or agreed to in writing, shall any Contributor be
157
+ liable to You for damages, including any direct, indirect, special,
158
+ incidental, or consequential damages of any character arising as a
159
+ result of this License or out of the use or inability to use the
160
+ Work (including but not limited to damages for loss of goodwill,
161
+ work stoppage, computer failure or malfunction, or any and all
162
+ other commercial damages or losses), even if such Contributor
163
+ has been advised of the possibility of such damages.
164
+
165
+ 9. Accepting Warranty or Support. While redistributing
166
+ the Work or Derivative Works thereof, You may choose to offer,
167
+ and charge a fee for, acceptance of support, warranty, indemnity,
168
+ or other liability obligations and/or rights consistent with this
169
+ License. However, in accepting such obligations, You may act only
170
+ on Your own behalf and on Your sole responsibility, not on behalf
171
+ of any other Contributor, and only if You agree to indemnify,
172
+ defend, and hold each Contributor harmless for any liability
173
+ incurred by, or claims asserted against, such Contributor by reason
174
+ of your accepting any such warranty or support.
175
+
176
+ END OF TERMS AND CONDITIONS
177
+
178
+ APPENDIX: How to apply the Apache License to your work.
179
+
180
+ To apply the Apache License to your work, attach the following
181
+ boilerplate notice, with the fields enclosed by brackets "[]"
182
+ replaced with your own identifying information. (Don't include
183
+ the brackets!) The text should be enclosed in the appropriate
184
+ comment syntax for the file format. We also recommend that a
185
+ file or class name and description of purpose be included on the
186
+ same "printed page" as the copyright notice for easier
187
+ identification within third-party archives.
188
+
189
+ Copyright 2025 Open Managed Agents Contributors
190
+
191
+ Licensed under the Apache License, Version 2.0 (the "License");
192
+ you may not use this file except in compliance with the License.
193
+ You may obtain a copy of the License at
194
+
195
+ http://www.apache.org/licenses/LICENSE-2.0
196
+
197
+ Unless required by applicable law or agreed to in writing, software
198
+ distributed under the License is distributed on an "AS IS" BASIS,
199
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200
+ See the License for the specific language governing permissions and
201
+ limitations under the License.
package/dist/index.js CHANGED
@@ -94,13 +94,59 @@ import { mkdir, readFile, writeFile, chmod } from "node:fs/promises";
94
94
  import { dirname } from "node:path";
95
95
  import { randomUUID } from "node:crypto";
96
96
  async function readCreds() {
97
+ let text;
97
98
  try {
98
- const text = await readFile(paths().credsFile, "utf-8");
99
- return JSON.parse(text);
99
+ text = await readFile(paths().credsFile, "utf-8");
100
100
  } catch (e) {
101
101
  if (e.code === "ENOENT") return null;
102
102
  throw e;
103
103
  }
104
+ const parsed = JSON.parse(text);
105
+ if (parsed.v === 2 && Array.isArray(parsed.tenants)) {
106
+ return parsed;
107
+ }
108
+ const v1 = parsed;
109
+ const v2 = await migrateV1ToV2(v1);
110
+ try {
111
+ await writeCreds(v2);
112
+ } catch {
113
+ }
114
+ return v2;
115
+ }
116
+ async function migrateV1ToV2(v1) {
117
+ const stubKey = v1.agentApiKey ?? "";
118
+ let tenants;
119
+ try {
120
+ const url2 = `${v1.serverUrl.replace(/\/$/, "")}/agents/runtime/me`;
121
+ const res = await fetch(url2, {
122
+ headers: { Authorization: `Bearer ${v1.token}` }
123
+ });
124
+ if (!res.ok) {
125
+ throw new Error(`HTTP ${res.status}`);
126
+ }
127
+ const body = await res.json();
128
+ if (!Array.isArray(body.tenants) || body.tenants.length === 0) {
129
+ throw new Error("server returned no tenants");
130
+ }
131
+ tenants = body.tenants.map((t) => ({
132
+ id: t.id,
133
+ name: t.name,
134
+ agentApiKey: stubKey
135
+ }));
136
+ } catch {
137
+ tenants = [
138
+ { id: UNKNOWN_TENANT_ID, name: "Unknown workspace", agentApiKey: stubKey }
139
+ ];
140
+ }
141
+ return {
142
+ v: 2,
143
+ serverUrl: v1.serverUrl,
144
+ runtimeId: v1.runtimeId,
145
+ token: v1.token,
146
+ tenants,
147
+ machineId: v1.machineId,
148
+ createdAt: v1.createdAt
149
+ };
104
150
  }
105
151
  async function writeCreds(creds) {
106
152
  const file2 = paths().credsFile;
@@ -129,10 +175,12 @@ async function getOrCreateMachineId() {
129
175
  await writeFile(file2, id + "\n", { mode: 384 });
130
176
  return id;
131
177
  }
178
+ var UNKNOWN_TENANT_ID;
132
179
  var init_config = __esm({
133
180
  "src/bridge/lib/config.ts"() {
134
181
  "use strict";
135
182
  init_platform();
183
+ UNKNOWN_TENANT_ID = "__unknown__";
136
184
  }
137
185
  });
138
186
 
@@ -7103,6 +7151,33 @@ function serviceInstallOpts() {
7103
7151
  };
7104
7152
  }
7105
7153
  async function runSetup(opts) {
7154
+ if (process.env.OMA_DEBUG_HANDLES) {
7155
+ let tick = 0;
7156
+ const probe = setInterval(() => {
7157
+ tick += 1;
7158
+ const handles = process._getActiveHandles();
7159
+ const requests = process._getActiveRequests();
7160
+ const hsum = handles.map((h) => h?.constructor?.name ?? "?").reduce((a, k) => {
7161
+ a[k] = (a[k] ?? 0) + 1;
7162
+ return a;
7163
+ }, {});
7164
+ const rsum = requests.map((r) => r?.constructor?.name ?? "?").reduce((a, k) => {
7165
+ a[k] = (a[k] ?? 0) + 1;
7166
+ return a;
7167
+ }, {});
7168
+ process.stderr.write(`
7169
+ [debug t=${tick * 2}s] handles: ${JSON.stringify(hsum)} requests: ${JSON.stringify(rsum)}
7170
+ `);
7171
+ }, 2e3);
7172
+ probe.unref();
7173
+ }
7174
+ try {
7175
+ await runSetupInner(opts);
7176
+ } finally {
7177
+ setImmediate(() => process.exit(0));
7178
+ }
7179
+ }
7180
+ async function runSetupInner(opts) {
7106
7181
  const profile = currentProfile();
7107
7182
  const profileTag = profile ? ` [profile=${profile}]` : "";
7108
7183
  printBanner(`setup \u2014 register this machine with ${opts.serverUrl}${profileTag}`, PKG_VERSION);
@@ -7145,14 +7220,22 @@ ${c.bold("Up to date.")}
7145
7220
  machine_id: machineId,
7146
7221
  hostname: hostname(),
7147
7222
  os: osTag(),
7148
- version: PKG_VERSION
7223
+ version: PKG_VERSION,
7224
+ // Opt into the v2 multi-tenant response — server returns one entry
7225
+ // per (runtime, tenant) pair instead of a single legacy agent_api_key.
7226
+ multi_tenant: true
7149
7227
  });
7150
- log.ok(`runtime registered ${c.dim(exchange.runtime_id.slice(0, 8) + "\u2026")}`);
7228
+ log.ok(`runtime registered ${c.dim(exchange.runtime_id.slice(0, 8) + "\u2026")} (${exchange.tenants.length} workspaces authorized)`);
7151
7229
  await writeCreds({
7230
+ v: 2,
7152
7231
  serverUrl: opts.serverUrl,
7153
7232
  runtimeId: exchange.runtime_id,
7154
7233
  token: exchange.token,
7155
- agentApiKey: exchange.agent_api_key,
7234
+ tenants: exchange.tenants.map((t) => ({
7235
+ id: t.id,
7236
+ name: t.name,
7237
+ agentApiKey: t.agent_api_key
7238
+ })),
7156
7239
  machineId,
7157
7240
  createdAt: Math.floor(Date.now() / 1e3)
7158
7241
  });
@@ -7238,8 +7321,12 @@ async function installAndReport(opts) {
7238
7321
  }
7239
7322
  process.stderr.write("\n");
7240
7323
  process.stderr.write(`${c.bold("Done.")} the runtime should appear online at ${c.cyan(opts.browserOrigin)}
7241
-
7242
7324
  `);
7325
+ process.stderr.write(
7326
+ `${c.dim(" After joining new workspaces, run `oma bridge refresh` to authorize them on this daemon.")}
7327
+
7328
+ `
7329
+ );
7243
7330
  }
7244
7331
  function execIntoDaemon() {
7245
7332
  const profile = currentProfile();
@@ -21641,7 +21728,14 @@ var init_session_manager = __esm({
21641
21728
  #spawner = new NodeSpawner();
21642
21729
  #runtime = new AcpRuntimeImpl(this.#spawner);
21643
21730
  #sessions = /* @__PURE__ */ new Map();
21644
- #env = { apiKey: "", apiUrl: "", runtimeToken: "" };
21731
+ #env = { apiUrl: "", runtimeToken: "" };
21732
+ /**
21733
+ * Per-tenant `oma_*` keys, keyed by tenant_id. Populated by the daemon
21734
+ * at startup from CredentialsV2.tenants and refreshed on SIGHUP after
21735
+ * `oma bridge refresh` rotates them server-side. Empty until the
21736
+ * daemon calls `setTenantKeys()`.
21737
+ */
21738
+ #tenantKeys = /* @__PURE__ */ new Map();
21645
21739
  /** Set by `drain()` to refuse new session.start while in-flight turns
21646
21740
  * finish. Existing sessions keep accepting prompts so a user mid-turn
21647
21741
  * doesn't hit "session not ready" mid-stream just because the daemon
@@ -21653,6 +21747,21 @@ var init_session_manager = __esm({
21653
21747
  setSpawnEnv(env) {
21654
21748
  this.#env = env;
21655
21749
  }
21750
+ /**
21751
+ * Replace the in-memory map of tenant_id → `oma_*` PAT. Called once at
21752
+ * daemon startup from CredentialsV2.tenants, and again on SIGHUP after
21753
+ * the user runs `oma bridge refresh` (so newly-added tenants become
21754
+ * sessionable without a daemon restart, and revoked tenants stop being
21755
+ * accepted).
21756
+ *
21757
+ * Existing sessions are NOT torn down — a session pinned to a tenant
21758
+ * that was just removed keeps running until the harness disposes it.
21759
+ * The lookup is consulted per session.start, not per turn.
21760
+ */
21761
+ setTenantKeys(tenants) {
21762
+ this.#tenantKeys.clear();
21763
+ for (const t of tenants) this.#tenantKeys.set(t.id, t.agentApiKey);
21764
+ }
21656
21765
  setSender(send) {
21657
21766
  this.#send = send;
21658
21767
  }
@@ -21674,7 +21783,7 @@ var init_session_manager = __esm({
21674
21783
  /** Re-announce alive sessions to the server (used after WS reconnect). */
21675
21784
  announceAll() {
21676
21785
  for (const [session_id, sess] of this.#sessions) {
21677
- this.#send({ type: "session.ready", session_id, acp_session_id: sess.acpSessionId });
21786
+ this.#send({ type: "session.ready", session_id, tenant_id: sess.tenantId, acp_session_id: sess.acpSessionId });
21678
21787
  }
21679
21788
  }
21680
21789
  async start(p) {
@@ -21687,11 +21796,38 @@ var init_session_manager = __esm({
21687
21796
  });
21688
21797
  return;
21689
21798
  }
21799
+ let tenantId = p.tenant_id ?? "";
21800
+ let tenantKey = tenantId ? this.#tenantKeys.get(tenantId) : void 0;
21801
+ if (!tenantId) {
21802
+ const fallback = this.#tenantKeys.keys().next();
21803
+ if (!fallback.done) {
21804
+ tenantId = fallback.value;
21805
+ tenantKey = this.#tenantKeys.get(tenantId);
21806
+ process.stderr.write(
21807
+ ` ! session.start missing tenant_id; falling back to first tenant ${tenantId.slice(0, 8)}\u2026
21808
+ `
21809
+ );
21810
+ }
21811
+ }
21812
+ if (!tenantKey) {
21813
+ this.#send({
21814
+ type: "session.error",
21815
+ session_id: p.session_id,
21816
+ tenant_id: p.tenant_id,
21817
+ message: `Tenant ${p.tenant_id ?? "(unspecified)"} not authorized for this runtime \u2014 run 'oma bridge refresh'`
21818
+ });
21819
+ return;
21820
+ }
21821
+ process.stderr.write(
21822
+ ` \u2192 tenant-key selected sid=${p.session_id.slice(0, 8)} tenant=${tenantId.slice(0, 14)}
21823
+ `
21824
+ );
21690
21825
  const existing = this.#sessions.get(p.session_id);
21691
21826
  if (existing) {
21692
21827
  this.#send({
21693
21828
  type: "session.ready",
21694
21829
  session_id: p.session_id,
21830
+ tenant_id: existing.tenantId,
21695
21831
  acp_session_id: existing.acpSessionId
21696
21832
  });
21697
21833
  return;
@@ -21759,7 +21895,7 @@ var init_session_manager = __esm({
21759
21895
  type: s.type,
21760
21896
  name: s.name,
21761
21897
  url: s.url,
21762
- headers: this.#env.apiKey ? [{ name: "Authorization", value: `Bearer ${this.#env.apiKey}` }] : []
21898
+ headers: tenantKey ? [{ name: "Authorization", value: `Bearer ${tenantKey}` }] : []
21763
21899
  }));
21764
21900
  process.stderr.write(
21765
21901
  ` \u2192 SessionManager.start ${agent.spec.command} cwd=${sessionCwd}` + (extraEnv.CLAUDE_CONFIG_DIR ? ` cfg=${extraEnv.CLAUDE_CONFIG_DIR}` : "") + (blocklist.length ? ` blocklist=${blocklist.length}` : "") + (mcpServersForAcp.length ? ` mcp=${mcpServersForAcp.length}` : "") + (bundleEnv.length ? ` env=${bundleEnv.length}` : "") + // Intentionally NOT logging env names or values — these come from
@@ -21780,17 +21916,20 @@ var init_session_manager = __esm({
21780
21916
  this.#sessions.set(p.session_id, {
21781
21917
  acp: session,
21782
21918
  acpSessionId: session.acpSessionId,
21919
+ tenantId,
21783
21920
  turns: /* @__PURE__ */ new Map()
21784
21921
  });
21785
21922
  this.#send({
21786
21923
  type: "session.ready",
21787
21924
  session_id: p.session_id,
21925
+ tenant_id: tenantId,
21788
21926
  acp_session_id: session.acpSessionId
21789
21927
  });
21790
21928
  } catch (e) {
21791
21929
  this.#send({
21792
21930
  type: "session.error",
21793
21931
  session_id: p.session_id,
21932
+ tenant_id: tenantId,
21794
21933
  message: e instanceof Error ? e.message : String(e)
21795
21934
  });
21796
21935
  }
@@ -21801,6 +21940,7 @@ var init_session_manager = __esm({
21801
21940
  this.#send({
21802
21941
  type: "session.error",
21803
21942
  session_id: p.session_id,
21943
+ tenant_id: p.tenant_id,
21804
21944
  turn_id: p.turn_id,
21805
21945
  message: "no such session"
21806
21946
  });
@@ -21821,6 +21961,7 @@ var init_session_manager = __esm({
21821
21961
  this.#send({
21822
21962
  type: "session.event",
21823
21963
  session_id: p.session_id,
21964
+ tenant_id: sess.tenantId,
21824
21965
  turn_id: p.turn_id,
21825
21966
  event: ev
21826
21967
  });
@@ -21829,16 +21970,18 @@ var init_session_manager = __esm({
21829
21970
  this.#send({
21830
21971
  type: "session.error",
21831
21972
  session_id: p.session_id,
21973
+ tenant_id: sess.tenantId,
21832
21974
  turn_id: p.turn_id,
21833
21975
  message: promptErr
21834
21976
  });
21835
21977
  } else {
21836
- this.#send({ type: "session.complete", session_id: p.session_id, turn_id: p.turn_id });
21978
+ this.#send({ type: "session.complete", session_id: p.session_id, tenant_id: sess.tenantId, turn_id: p.turn_id });
21837
21979
  }
21838
21980
  } catch (e) {
21839
21981
  this.#send({
21840
21982
  type: "session.error",
21841
21983
  session_id: p.session_id,
21984
+ tenant_id: sess.tenantId,
21842
21985
  turn_id: p.turn_id,
21843
21986
  message: e instanceof Error ? e.message : String(e)
21844
21987
  });
@@ -21852,9 +21995,11 @@ var init_session_manager = __esm({
21852
21995
  sess.turns.get(turn_id)?.abort();
21853
21996
  }
21854
21997
  async dispose(session_id) {
21998
+ const sess = this.#sessions.get(session_id);
21999
+ const tenantId = sess?.tenantId ?? "";
21855
22000
  await this.#killChild(session_id);
21856
22001
  await removeSessionCwd(session_id);
21857
- this.#send({ type: "session.disposed", session_id });
22002
+ this.#send({ type: "session.disposed", session_id, tenant_id: tenantId });
21858
22003
  }
21859
22004
  /** Best-effort cleanup on daemon shutdown. KEEPS spawn cwds — sessions are
21860
22005
  * still live at the platform; the daemon coming back tomorrow needs the
@@ -22218,8 +22363,15 @@ async function runDaemon() {
22218
22363
  }
22219
22364
  process.on("SIGHUP", () => {
22220
22365
  void (async () => {
22221
- log.step("SIGHUP \u2014 refreshing agent detection");
22366
+ log.step("SIGHUP \u2014 refreshing agent detection + credentials");
22222
22367
  try {
22368
+ const freshCreds = await readCreds();
22369
+ if (freshCreds) {
22370
+ sessions.setTenantKeys(freshCreds.tenants);
22371
+ log.ok(`re-loaded credentials (${freshCreds.tenants.length} tenants)`);
22372
+ } else {
22373
+ log.warn("credentials file disappeared mid-SIGHUP; tenant keys unchanged");
22374
+ }
22223
22375
  await loadRegistry({ cachePath, forceRefresh: true });
22224
22376
  const agents = (await detectAll()).map((a) => ({ id: a.id, binary: a.spec.command }));
22225
22377
  const localSkillsDetailed = await detectLocalSkills();
@@ -22251,10 +22403,10 @@ async function runDaemon() {
22251
22403
  const sessions = new SessionManager(() => {
22252
22404
  });
22253
22405
  sessions.setSpawnEnv({
22254
- apiKey: creds.agentApiKey ?? "",
22255
22406
  apiUrl: creds.serverUrl,
22256
22407
  runtimeToken: creds.token
22257
22408
  });
22409
+ sessions.setTenantKeys(creds.tenants);
22258
22410
  while (!stopping) {
22259
22411
  try {
22260
22412
  const ws = new wrapper_default(wsUrl, {
@@ -22569,22 +22721,221 @@ var init_agents = __esm({
22569
22721
  }
22570
22722
  });
22571
22723
 
22724
+ // src/bridge/commands/refresh.ts
22725
+ var refresh_exports = {};
22726
+ __export(refresh_exports, {
22727
+ runRefresh: () => runRefresh
22728
+ });
22729
+ import { readFileSync as readFileSync2 } from "node:fs";
22730
+ import { join as join10 } from "node:path";
22731
+ async function runRefresh() {
22732
+ const profile = currentProfile();
22733
+ const profileTag = profile ? ` [profile=${profile}]` : "";
22734
+ printBanner(`refresh \u2014 reconcile tenants${profileTag}`, PKG_VERSION);
22735
+ const creds = await readCreds();
22736
+ if (!creds) {
22737
+ log.err("not set up \u2014 run `oma bridge setup` first");
22738
+ log.hint(`looked for ${paths().credsFile}`);
22739
+ process.exit(2);
22740
+ }
22741
+ const url2 = `${creds.serverUrl.replace(/\/$/, "")}/agents/runtime/${encodeURIComponent(creds.runtimeId)}/refresh`;
22742
+ let response;
22743
+ try {
22744
+ const res = await fetch(url2, {
22745
+ method: "POST",
22746
+ headers: {
22747
+ Authorization: `Bearer ${creds.token}`,
22748
+ "content-type": "application/json"
22749
+ },
22750
+ body: "{}"
22751
+ });
22752
+ const text = await res.text();
22753
+ if (!res.ok) {
22754
+ log.err(`refresh failed: HTTP ${res.status}: ${text.slice(0, 200)}`);
22755
+ process.exit(1);
22756
+ }
22757
+ response = JSON.parse(text);
22758
+ } catch (e) {
22759
+ log.err(`refresh request failed: ${e.message}`);
22760
+ process.exit(1);
22761
+ }
22762
+ const prevIds = new Set(creds.tenants.map((t) => t.id));
22763
+ const nextIds = new Set(response.tenants.map((t) => t.id));
22764
+ const added = response.tenants.filter((t) => !prevIds.has(t.id));
22765
+ const removed = creds.tenants.filter((t) => !nextIds.has(t.id));
22766
+ const existing = response.tenants.filter((t) => prevIds.has(t.id));
22767
+ const updated = {
22768
+ ...creds,
22769
+ tenants: response.tenants.map((t) => ({
22770
+ id: t.id,
22771
+ name: t.name,
22772
+ agentApiKey: t.agent_api_key
22773
+ }))
22774
+ };
22775
+ await writeCreds(updated);
22776
+ log.ok(`credentials written ${c.dim(paths().credsFile)}`);
22777
+ const fmtList = (xs) => xs.length === 0 ? "\u2014" : xs.map((t) => `${t.name} (${t.id.slice(0, 8)}\u2026)`).join(", ");
22778
+ process.stderr.write(
22779
+ `
22780
+ Updated: +${added.length} added, -${removed.length} removed, =${existing.length} existing
22781
+ `
22782
+ );
22783
+ if (added.length > 0) process.stderr.write(` ${c.dim("added :")} ${fmtList(added)}
22784
+ `);
22785
+ if (removed.length > 0) process.stderr.write(` ${c.dim("removed :")} ${fmtList(removed)}
22786
+ `);
22787
+ if (existing.length > 0) process.stderr.write(` ${c.dim("kept :")} ${fmtList(existing)}
22788
+ `);
22789
+ process.stderr.write("\n");
22790
+ const pidFile = join10(paths().configDir, "daemon.pid");
22791
+ let pid = 0;
22792
+ try {
22793
+ pid = parseInt(readFileSync2(pidFile, "utf-8").trim(), 10);
22794
+ if (!Number.isFinite(pid) || pid <= 0) pid = 0;
22795
+ if (pid) process.kill(pid, 0);
22796
+ } catch {
22797
+ pid = 0;
22798
+ }
22799
+ if (pid === 0) {
22800
+ log.hint(`no running daemon${profileTag}; new tenants will load on next \`oma bridge daemon\` start`);
22801
+ return;
22802
+ }
22803
+ try {
22804
+ process.kill(pid, "SIGHUP");
22805
+ log.ok(`daemon${profileTag} reloading credentials (pid ${pid})`);
22806
+ } catch (e) {
22807
+ log.warn(`SIGHUP to pid ${pid} failed: ${e.message}`);
22808
+ log.hint(`creds file is still updated; restart the daemon to apply`);
22809
+ process.exit(2);
22810
+ }
22811
+ }
22812
+ var init_refresh = __esm({
22813
+ "src/bridge/commands/refresh.ts"() {
22814
+ "use strict";
22815
+ init_config();
22816
+ init_platform();
22817
+ init_style();
22818
+ init_version();
22819
+ }
22820
+ });
22821
+
22822
+ // src/bridge/commands/profiles.ts
22823
+ var profiles_exports = {};
22824
+ __export(profiles_exports, {
22825
+ runProfilesList: () => runProfilesList
22826
+ });
22827
+ import { readdir as readdir3, readFile as readFile7 } from "node:fs/promises";
22828
+ import { homedir as homedir5 } from "node:os";
22829
+ import { join as join11 } from "node:path";
22830
+ async function runProfilesList() {
22831
+ printBanner("bridge profiles \u2014 installed daemon profiles", PKG_VERSION);
22832
+ const omaRoot = join11(homedir5(), ".oma");
22833
+ let entries;
22834
+ try {
22835
+ entries = await readdir3(omaRoot);
22836
+ } catch {
22837
+ process.stderr.write(c.dim(` (no profiles installed \u2014 ${omaRoot} doesn't exist)
22838
+ `));
22839
+ return;
22840
+ }
22841
+ const profiles = [];
22842
+ for (const name of entries.sort()) {
22843
+ if (name !== "bridge" && !name.startsWith("bridge-")) continue;
22844
+ const profile = name === "bridge" ? "" : name.slice("bridge-".length);
22845
+ profiles.push(await readProfile(profile, join11(omaRoot, name)));
22846
+ }
22847
+ if (profiles.length === 0) {
22848
+ process.stderr.write(c.dim(" (no profiles installed)\n"));
22849
+ process.stderr.write(` Run ${c.cyan("oma bridge setup")} to create one.
22850
+ `);
22851
+ return;
22852
+ }
22853
+ process.stderr.write(
22854
+ ` ${c.dim("PROFILE")} ${c.dim("SERVER")} ${c.dim("RUNTIME")} ${c.dim("TENANTS")} ${c.dim("DAEMON")}
22855
+ `
22856
+ );
22857
+ for (const p of profiles) {
22858
+ const profileLabel = p.profile === "" ? c.bold("(default)") : c.bold(p.profile);
22859
+ const server = p.serverUrl ?? c.dim("\u2014");
22860
+ const runtime = p.runtimeId ? p.runtimeId.slice(0, 8) + "\u2026" : c.dim("\u2014");
22861
+ const tenants = p.tenantCount === null ? c.dim("\u2014") : p.credsVersion === 1 ? `1 ${c.dim("(v1 creds \u2014 pre-multi-tenant)")}` : String(p.tenantCount);
22862
+ const daemon = p.daemonPid ? p.daemonAlive ? `${c.green("\u25CF")} pid ${p.daemonPid}` : `${c.yellow("\u25CB")} pid ${p.daemonPid} ${c.dim("(stale)")}` : c.dim("\u2014");
22863
+ process.stderr.write(` ${profileLabel} ${server} ${runtime} ${tenants} ${daemon}
22864
+ `);
22865
+ }
22866
+ process.stderr.write(
22867
+ `
22868
+ ${c.dim(
22869
+ "Switch active profile with `OMA_PROFILE=<name> oma bridge <cmd>` (or --profile=<name>)."
22870
+ )}
22871
+ `
22872
+ );
22873
+ }
22874
+ async function readProfile(profile, configDir) {
22875
+ const info = {
22876
+ profile,
22877
+ configDir,
22878
+ serverUrl: null,
22879
+ runtimeId: null,
22880
+ tenantCount: null,
22881
+ credsVersion: "unknown",
22882
+ daemonPid: null,
22883
+ daemonAlive: false
22884
+ };
22885
+ try {
22886
+ const raw = await readFile7(join11(configDir, "credentials.json"), "utf-8");
22887
+ const parsed = JSON.parse(raw);
22888
+ info.serverUrl = parsed.serverUrl ?? null;
22889
+ info.runtimeId = parsed.runtimeId ?? null;
22890
+ if (parsed.v === 2 && Array.isArray(parsed.tenants)) {
22891
+ info.credsVersion = 2;
22892
+ info.tenantCount = parsed.tenants.length;
22893
+ } else if (parsed.agentApiKey) {
22894
+ info.credsVersion = 1;
22895
+ info.tenantCount = 1;
22896
+ }
22897
+ } catch {
22898
+ }
22899
+ try {
22900
+ const pidStr = (await readFile7(join11(configDir, "daemon.pid"), "utf-8")).trim();
22901
+ const pid = parseInt(pidStr, 10);
22902
+ if (Number.isFinite(pid) && pid > 0) {
22903
+ info.daemonPid = pid;
22904
+ try {
22905
+ process.kill(pid, 0);
22906
+ info.daemonAlive = true;
22907
+ } catch {
22908
+ info.daemonAlive = false;
22909
+ }
22910
+ }
22911
+ } catch {
22912
+ }
22913
+ return info;
22914
+ }
22915
+ var init_profiles = __esm({
22916
+ "src/bridge/commands/profiles.ts"() {
22917
+ "use strict";
22918
+ init_style();
22919
+ init_version();
22920
+ }
22921
+ });
22922
+
22572
22923
  // src/index.ts
22573
22924
  init_platform();
22574
22925
  import { createServer as createServer2 } from "node:http";
22575
22926
  import { execSync } from "node:child_process";
22576
- import { homedir as homedir5, hostname as hostname4 } from "node:os";
22577
- import { join as join10, dirname as dirname9 } from "node:path";
22578
- import { mkdirSync as mkdirSync3, readFileSync as readFileSync2, writeFileSync as writeFileSync2, unlinkSync as unlinkSync3, existsSync as existsSync2, chmodSync as chmodSync2 } from "node:fs";
22927
+ import { homedir as homedir6, hostname as hostname4 } from "node:os";
22928
+ import { join as join12, dirname as dirname9 } from "node:path";
22929
+ import { mkdirSync as mkdirSync3, readFileSync as readFileSync3, writeFileSync as writeFileSync2, unlinkSync as unlinkSync3, existsSync as existsSync2, chmodSync as chmodSync2 } from "node:fs";
22579
22930
  import { randomBytes as randomBytes3 } from "node:crypto";
22580
22931
  function credentialsPath() {
22581
22932
  const xdg = process.env.XDG_CONFIG_HOME;
22582
- const base = xdg && xdg.length > 0 ? xdg : join10(homedir5(), ".config");
22933
+ const base = xdg && xdg.length > 0 ? xdg : join12(homedir6(), ".config");
22583
22934
  const profile = currentProfile();
22584
22935
  const file2 = profile ? `credentials.${profile}.json` : "credentials.json";
22585
- return join10(base, "oma", file2);
22936
+ return join12(base, "oma", file2);
22586
22937
  }
22587
- function migrateV1ToV2(v1) {
22938
+ function migrateV1ToV22(v1) {
22588
22939
  return {
22589
22940
  version: 2,
22590
22941
  base_url: v1.base_url,
@@ -22605,11 +22956,11 @@ function readCredentials() {
22605
22956
  const path2 = credentialsPath();
22606
22957
  try {
22607
22958
  if (!existsSync2(path2)) return null;
22608
- const raw = readFileSync2(path2, "utf8");
22959
+ const raw = readFileSync3(path2, "utf8");
22609
22960
  const parsed = JSON.parse(raw);
22610
22961
  if (parsed.version === 2) return parsed;
22611
22962
  if (parsed.version === 1) {
22612
- const v2 = migrateV1ToV2(parsed);
22963
+ const v2 = migrateV1ToV22(parsed);
22613
22964
  try {
22614
22965
  writeCredentials(v2);
22615
22966
  } catch {
@@ -22826,9 +23177,11 @@ async function streamChat(config2, sessionId, text) {
22826
23177
  }
22827
23178
  }
22828
23179
  async function tailSession(config2, sessionId) {
22829
- const res = await rawStream(config2, `/v1/sessions/${sessionId}/events/stream`, {
22830
- headers: { accept: "text/event-stream" }
22831
- });
23180
+ const res = await rawStream(
23181
+ config2,
23182
+ `/v1/sessions/${sessionId}/events/stream?include=chunks&replay=1`,
23183
+ { headers: { accept: "text/event-stream" } }
23184
+ );
22832
23185
  for await (const ev of parseSSE(res)) {
22833
23186
  process.stdout.write(JSON.stringify(ev) + "\n");
22834
23187
  }
@@ -23177,7 +23530,7 @@ var commands = [
23177
23530
  match: ["agents", "create"],
23178
23531
  usage: "oma agents create <name> [--model <id>]",
23179
23532
  desc: "Create agent",
23180
- http: "POST /v1/agents {name, model, system, tools, skills?, mcp_servers?, callable_agents?, runtime_binding?}",
23533
+ http: "POST /v1/agents {name, model, system, tools, skills?, mcp_servers?, multiagent?, _oma?:{runtime_binding,harness,...}}",
23181
23534
  async run(config2, args) {
23182
23535
  const name = flag(args, "--name") || args.find((a) => !a.startsWith("--"));
23183
23536
  const model = flag(args, "--model") || "claude-sonnet-4-6";
@@ -23196,8 +23549,10 @@ var commands = [
23196
23549
  tools: [{ type: "agent_toolset_20260401" }]
23197
23550
  };
23198
23551
  if (useAcpProxy) {
23199
- body.harness = "acp-proxy";
23200
- body.runtime_binding = { runtime_id: runtimeId, acp_agent_id: acpAgentId };
23552
+ body._oma = {
23553
+ harness: "acp-proxy",
23554
+ runtime_binding: { runtime_id: runtimeId, acp_agent_id: acpAgentId }
23555
+ };
23201
23556
  }
23202
23557
  const agent = await apiFetch(config2, "/v1/agents", { method: "POST", body: JSON.stringify(body) });
23203
23558
  console.log(`Agent created: ${agent.name} (${agent.id})${useAcpProxy ? ` [acp-proxy \u2192 ${acpAgentId} on ${runtimeId.slice(0, 8)}\u2026]` : ""}`);
@@ -23407,27 +23762,27 @@ Version: v${a.version}`);
23407
23762
  console.log("No model cards. Create one with: oma models create");
23408
23763
  return;
23409
23764
  }
23410
- table([["NAME", "PROVIDER", "MODEL", "KEY", "DEFAULT"], ...data.map((c3) => [c3.name, c3.provider, c3.model_id, `****${c3.api_key_preview || ""}`, c3.is_default ? "yes" : ""])]);
23765
+ table([["MODEL_ID", "PROVIDER", "WIRE MODEL", "KEY", "DEFAULT"], ...data.map((c3) => [c3.model_id, c3.provider, c3.model === c3.model_id ? "(same)" : c3.model, `****${c3.api_key_preview || ""}`, c3.is_default ? "yes" : ""])]);
23411
23766
  }
23412
23767
  },
23413
23768
  {
23414
23769
  group: "Model Cards",
23415
23770
  match: ["models", "create"],
23416
- usage: "oma models create --name <n> --model-id <id> --api-key <key> [--provider <p>]",
23771
+ usage: "oma models create --model-id <id> --api-key <key> [--model <wire>] [--provider <p>]",
23417
23772
  desc: "Create model card",
23418
- http: "POST /v1/model_cards {name, provider, model_id, api_key, base_url?, is_default?}",
23773
+ http: "POST /v1/model_cards {model_id, provider, model?, api_key, base_url?, is_default?}",
23419
23774
  async run(config2, args) {
23420
- const name = flag(args, "--name");
23421
- const provider = flag(args, "--provider") || "ant";
23422
23775
  const modelId = flag(args, "--model-id");
23776
+ const provider = flag(args, "--provider") || "ant";
23777
+ const model = flag(args, "--model");
23423
23778
  const apiKey = flag(args, "--api-key");
23424
23779
  const baseUrl = flag(args, "--base-url");
23425
- if (!name || !modelId || !apiKey) {
23426
- console.error("Usage: oma models create --name <name> --model-id <id> --api-key <key> [--provider ant|oai|ant-compatible|oai-compatible] [--base-url <url>]");
23780
+ if (!modelId || !apiKey) {
23781
+ console.error("Usage: oma models create --model-id <id> --api-key <key> [--model <wire>] [--provider ant|oai|ant-compatible|oai-compatible] [--base-url <url>]");
23427
23782
  process.exit(1);
23428
23783
  }
23429
- const card = await apiFetch(config2, "/v1/model_cards", { method: "POST", body: JSON.stringify({ name, provider, model_id: modelId, api_key: apiKey, base_url: baseUrl }) });
23430
- console.log(`Model card created: ${card.name} (${card.id})`);
23784
+ const card = await apiFetch(config2, "/v1/model_cards", { method: "POST", body: JSON.stringify({ model_id: modelId, provider, model, api_key: apiKey, base_url: baseUrl }) });
23785
+ console.log(`Model card created: ${card.model_id} (${card.id})`);
23431
23786
  }
23432
23787
  },
23433
23788
  // API Keys
@@ -23514,27 +23869,26 @@ Save this key \u2014 it won't be shown again.`);
23514
23869
  console.log("No credentials in this vault.");
23515
23870
  return;
23516
23871
  }
23517
- table([["NAME", "TYPE", "DETAIL"], ...data.map((c3) => [c3.display_name, c3.auth.type, c3.auth.mcp_server_url || c3.auth.command_prefixes?.join(", ") || ""])]);
23872
+ table([["NAME", "TYPE", "DETAIL"], ...data.map((c3) => [c3.display_name, c3.auth.type, c3.auth.mcp_server_url || c3.auth.cli_id || ""])]);
23518
23873
  }
23519
23874
  },
23520
23875
  {
23521
23876
  group: "Vaults",
23522
- match: ["secret", "add"],
23523
- usage: "oma secret add --vault <id> --name <n> --cmd <pfx> --env <var> --token <t>",
23524
- desc: "Add secret credential",
23525
- http: "POST /v1/vaults/:id/credentials {display_name, auth:{type, command_prefixes, env_var, token}}",
23877
+ match: ["cli", "add"],
23878
+ usage: "oma cli add --vault <id> --name <n> --cli-id <gh|aws|...> --token <t>",
23879
+ desc: "Add cap CLI credential",
23880
+ http: "POST /v1/vaults/:id/credentials {display_name, auth:{type:cap_cli, cli_id, token}}",
23526
23881
  async run(config2, args) {
23527
23882
  const vaultId = flag(args, "--vault");
23528
23883
  const name = flag(args, "--name");
23529
- const cmd = flag(args, "--cmd");
23530
- const envVar = flag(args, "--env");
23884
+ const cliId = flag(args, "--cli-id");
23531
23885
  const token = flag(args, "--token");
23532
- if (!vaultId || !name || !cmd || !envVar || !token) {
23533
- console.error("Usage: oma secret add --vault <id> --name <name> --cmd <prefixes> --env <var> --token <token>");
23886
+ if (!vaultId || !name || !cliId || !token) {
23887
+ console.error("Usage: oma cli add --vault <id> --name <name> --cli-id <gh|aws|kubectl|...> --token <token>");
23534
23888
  process.exit(1);
23535
23889
  }
23536
- const cred = await apiFetch(config2, `/v1/vaults/${vaultId}/credentials`, { method: "POST", body: JSON.stringify({ display_name: name, auth: { type: "command_secret", command_prefixes: cmd.split(",").map((s) => s.trim()), env_var: envVar, token } }) });
23537
- console.log(`Secret added: ${name} (${cred.id})`);
23890
+ const cred = await apiFetch(config2, `/v1/vaults/${vaultId}/credentials`, { method: "POST", body: JSON.stringify({ display_name: name, auth: { type: "cap_cli", cli_id: cliId, token } }) });
23891
+ console.log(`CLI credential added: ${name} (${cred.id})`);
23538
23892
  }
23539
23893
  },
23540
23894
  // Skills
@@ -25050,6 +25404,7 @@ Usage:`);
25050
25404
  Bridge (local runtime):
25051
25405
  oma bridge setup [--force] [--no-service] Pair this machine with OMA + start daemon
25052
25406
  oma bridge status Show creds + probe server reachability
25407
+ oma bridge refresh Reconcile authorized tenants + reload daemon
25053
25408
  oma bridge agents refresh Re-detect agents + offer wrapper installs
25054
25409
  oma bridge uninstall Stop service + remove creds
25055
25410
  (oma bridge daemon Internal: launched by service mgr / debugging)
@@ -25136,9 +25491,25 @@ async function main() {
25136
25491
  await runAgents2(args.slice(2));
25137
25492
  return;
25138
25493
  }
25494
+ case "refresh": {
25495
+ const { runRefresh: runRefresh2 } = await Promise.resolve().then(() => (init_refresh(), refresh_exports));
25496
+ await runRefresh2();
25497
+ return;
25498
+ }
25499
+ case "profiles": {
25500
+ const sub2 = args[2] ?? "list";
25501
+ if (sub2 !== "list" && sub2 !== "ls") {
25502
+ console.error(`Unknown subcommand: oma bridge profiles ${sub2}
25503
+ Try: oma bridge profiles list`);
25504
+ process.exit(1);
25505
+ }
25506
+ const { runProfilesList: runProfilesList2 } = await Promise.resolve().then(() => (init_profiles(), profiles_exports));
25507
+ await runProfilesList2();
25508
+ return;
25509
+ }
25139
25510
  default:
25140
25511
  console.error(
25141
- "oma bridge \u2014 pair a local ACP agent with OMA\n\n oma bridge setup [--server-url=\u2026] [--no-service] [--force] [--yes]\n Pair + install service + start daemon\n oma bridge status Creds + service kind + probe server\n oma bridge agents refresh [--yes] Re-scan + offer-install wrappers + reload\n oma bridge uninstall Stop service + remove creds\n oma bridge daemon (internal \u2014 launched by service mgr / for debug)\n\n Use --profile <name> (or OMA_PROFILE=<name>) to run multiple\n daemons side-by-side (e.g. prod in launchd + staging foreground).\n"
25512
+ "oma bridge \u2014 pair a local ACP agent with OMA\n\n oma bridge setup [--server-url=\u2026] [--no-service] [--force] [--yes]\n Pair + install service + start daemon\n oma bridge status Creds + service kind + probe server\n oma bridge refresh Reconcile authorized tenants + reload daemon\n oma bridge profiles list List all installed daemon profiles on this machine\n oma bridge agents refresh [--yes] Re-scan + offer-install wrappers + reload\n oma bridge uninstall Stop service + remove creds\n oma bridge daemon (internal \u2014 launched by service mgr / for debug)\n\n Use --profile <name> (or OMA_PROFILE=<name>) to run multiple\n daemons side-by-side (e.g. prod in launchd + staging foreground).\n"
25142
25513
  );
25143
25514
  process.exit(sub ? 1 : 0);
25144
25515
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openma/cli",
3
- "version": "0.4.0",
3
+ "version": "0.5.0",
4
4
  "description": "CLI for the openma managed agents platform",
5
5
  "type": "module",
6
6
  "bin": {
@@ -24,7 +24,7 @@
24
24
  "cli",
25
25
  "llm"
26
26
  ],
27
- "license": "MIT",
27
+ "license": "Apache-2.0",
28
28
  "homepage": "https://openma.dev",
29
29
  "repository": {
30
30
  "type": "git",