@openma/cli 0.4.1 → 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 +392 -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
 
@@ -7172,14 +7220,22 @@ ${c.bold("Up to date.")}
7172
7220
  machine_id: machineId,
7173
7221
  hostname: hostname(),
7174
7222
  os: osTag(),
7175
- 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
7176
7227
  });
7177
- 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)`);
7178
7229
  await writeCreds({
7230
+ v: 2,
7179
7231
  serverUrl: opts.serverUrl,
7180
7232
  runtimeId: exchange.runtime_id,
7181
7233
  token: exchange.token,
7182
- 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
+ })),
7183
7239
  machineId,
7184
7240
  createdAt: Math.floor(Date.now() / 1e3)
7185
7241
  });
@@ -7265,8 +7321,12 @@ async function installAndReport(opts) {
7265
7321
  }
7266
7322
  process.stderr.write("\n");
7267
7323
  process.stderr.write(`${c.bold("Done.")} the runtime should appear online at ${c.cyan(opts.browserOrigin)}
7268
-
7269
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
+ );
7270
7330
  }
7271
7331
  function execIntoDaemon() {
7272
7332
  const profile = currentProfile();
@@ -21668,7 +21728,14 @@ var init_session_manager = __esm({
21668
21728
  #spawner = new NodeSpawner();
21669
21729
  #runtime = new AcpRuntimeImpl(this.#spawner);
21670
21730
  #sessions = /* @__PURE__ */ new Map();
21671
- #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();
21672
21739
  /** Set by `drain()` to refuse new session.start while in-flight turns
21673
21740
  * finish. Existing sessions keep accepting prompts so a user mid-turn
21674
21741
  * doesn't hit "session not ready" mid-stream just because the daemon
@@ -21680,6 +21747,21 @@ var init_session_manager = __esm({
21680
21747
  setSpawnEnv(env) {
21681
21748
  this.#env = env;
21682
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
+ }
21683
21765
  setSender(send) {
21684
21766
  this.#send = send;
21685
21767
  }
@@ -21701,7 +21783,7 @@ var init_session_manager = __esm({
21701
21783
  /** Re-announce alive sessions to the server (used after WS reconnect). */
21702
21784
  announceAll() {
21703
21785
  for (const [session_id, sess] of this.#sessions) {
21704
- 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 });
21705
21787
  }
21706
21788
  }
21707
21789
  async start(p) {
@@ -21714,11 +21796,38 @@ var init_session_manager = __esm({
21714
21796
  });
21715
21797
  return;
21716
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
+ );
21717
21825
  const existing = this.#sessions.get(p.session_id);
21718
21826
  if (existing) {
21719
21827
  this.#send({
21720
21828
  type: "session.ready",
21721
21829
  session_id: p.session_id,
21830
+ tenant_id: existing.tenantId,
21722
21831
  acp_session_id: existing.acpSessionId
21723
21832
  });
21724
21833
  return;
@@ -21786,7 +21895,7 @@ var init_session_manager = __esm({
21786
21895
  type: s.type,
21787
21896
  name: s.name,
21788
21897
  url: s.url,
21789
- headers: this.#env.apiKey ? [{ name: "Authorization", value: `Bearer ${this.#env.apiKey}` }] : []
21898
+ headers: tenantKey ? [{ name: "Authorization", value: `Bearer ${tenantKey}` }] : []
21790
21899
  }));
21791
21900
  process.stderr.write(
21792
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
@@ -21807,17 +21916,20 @@ var init_session_manager = __esm({
21807
21916
  this.#sessions.set(p.session_id, {
21808
21917
  acp: session,
21809
21918
  acpSessionId: session.acpSessionId,
21919
+ tenantId,
21810
21920
  turns: /* @__PURE__ */ new Map()
21811
21921
  });
21812
21922
  this.#send({
21813
21923
  type: "session.ready",
21814
21924
  session_id: p.session_id,
21925
+ tenant_id: tenantId,
21815
21926
  acp_session_id: session.acpSessionId
21816
21927
  });
21817
21928
  } catch (e) {
21818
21929
  this.#send({
21819
21930
  type: "session.error",
21820
21931
  session_id: p.session_id,
21932
+ tenant_id: tenantId,
21821
21933
  message: e instanceof Error ? e.message : String(e)
21822
21934
  });
21823
21935
  }
@@ -21828,6 +21940,7 @@ var init_session_manager = __esm({
21828
21940
  this.#send({
21829
21941
  type: "session.error",
21830
21942
  session_id: p.session_id,
21943
+ tenant_id: p.tenant_id,
21831
21944
  turn_id: p.turn_id,
21832
21945
  message: "no such session"
21833
21946
  });
@@ -21848,6 +21961,7 @@ var init_session_manager = __esm({
21848
21961
  this.#send({
21849
21962
  type: "session.event",
21850
21963
  session_id: p.session_id,
21964
+ tenant_id: sess.tenantId,
21851
21965
  turn_id: p.turn_id,
21852
21966
  event: ev
21853
21967
  });
@@ -21856,16 +21970,18 @@ var init_session_manager = __esm({
21856
21970
  this.#send({
21857
21971
  type: "session.error",
21858
21972
  session_id: p.session_id,
21973
+ tenant_id: sess.tenantId,
21859
21974
  turn_id: p.turn_id,
21860
21975
  message: promptErr
21861
21976
  });
21862
21977
  } else {
21863
- 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 });
21864
21979
  }
21865
21980
  } catch (e) {
21866
21981
  this.#send({
21867
21982
  type: "session.error",
21868
21983
  session_id: p.session_id,
21984
+ tenant_id: sess.tenantId,
21869
21985
  turn_id: p.turn_id,
21870
21986
  message: e instanceof Error ? e.message : String(e)
21871
21987
  });
@@ -21879,9 +21995,11 @@ var init_session_manager = __esm({
21879
21995
  sess.turns.get(turn_id)?.abort();
21880
21996
  }
21881
21997
  async dispose(session_id) {
21998
+ const sess = this.#sessions.get(session_id);
21999
+ const tenantId = sess?.tenantId ?? "";
21882
22000
  await this.#killChild(session_id);
21883
22001
  await removeSessionCwd(session_id);
21884
- this.#send({ type: "session.disposed", session_id });
22002
+ this.#send({ type: "session.disposed", session_id, tenant_id: tenantId });
21885
22003
  }
21886
22004
  /** Best-effort cleanup on daemon shutdown. KEEPS spawn cwds — sessions are
21887
22005
  * still live at the platform; the daemon coming back tomorrow needs the
@@ -22245,8 +22363,15 @@ async function runDaemon() {
22245
22363
  }
22246
22364
  process.on("SIGHUP", () => {
22247
22365
  void (async () => {
22248
- log.step("SIGHUP \u2014 refreshing agent detection");
22366
+ log.step("SIGHUP \u2014 refreshing agent detection + credentials");
22249
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
+ }
22250
22375
  await loadRegistry({ cachePath, forceRefresh: true });
22251
22376
  const agents = (await detectAll()).map((a) => ({ id: a.id, binary: a.spec.command }));
22252
22377
  const localSkillsDetailed = await detectLocalSkills();
@@ -22278,10 +22403,10 @@ async function runDaemon() {
22278
22403
  const sessions = new SessionManager(() => {
22279
22404
  });
22280
22405
  sessions.setSpawnEnv({
22281
- apiKey: creds.agentApiKey ?? "",
22282
22406
  apiUrl: creds.serverUrl,
22283
22407
  runtimeToken: creds.token
22284
22408
  });
22409
+ sessions.setTenantKeys(creds.tenants);
22285
22410
  while (!stopping) {
22286
22411
  try {
22287
22412
  const ws = new wrapper_default(wsUrl, {
@@ -22596,22 +22721,221 @@ var init_agents = __esm({
22596
22721
  }
22597
22722
  });
22598
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
+
22599
22923
  // src/index.ts
22600
22924
  init_platform();
22601
22925
  import { createServer as createServer2 } from "node:http";
22602
22926
  import { execSync } from "node:child_process";
22603
- import { homedir as homedir5, hostname as hostname4 } from "node:os";
22604
- import { join as join10, dirname as dirname9 } from "node:path";
22605
- 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";
22606
22930
  import { randomBytes as randomBytes3 } from "node:crypto";
22607
22931
  function credentialsPath() {
22608
22932
  const xdg = process.env.XDG_CONFIG_HOME;
22609
- const base = xdg && xdg.length > 0 ? xdg : join10(homedir5(), ".config");
22933
+ const base = xdg && xdg.length > 0 ? xdg : join12(homedir6(), ".config");
22610
22934
  const profile = currentProfile();
22611
22935
  const file2 = profile ? `credentials.${profile}.json` : "credentials.json";
22612
- return join10(base, "oma", file2);
22936
+ return join12(base, "oma", file2);
22613
22937
  }
22614
- function migrateV1ToV2(v1) {
22938
+ function migrateV1ToV22(v1) {
22615
22939
  return {
22616
22940
  version: 2,
22617
22941
  base_url: v1.base_url,
@@ -22632,11 +22956,11 @@ function readCredentials() {
22632
22956
  const path2 = credentialsPath();
22633
22957
  try {
22634
22958
  if (!existsSync2(path2)) return null;
22635
- const raw = readFileSync2(path2, "utf8");
22959
+ const raw = readFileSync3(path2, "utf8");
22636
22960
  const parsed = JSON.parse(raw);
22637
22961
  if (parsed.version === 2) return parsed;
22638
22962
  if (parsed.version === 1) {
22639
- const v2 = migrateV1ToV2(parsed);
22963
+ const v2 = migrateV1ToV22(parsed);
22640
22964
  try {
22641
22965
  writeCredentials(v2);
22642
22966
  } catch {
@@ -22853,9 +23177,11 @@ async function streamChat(config2, sessionId, text) {
22853
23177
  }
22854
23178
  }
22855
23179
  async function tailSession(config2, sessionId) {
22856
- const res = await rawStream(config2, `/v1/sessions/${sessionId}/events/stream`, {
22857
- headers: { accept: "text/event-stream" }
22858
- });
23180
+ const res = await rawStream(
23181
+ config2,
23182
+ `/v1/sessions/${sessionId}/events/stream?include=chunks&replay=1`,
23183
+ { headers: { accept: "text/event-stream" } }
23184
+ );
22859
23185
  for await (const ev of parseSSE(res)) {
22860
23186
  process.stdout.write(JSON.stringify(ev) + "\n");
22861
23187
  }
@@ -23204,7 +23530,7 @@ var commands = [
23204
23530
  match: ["agents", "create"],
23205
23531
  usage: "oma agents create <name> [--model <id>]",
23206
23532
  desc: "Create agent",
23207
- 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,...}}",
23208
23534
  async run(config2, args) {
23209
23535
  const name = flag(args, "--name") || args.find((a) => !a.startsWith("--"));
23210
23536
  const model = flag(args, "--model") || "claude-sonnet-4-6";
@@ -23223,8 +23549,10 @@ var commands = [
23223
23549
  tools: [{ type: "agent_toolset_20260401" }]
23224
23550
  };
23225
23551
  if (useAcpProxy) {
23226
- body.harness = "acp-proxy";
23227
- 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
+ };
23228
23556
  }
23229
23557
  const agent = await apiFetch(config2, "/v1/agents", { method: "POST", body: JSON.stringify(body) });
23230
23558
  console.log(`Agent created: ${agent.name} (${agent.id})${useAcpProxy ? ` [acp-proxy \u2192 ${acpAgentId} on ${runtimeId.slice(0, 8)}\u2026]` : ""}`);
@@ -23434,27 +23762,27 @@ Version: v${a.version}`);
23434
23762
  console.log("No model cards. Create one with: oma models create");
23435
23763
  return;
23436
23764
  }
23437
- 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" : ""])]);
23438
23766
  }
23439
23767
  },
23440
23768
  {
23441
23769
  group: "Model Cards",
23442
23770
  match: ["models", "create"],
23443
- 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>]",
23444
23772
  desc: "Create model card",
23445
- 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?}",
23446
23774
  async run(config2, args) {
23447
- const name = flag(args, "--name");
23448
- const provider = flag(args, "--provider") || "ant";
23449
23775
  const modelId = flag(args, "--model-id");
23776
+ const provider = flag(args, "--provider") || "ant";
23777
+ const model = flag(args, "--model");
23450
23778
  const apiKey = flag(args, "--api-key");
23451
23779
  const baseUrl = flag(args, "--base-url");
23452
- if (!name || !modelId || !apiKey) {
23453
- 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>]");
23454
23782
  process.exit(1);
23455
23783
  }
23456
- const card = await apiFetch(config2, "/v1/model_cards", { method: "POST", body: JSON.stringify({ name, provider, model_id: modelId, api_key: apiKey, base_url: baseUrl }) });
23457
- 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})`);
23458
23786
  }
23459
23787
  },
23460
23788
  // API Keys
@@ -23541,27 +23869,26 @@ Save this key \u2014 it won't be shown again.`);
23541
23869
  console.log("No credentials in this vault.");
23542
23870
  return;
23543
23871
  }
23544
- 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 || ""])]);
23545
23873
  }
23546
23874
  },
23547
23875
  {
23548
23876
  group: "Vaults",
23549
- match: ["secret", "add"],
23550
- usage: "oma secret add --vault <id> --name <n> --cmd <pfx> --env <var> --token <t>",
23551
- desc: "Add secret credential",
23552
- 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}}",
23553
23881
  async run(config2, args) {
23554
23882
  const vaultId = flag(args, "--vault");
23555
23883
  const name = flag(args, "--name");
23556
- const cmd = flag(args, "--cmd");
23557
- const envVar = flag(args, "--env");
23884
+ const cliId = flag(args, "--cli-id");
23558
23885
  const token = flag(args, "--token");
23559
- if (!vaultId || !name || !cmd || !envVar || !token) {
23560
- 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>");
23561
23888
  process.exit(1);
23562
23889
  }
23563
- 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 } }) });
23564
- 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})`);
23565
23892
  }
23566
23893
  },
23567
23894
  // Skills
@@ -25077,6 +25404,7 @@ Usage:`);
25077
25404
  Bridge (local runtime):
25078
25405
  oma bridge setup [--force] [--no-service] Pair this machine with OMA + start daemon
25079
25406
  oma bridge status Show creds + probe server reachability
25407
+ oma bridge refresh Reconcile authorized tenants + reload daemon
25080
25408
  oma bridge agents refresh Re-detect agents + offer wrapper installs
25081
25409
  oma bridge uninstall Stop service + remove creds
25082
25410
  (oma bridge daemon Internal: launched by service mgr / debugging)
@@ -25163,9 +25491,25 @@ async function main() {
25163
25491
  await runAgents2(args.slice(2));
25164
25492
  return;
25165
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
+ }
25166
25510
  default:
25167
25511
  console.error(
25168
- "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"
25169
25513
  );
25170
25514
  process.exit(sub ? 1 : 0);
25171
25515
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openma/cli",
3
- "version": "0.4.1",
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",