run402 4.97.0 → 4.99.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/core-dist/binding-file.js +28 -1
- package/core-dist/wallet.js +3 -3
- package/lib/auth.mjs +1 -2
- package/lib/buzz-doctor.mjs +21 -806
- package/lib/buzz-doctor.test.mjs +7 -6
- package/lib/buzz.test.mjs +13 -2
- package/lib/doctor.mjs +72 -811
- package/lib/init.mjs +35 -576
- package/lib/org-context.mjs +39 -376
- package/lib/orgs.mjs +22 -106
- package/lib/projects.mjs +1 -2
- package/lib/repos.mjs +6 -6
- package/lib/rooms.mjs +1 -2
- package/lib/sdk-errors.mjs +26 -0
- package/lib/status.mjs +14 -154
- package/lib/up.mjs +1 -2
- package/lib/vault-scaffold.mjs +2 -3
- package/lib/vault-target.mjs +14 -141
- package/lib/wallet-context.mjs +61 -227
- package/lib/wallets.mjs +47 -310
- package/package.json +1 -2
- package/sdk/core-dist/binding-file.js +28 -1
- package/sdk/core-dist/wallet.js +3 -3
- package/sdk/dist/errors.d.ts +24 -2
- package/sdk/dist/errors.d.ts.map +1 -1
- package/sdk/dist/errors.js +44 -2
- package/sdk/dist/errors.js.map +1 -1
- package/sdk/dist/index.d.ts +18 -3
- package/sdk/dist/index.d.ts.map +1 -1
- package/sdk/dist/index.js +12 -1
- package/sdk/dist/index.js.map +1 -1
- package/sdk/dist/kernel.d.ts +34 -0
- package/sdk/dist/kernel.d.ts.map +1 -1
- package/sdk/dist/kernel.js +15 -1
- package/sdk/dist/kernel.js.map +1 -1
- package/sdk/dist/namespaces/admin.d.ts.map +1 -1
- package/sdk/dist/namespaces/admin.js +8 -0
- package/sdk/dist/namespaces/admin.js.map +1 -1
- package/sdk/dist/namespaces/agent.d.ts +2 -2
- package/sdk/dist/namespaces/agent.d.ts.map +1 -1
- package/sdk/dist/namespaces/agent.js +4 -0
- package/sdk/dist/namespaces/agent.js.map +1 -1
- package/sdk/dist/namespaces/agent.types.d.ts +5 -0
- package/sdk/dist/namespaces/agent.types.d.ts.map +1 -1
- package/sdk/dist/namespaces/apps.d.ts.map +1 -1
- package/sdk/dist/namespaces/apps.js +2 -0
- package/sdk/dist/namespaces/apps.js.map +1 -1
- package/sdk/dist/namespaces/auth.d.ts.map +1 -1
- package/sdk/dist/namespaces/auth.js +9 -0
- package/sdk/dist/namespaces/auth.js.map +1 -1
- package/sdk/dist/namespaces/branches.d.ts.map +1 -1
- package/sdk/dist/namespaces/branches.js +2 -0
- package/sdk/dist/namespaces/branches.js.map +1 -1
- package/sdk/dist/namespaces/ci.d.ts.map +1 -1
- package/sdk/dist/namespaces/ci.js +2 -0
- package/sdk/dist/namespaces/ci.js.map +1 -1
- package/sdk/dist/namespaces/credentials.d.ts +0 -23
- package/sdk/dist/namespaces/credentials.d.ts.map +1 -1
- package/sdk/dist/namespaces/credentials.js +29 -0
- package/sdk/dist/namespaces/credentials.js.map +1 -1
- package/sdk/dist/namespaces/escalations.d.ts.map +1 -1
- package/sdk/dist/namespaces/escalations.js +2 -0
- package/sdk/dist/namespaces/escalations.js.map +1 -1
- package/sdk/dist/namespaces/grants.d.ts.map +1 -1
- package/sdk/dist/namespaces/grants.js +4 -0
- package/sdk/dist/namespaces/grants.js.map +1 -1
- package/sdk/dist/namespaces/live.d.ts +0 -16
- package/sdk/dist/namespaces/live.d.ts.map +1 -1
- package/sdk/dist/namespaces/live.js +19 -0
- package/sdk/dist/namespaces/live.js.map +1 -1
- package/sdk/dist/namespaces/me.d.ts.map +1 -1
- package/sdk/dist/namespaces/me.js +3 -0
- package/sdk/dist/namespaces/me.js.map +1 -1
- package/sdk/dist/namespaces/org.d.ts.map +1 -1
- package/sdk/dist/namespaces/org.js +3 -0
- package/sdk/dist/namespaces/org.js.map +1 -1
- package/sdk/dist/namespaces/projects.d.ts.map +1 -1
- package/sdk/dist/namespaces/projects.js +5 -0
- package/sdk/dist/namespaces/projects.js.map +1 -1
- package/sdk/dist/namespaces/repos.d.ts.map +1 -1
- package/sdk/dist/namespaces/repos.js +5 -0
- package/sdk/dist/namespaces/repos.js.map +1 -1
- package/sdk/dist/namespaces/rooms.d.ts.map +1 -1
- package/sdk/dist/namespaces/rooms.js +3 -0
- package/sdk/dist/namespaces/rooms.js.map +1 -1
- package/sdk/dist/namespaces/session.d.ts.map +1 -1
- package/sdk/dist/namespaces/session.js +18 -0
- package/sdk/dist/namespaces/session.js.map +1 -1
- package/sdk/dist/namespaces/transfers.d.ts.map +1 -1
- package/sdk/dist/namespaces/transfers.js +3 -0
- package/sdk/dist/namespaces/transfers.js.map +1 -1
- package/sdk/dist/namespaces/wallets.d.ts.map +1 -1
- package/sdk/dist/namespaces/wallets.js +2 -0
- package/sdk/dist/namespaces/wallets.js.map +1 -1
- package/sdk/dist/namespaces/write-approval.d.ts.map +1 -1
- package/sdk/dist/namespaces/write-approval.js +3 -0
- package/sdk/dist/namespaces/write-approval.js.map +1 -1
- package/sdk/dist/node/actions-node.d.ts.map +1 -1
- package/sdk/dist/node/actions-node.js +3 -0
- package/sdk/dist/node/actions-node.js.map +1 -1
- package/sdk/dist/node/buzz-doctor-contract.d.ts +155 -0
- package/sdk/dist/node/buzz-doctor-contract.d.ts.map +1 -0
- package/sdk/dist/node/buzz-doctor-contract.js +385 -0
- package/sdk/dist/node/buzz-doctor-contract.js.map +1 -0
- package/sdk/dist/node/buzz-doctor.d.ts +155 -0
- package/sdk/dist/node/buzz-doctor.d.ts.map +1 -0
- package/sdk/dist/node/buzz-doctor.js +810 -0
- package/sdk/dist/node/buzz-doctor.js.map +1 -0
- package/sdk/dist/node/credentials.d.ts +9 -4
- package/sdk/dist/node/credentials.d.ts.map +1 -1
- package/sdk/dist/node/credentials.js +1 -1
- package/sdk/dist/node/credentials.js.map +1 -1
- package/sdk/dist/node/diagnostics.d.ts +45 -0
- package/sdk/dist/node/diagnostics.d.ts.map +1 -0
- package/sdk/dist/node/diagnostics.js +67 -0
- package/sdk/dist/node/diagnostics.js.map +1 -0
- package/sdk/dist/node/doctor.d.ts +101 -0
- package/sdk/dist/node/doctor.d.ts.map +1 -0
- package/sdk/dist/node/doctor.js +618 -0
- package/sdk/dist/node/doctor.js.map +1 -0
- package/sdk/dist/node/index.d.ts +57 -5
- package/sdk/dist/node/index.d.ts.map +1 -1
- package/sdk/dist/node/index.js +36 -1
- package/sdk/dist/node/index.js.map +1 -1
- package/sdk/dist/node/init.d.ts +137 -0
- package/sdk/dist/node/init.d.ts.map +1 -0
- package/sdk/dist/node/init.js +507 -0
- package/sdk/dist/node/init.js.map +1 -0
- package/sdk/dist/node/lightning-wallet.d.ts +61 -0
- package/sdk/dist/node/lightning-wallet.d.ts.map +1 -0
- package/sdk/dist/node/lightning-wallet.js +126 -0
- package/sdk/dist/node/lightning-wallet.js.map +1 -0
- package/sdk/dist/node/local-actions.d.ts +23 -0
- package/sdk/dist/node/local-actions.d.ts.map +1 -0
- package/sdk/dist/node/local-actions.js +38 -0
- package/sdk/dist/node/local-actions.js.map +1 -0
- package/sdk/dist/node/org-context.d.ts +169 -0
- package/sdk/dist/node/org-context.d.ts.map +1 -0
- package/sdk/dist/node/org-context.js +403 -0
- package/sdk/dist/node/org-context.js.map +1 -0
- package/sdk/dist/node/status.d.ts +62 -0
- package/sdk/dist/node/status.d.ts.map +1 -0
- package/sdk/dist/node/status.js +132 -0
- package/sdk/dist/node/status.js.map +1 -0
- package/sdk/dist/node/vault-target.d.ts +49 -0
- package/sdk/dist/node/vault-target.d.ts.map +1 -0
- package/sdk/dist/node/vault-target.js +109 -0
- package/sdk/dist/node/vault-target.js.map +1 -0
- package/sdk/dist/node/wallets.d.ts +242 -0
- package/sdk/dist/node/wallets.d.ts.map +1 -0
- package/sdk/dist/node/wallets.js +484 -0
- package/sdk/dist/node/wallets.js.map +1 -0
- package/sdk/dist/secret-gate.d.ts +377 -0
- package/sdk/dist/secret-gate.d.ts.map +1 -0
- package/sdk/dist/secret-gate.js +273 -0
- package/sdk/dist/secret-gate.js.map +1 -0
- package/vault-surface.json +1 -1
- package/fixtures/buzz-v0.5.2-cli-capabilities.json +0 -92
- package/fixtures/run402-buzz-doctor-v1-contract.json +0 -140
- package/lib/buzz-doctor-contract.mjs +0 -97
- package/lib/buzz-doctor-contract.test.mjs +0 -134
- package/lib/lightning-wallet.mjs +0 -115
package/lib/org-context.mjs
CHANGED
|
@@ -1,182 +1,42 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
* (
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
* Precedence is four INTENT CLASSES, highest first, and inside each class an
|
|
11
|
-
* organization named directly outranks one derived from a project named in that
|
|
12
|
-
* same class:
|
|
13
|
-
*
|
|
14
|
-
* 1. flag --org <org_id> else --project <project_id> -> its org
|
|
15
|
-
* 2. environment RUN402_ORG, else the org else RUN402_PROJECT_ID -> its org
|
|
16
|
-
* half of RUN402_ROOM
|
|
17
|
-
* 3. binding the `org` key of the nearest .run402(.local).json
|
|
18
|
-
* 4. profile state the profile's selected org else its active project -> org
|
|
19
|
-
*
|
|
20
|
-
* The class rule is what makes `--project X` behave: naming a project IS naming
|
|
21
|
-
* its organization, so a stale profile selection must not outrank it. Classes 3
|
|
22
|
-
* and 4 are what let an org that owns NO project be addressed at all — every
|
|
23
|
-
* earlier rung needed an argument, a variable, or a deployed project.
|
|
2
|
+
* Organization resolution for org-scoped CLI families: the CLI edge of
|
|
3
|
+
* `r.orgs.resolve()` (`@run402/sdk/node`), which owns the precedence chain —
|
|
4
|
+
* flag (`--org`, then `--project`), environment (`RUN402_ORG`, the org half of
|
|
5
|
+
* `RUN402_ROOM`, then `RUN402_PROJECT_ID`), the `.run402.json` binding, this
|
|
6
|
+
* checkout's vault pin, then profile state (`orgs use`, then the active
|
|
7
|
+
* project). This module adds only what is CLI-shaped: reading the flags out of
|
|
8
|
+
* argv, the leading `<org_id>` positional contract, the per-command conflict
|
|
9
|
+
* exemption, and printing a refusal as the CLI's error envelope.
|
|
24
10
|
*
|
|
25
11
|
* `rooms`/`claims` resolve a PAIR ({orgId, roomKey}) with their own
|
|
26
|
-
* `RUN402_ROOM`
|
|
27
|
-
*
|
|
28
|
-
* precedence, and only the ORG HALF of its fallback delegates here. One
|
|
29
|
-
* resolver per shape, composed for the half they share.
|
|
30
|
-
*
|
|
31
|
-
* Two things this deliberately does NOT do:
|
|
32
|
-
* - Infer the org from the caller's memberships, at any count. Membership is
|
|
33
|
-
* server state that changes without the caller acting, so a heuristic that is
|
|
34
|
-
* right today silently changes meaning the day they are invited elsewhere.
|
|
35
|
-
* - Validate membership locally. A well-formed id goes to the server and the
|
|
36
|
-
* server's answer is surfaced as returned — never rewritten into a local
|
|
37
|
-
* not-found (that would invent the existence oracle the gateway refuses to
|
|
38
|
-
* provide) and never retried against a lower class (that would act on a
|
|
39
|
-
* different organization than the caller named).
|
|
12
|
+
* `RUN402_ROOM` form — see `rooms-context.mjs`; only the org half of their
|
|
13
|
+
* fallback delegates here.
|
|
40
14
|
*/
|
|
41
15
|
import { getSdk } from "./sdk.mjs";
|
|
42
16
|
import { flagValue, positionalArgs } from "./argparse.mjs";
|
|
43
|
-
import {
|
|
44
|
-
import {
|
|
45
|
-
import { nextAction } from "./next-actions.mjs";
|
|
46
|
-
import { describeRejectedValue } from "../core-dist/redact.js";
|
|
47
|
-
import {
|
|
48
|
-
getActiveOrgId as coreGetActiveOrgId,
|
|
49
|
-
setActiveOrgId as coreSetActiveOrgId,
|
|
50
|
-
clearActiveOrgId as coreClearActiveOrgId,
|
|
51
|
-
} from "../core-dist/profile-state.js";
|
|
52
|
-
import { getActiveProjectId } from "../core-dist/keystore.js";
|
|
53
|
-
import { getProject, resolveProjectId, updateProject } from "./config.mjs";
|
|
17
|
+
import { fail, failLocal } from "./sdk-errors.mjs";
|
|
18
|
+
import { ORG_ID_RE, assertOrgIdShape, orgProvenance, orgRequiredError } from "#sdk/node";
|
|
54
19
|
|
|
55
|
-
export
|
|
56
|
-
export const ROOM_ENV = "RUN402_ROOM";
|
|
57
|
-
export const PROJECT_ENV = "RUN402_PROJECT_ID";
|
|
20
|
+
export { ORG_ENV, ROOM_ENV, PROJECT_ENV, orgProvenance } from "#sdk/node";
|
|
58
21
|
|
|
59
22
|
/** Command groups that must stay usable while selection is ambiguous. */
|
|
60
23
|
const CONFLICT_EXEMPT = new Set(["orgs", "wallets", "doctor"]);
|
|
61
24
|
|
|
62
|
-
/**
|
|
63
|
-
const
|
|
25
|
+
/** The refusals the resolver itself raises; anything else is the server's answer. */
|
|
26
|
+
const RESOLVER_CODES = new Set(["BAD_ORG_ID", "AMBIGUOUS_ORG", "ORG_REQUIRED"]);
|
|
64
27
|
|
|
65
28
|
const trimmed = (v) => (typeof v === "string" && v.trim() ? v.trim() : null);
|
|
66
29
|
|
|
67
|
-
/**
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
* A rejected value is a value we know nothing about — see
|
|
72
|
-
* core-dist/redact.js's doc comment: the
|
|
73
|
-
* same class of mistake that put a private key into RUN402_WALLET can put
|
|
74
|
-
* one into RUN402_ORG / --org, so this must never echo the raw value.
|
|
75
|
-
*/
|
|
76
|
-
function assertOrgIdShape(orgId, origin) {
|
|
77
|
-
if (ORG_ID_RE.test(orgId)) return orgId;
|
|
78
|
-
fail({
|
|
79
|
-
code: "BAD_ORG_ID",
|
|
80
|
-
message: `Invalid organization id ${JSON.stringify(describeRejectedValue(orgId))} (from ${origin}).`,
|
|
81
|
-
hint: "An org_id is a UUID. Run 'run402 orgs list' to see the organizations you belong to.",
|
|
82
|
-
details: { org_id: describeRejectedValue(orgId), origin },
|
|
83
|
-
next_actions: [listOrgsAction()],
|
|
84
|
-
});
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
/** The org half of `RUN402_ROOM=<org_id>/<room_key>`, or null. */
|
|
88
|
-
function orgFromRoomEnv(env) {
|
|
89
|
-
const raw = trimmed(env[ROOM_ENV]);
|
|
90
|
-
if (!raw) return null;
|
|
91
|
-
const slash = raw.indexOf("/");
|
|
92
|
-
if (slash <= 0 || slash === raw.length - 1) return null; // rooms-context reports the malformed form
|
|
93
|
-
return raw.slice(0, slash);
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
/**
|
|
97
|
-
* `r402.orgId` from `cwd`'s LOCAL git config (kygit-handoff design D10) —
|
|
98
|
-
* best-effort, gracefully degrading like `findBindingKey`: no repository,
|
|
99
|
-
* no pin, or a shape-invalid value all answer `null` rather than throwing,
|
|
100
|
-
* so a bare directory or a checkout with no vault remote costs nothing.
|
|
101
|
-
* Checks a real vault checkout's pin first (gated on `r402.repoId`), then
|
|
102
|
-
* falls back to the bare org/room pin a room-only `rooms join <key>` writes
|
|
103
|
-
* in a directory with no vault at all (add-room-invite design D10).
|
|
104
|
-
*/
|
|
105
|
-
async function readVaultPinnedOrgId(cwd) {
|
|
106
|
-
try {
|
|
107
|
-
const { readPinnedVaultRepo, readPinnedRoomBinding } = await import("#sdk/node");
|
|
108
|
-
const pinned = await readPinnedVaultRepo(cwd);
|
|
109
|
-
const orgId = trimmed(pinned?.org_id);
|
|
110
|
-
if (orgId && ORG_ID_RE.test(orgId)) return orgId;
|
|
111
|
-
const bare = await readPinnedRoomBinding(cwd);
|
|
112
|
-
const bareOrgId = trimmed(bare?.org_id);
|
|
113
|
-
return bareOrgId && ORG_ID_RE.test(bareOrgId) ? bareOrgId : null;
|
|
114
|
-
} catch {
|
|
115
|
-
return null;
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
function listOrgsAction() {
|
|
120
|
-
return nextAction("edit_request", {
|
|
121
|
-
command: "run402 orgs list",
|
|
122
|
-
why: "List the organizations this wallet belongs to.",
|
|
123
|
-
});
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
function orgRequiredActions() {
|
|
127
|
-
return [
|
|
128
|
-
nextAction("edit_request", {
|
|
129
|
-
command: "run402 orgs use <org_id>",
|
|
130
|
-
why: "Select a current organization for this wallet profile.",
|
|
131
|
-
}),
|
|
132
|
-
listOrgsAction(),
|
|
133
|
-
nextAction("edit_request", {
|
|
134
|
-
command: "run402 <command> --org <org_id>",
|
|
135
|
-
why: "Name the organization on this one call.",
|
|
136
|
-
}),
|
|
137
|
-
nextAction("edit_request", {
|
|
138
|
-
command: "export RUN402_ORG=<org_id>",
|
|
139
|
-
why: "Name the organization for every call in this shell (harness wiring).",
|
|
140
|
-
}),
|
|
141
|
-
nextAction("edit_request", {
|
|
142
|
-
command: `echo '{"org":"<org_id>"}' > .run402.json`,
|
|
143
|
-
why: "Bind this checkout to an organization so every agent in it inherits the same one.",
|
|
144
|
-
}),
|
|
145
|
-
nextAction("edit_request", {
|
|
146
|
-
command: "run402 projects use <project_id>",
|
|
147
|
-
why: "Select a project; its owning organization becomes the current one.",
|
|
148
|
-
}),
|
|
149
|
-
];
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
/** Resolve a project's owning org (one GET). Returns null when unresolvable. */
|
|
153
|
-
async function orgOfProject(projectId, { required }) {
|
|
154
|
-
// Upstream's per-project cache answers the same question for free when it is
|
|
155
|
-
// warm. It is NOT used as the whole implementation: it returns null on
|
|
156
|
-
// failure, and an explicitly named project must be able to HARD STOP rather
|
|
157
|
-
// than fall through to a lower class.
|
|
158
|
-
const cached = getProject(projectId)?.org_id;
|
|
159
|
-
if (typeof cached === "string" && cached.length > 0) return cached;
|
|
160
|
-
try {
|
|
161
|
-
const scoped = await getSdk().rooms.forProject(projectId);
|
|
162
|
-
return scoped.orgId;
|
|
163
|
-
} catch (err) {
|
|
164
|
-
// An EXPLICITLY named project that cannot be resolved is a hard stop — the
|
|
165
|
-
// caller named it, so falling to a lower class would act on a different
|
|
166
|
-
// organization than the one they asked for. An implicitly selected project
|
|
167
|
-
// (profile state) may simply be stale; skip the class.
|
|
168
|
-
if (!required) return null;
|
|
169
|
-
throw err;
|
|
170
|
-
}
|
|
30
|
+
/** Print a resolver refusal as the CLI envelope; rethrow a server or network error. */
|
|
31
|
+
function failResolver(err) {
|
|
32
|
+
if (err?.kind === "local_error" && RESOLVER_CODES.has(err?.code)) failLocal(err);
|
|
33
|
+
throw err;
|
|
171
34
|
}
|
|
172
35
|
|
|
173
36
|
/**
|
|
174
37
|
* Resolve the organization an org-scoped command acts on.
|
|
175
38
|
*
|
|
176
|
-
*
|
|
177
|
-
* `escalations`) or an options object for callers that already hold values.
|
|
178
|
-
*
|
|
179
|
-
* @param {string[]|object} input normalized argv, or { org, project }
|
|
39
|
+
* @param {string[]|object} input normalized argv (`--org`, `--project` read from it), or { org, project }
|
|
180
40
|
* @param {object} [opts]
|
|
181
41
|
* @param {string} [opts.cmd] command group, for the conflict exemption
|
|
182
42
|
* @param {object} [opts.env] environment (injectable for tests)
|
|
@@ -188,136 +48,30 @@ export async function resolveOrg(input = {}, opts = {}) {
|
|
|
188
48
|
const { cmd, env = process.env, cwd = process.cwd(), optional = false } = opts;
|
|
189
49
|
const org = Array.isArray(input) ? flagValue(input, "--org") : input.org;
|
|
190
50
|
const project = Array.isArray(input) ? flagValue(input, "--project") : input.project;
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
return { orgId: assertOrgIdShape(orgFlag, "--org"), source: "flag", sourceDetail: "--org" };
|
|
196
|
-
}
|
|
197
|
-
const projectFlag = trimmed(project);
|
|
198
|
-
if (projectFlag) {
|
|
199
|
-
const orgId = await orgOfProject(projectFlag, { required: true });
|
|
200
|
-
if (orgId) return { orgId, source: "flag", sourceDetail: "--project" };
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
// --- Class 2 and 3: environment vs binding (the conflict pair) ------------
|
|
204
|
-
const bindingHit = findBindingKey(cwd, "org");
|
|
205
|
-
const bindingOrg = bindingHit ? assertOrgIdShape(bindingHit.value, bindingHit.file) : null;
|
|
206
|
-
|
|
207
|
-
const envDirect = trimmed(env[ORG_ENV]);
|
|
208
|
-
const envRoomOrg = envDirect ? null : orgFromRoomEnv(env);
|
|
209
|
-
const envProject = envDirect || envRoomOrg ? null : trimmed(env[PROJECT_ENV]);
|
|
210
|
-
|
|
211
|
-
let envOrg = null;
|
|
212
|
-
let envDetail = null;
|
|
213
|
-
if (envDirect) {
|
|
214
|
-
envOrg = assertOrgIdShape(envDirect, ORG_ENV);
|
|
215
|
-
envDetail = ORG_ENV;
|
|
216
|
-
} else if (envRoomOrg) {
|
|
217
|
-
envOrg = assertOrgIdShape(envRoomOrg, ROOM_ENV);
|
|
218
|
-
envDetail = ROOM_ENV;
|
|
219
|
-
} else if (envProject && bindingOrg) {
|
|
220
|
-
// Only worth a lookup when a binding exists to disagree with; otherwise the
|
|
221
|
-
// env class wins uncontested and the lookup happens once, below.
|
|
222
|
-
envOrg = await orgOfProject(envProject, { required: false });
|
|
223
|
-
envDetail = PROJECT_ENV;
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
if (envOrg && bindingOrg && envOrg !== bindingOrg && !CONFLICT_EXEMPT.has(cmd)) {
|
|
227
|
-
// The wallet tier's rule, scoped to the same pair. A binding deliberately
|
|
228
|
-
// declared by a checkout outranks the profile's selection SILENTLY — that
|
|
229
|
-
// is what a binding is for — but an ambient env var disagreeing with a
|
|
230
|
-
// committed file is exactly the surprise worth stopping on.
|
|
231
|
-
fail({
|
|
232
|
-
code: "AMBIGUOUS_ORG",
|
|
233
|
-
message: `Ambiguous organization: ${envDetail}=${envOrg} but ${bindingHit.file} binds ${bindingOrg}.`,
|
|
234
|
-
hint: `Resolve with one of: pass --org <org_id>, unset ${envDetail}, or edit the binding file.`,
|
|
235
|
-
details: {
|
|
236
|
-
candidates: [
|
|
237
|
-
{ org_id: envOrg, source: "env", source_detail: envDetail },
|
|
238
|
-
{ org_id: bindingOrg, source: "binding", source_detail: bindingHit.file },
|
|
239
|
-
],
|
|
240
|
-
},
|
|
241
|
-
next_actions: [
|
|
242
|
-
nextAction("edit_request", {
|
|
243
|
-
command: "run402 <command> --org <org_id>",
|
|
244
|
-
why: "The flag resolves the conflict for this call.",
|
|
245
|
-
}),
|
|
246
|
-
],
|
|
247
|
-
});
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
if (envOrg) return { orgId: envOrg, source: "env", sourceDetail: envDetail };
|
|
251
|
-
if (envProject) {
|
|
252
|
-
const orgId = await orgOfProject(envProject, { required: false });
|
|
253
|
-
if (orgId) return { orgId, source: "env", sourceDetail: PROJECT_ENV };
|
|
254
|
-
}
|
|
255
|
-
if (bindingOrg) return { orgId: bindingOrg, source: "binding", sourceDetail: bindingHit.file };
|
|
256
|
-
|
|
257
|
-
// --- Class 3.5: vault local pin (kygit-handoff design D10) ------------
|
|
258
|
-
// `r402.orgId` in this checkout's LOCAL git config — written by every
|
|
259
|
-
// vault pin site (`repos create`, `resume`, address resolution), a
|
|
260
|
-
// rung below env/binding and above profile state: a checkout that IS a
|
|
261
|
-
// resumed/pinned vault should resolve its own org with zero configuration,
|
|
262
|
-
// but an explicit env var or a committed `.run402.json` binding still wins.
|
|
263
|
-
const pinnedOrg = await readVaultPinnedOrgId(cwd);
|
|
264
|
-
if (pinnedOrg) return { orgId: pinnedOrg, source: "vault_pin", sourceDetail: "r402.orgId (local git config)" };
|
|
265
|
-
|
|
266
|
-
// --- Class 4: profile state ----------------------------------------------
|
|
267
|
-
const selected = trimmed(coreGetActiveOrgId());
|
|
268
|
-
if (selected) {
|
|
269
|
-
return { orgId: assertOrgIdShape(selected, "orgs use"), source: "profile", sourceDetail: "orgs use" };
|
|
270
|
-
}
|
|
271
|
-
const activeProject = trimmed(getActiveProjectId());
|
|
272
|
-
if (activeProject) {
|
|
273
|
-
const orgId = await orgOfProject(activeProject, { required: false });
|
|
274
|
-
if (orgId) return { orgId, source: "profile", sourceDetail: "projects use" };
|
|
51
|
+
try {
|
|
52
|
+
return await getSdk().orgs.resolve({ org, project }, { env, cwd, optional, allowConflict: CONFLICT_EXEMPT.has(cmd) });
|
|
53
|
+
} catch (err) {
|
|
54
|
+
return failResolver(err);
|
|
275
55
|
}
|
|
276
|
-
|
|
277
|
-
if (optional) return null;
|
|
278
|
-
fail({
|
|
279
|
-
code: "ORG_REQUIRED",
|
|
280
|
-
message: "No organization specified and no current organization set.",
|
|
281
|
-
hint: `Pass --org <org_id>, set ${ORG_ENV}, bind this directory in .run402.json, or run: run402 orgs use <org_id>`,
|
|
282
|
-
next_actions: orgRequiredActions(),
|
|
283
|
-
});
|
|
284
|
-
return null; // unreachable — fail() exits
|
|
285
56
|
}
|
|
286
57
|
|
|
287
|
-
/**
|
|
288
|
-
* Resolve the addressed organization id from normalized argv.
|
|
289
|
-
*
|
|
290
|
-
* The Wave E signature, preserved: callers that only need the id keep passing
|
|
291
|
-
* argv and getting a string back. Use {@link resolveOrg} when the provenance
|
|
292
|
-
* pair is wanted too.
|
|
293
|
-
*/
|
|
58
|
+
/** The addressed organization id from normalized argv (see {@link resolveOrg}). */
|
|
294
59
|
export async function resolveOrgId(a, opts = {}) {
|
|
295
60
|
const resolved = await resolveOrg(a, opts);
|
|
296
61
|
return resolved ? resolved.orgId : null;
|
|
297
62
|
}
|
|
298
63
|
|
|
299
64
|
/**
|
|
300
|
-
* The org-scoped POSITIONAL contract
|
|
301
|
-
*
|
|
302
|
-
*
|
|
303
|
-
*
|
|
304
|
-
*
|
|
305
|
-
*
|
|
306
|
-
* own next positional (a wallet, an email, a principal) and the org comes from
|
|
307
|
-
* the shared chain: `--org`, then `RUN402_ORG`, then the `.run402.json`
|
|
308
|
-
* binding, then `orgs use`. So inside a bound checkout the two-agent case reads
|
|
309
|
-
* `run402 orgs members add 0xB… --role developer` with nothing else to know.
|
|
310
|
-
*
|
|
311
|
-
* Naming the org twice with different values (positional AND `--org`) is
|
|
312
|
-
* `AMBIGUOUS_ORG`, never a silent pick — the same refusal the env-vs-binding
|
|
313
|
-
* pair gets. A non-org first positional with no chain answer fails
|
|
314
|
-
* `ORG_REQUIRED` and NAMES the rejected value, so `orgs get foo` says why `foo`
|
|
315
|
-
* did not count rather than reporting a bare "no organization".
|
|
65
|
+
* The org-scoped POSITIONAL contract: `<org_id>` is optional sugar on every
|
|
66
|
+
* verb that acts on an organization. A leading positional that IS an org id
|
|
67
|
+
* addresses that org; anything else is the verb's own next positional and the
|
|
68
|
+
* org comes from the chain. Naming the org twice with different values
|
|
69
|
+
* (positional AND `--org`) is `AMBIGUOUS_ORG`; a non-org first positional with
|
|
70
|
+
* no chain answer fails `ORG_REQUIRED` naming the rejected value.
|
|
316
71
|
*
|
|
317
72
|
* Returns the org id with provenance plus the REMAINING positionals; bound
|
|
318
|
-
* those with {@link requireRest}
|
|
319
|
-
*
|
|
320
|
-
* through this shape by `cli-conventions-gate.test.mjs`.
|
|
73
|
+
* those with {@link requireRest}. Every `orgScoped` entry in the command
|
|
74
|
+
* manifest is driven through this shape by `cli-conventions-gate.test.mjs`.
|
|
321
75
|
*
|
|
322
76
|
* @param {string[]} a normalized argv
|
|
323
77
|
* @param {string[]} valueFlags flags that take a value (must include "--org")
|
|
@@ -345,16 +99,8 @@ export async function takeOrgPositional(a, valueFlags = [], opts = {}) {
|
|
|
345
99
|
}
|
|
346
100
|
const resolved = await resolveOrg(a, { ...opts, optional: true });
|
|
347
101
|
if (resolved) return { orgId: resolved.orgId, rest: positionals, source: resolved.source, sourceDetail: resolved.sourceDetail };
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
message: typeof first === "string"
|
|
351
|
-
? `No organization specified and no current organization set (${JSON.stringify(first)} is not an org_id — an org_id is a UUID).`
|
|
352
|
-
: "No organization specified and no current organization set.",
|
|
353
|
-
hint: `Pass --org <org_id> (or a leading <org_id> positional), set ${ORG_ENV}, bind this directory in .run402.json, or run: run402 orgs use <org_id>`,
|
|
354
|
-
...(typeof first === "string" ? { details: { rejected_positional: first } } : {}),
|
|
355
|
-
next_actions: orgRequiredActions(),
|
|
356
|
-
});
|
|
357
|
-
return null; // unreachable — fail() exits
|
|
102
|
+
failLocal(orgRequiredError({ ...(typeof first === "string" ? { rejectedPositional: first } : {}), positionalHint: true }));
|
|
103
|
+
return null; // unreachable — failLocal() exits
|
|
358
104
|
}
|
|
359
105
|
|
|
360
106
|
/**
|
|
@@ -377,94 +123,11 @@ export function requireRest(rest, opts = {}) {
|
|
|
377
123
|
return rest;
|
|
378
124
|
}
|
|
379
125
|
|
|
380
|
-
/** Validate an org id supplied by a human, naming the origin.
|
|
126
|
+
/** Validate an org id supplied by a human, naming the origin. Exits on a bad shape. */
|
|
381
127
|
export function requireOrgIdShape(orgId, origin = "--org") {
|
|
382
|
-
return assertOrgIdShape(orgId, origin);
|
|
383
|
-
}
|
|
384
|
-
|
|
385
|
-
/** The profile's selected organization, without running the chain. */
|
|
386
|
-
export function getSelectedOrgId() {
|
|
387
|
-
return trimmed(coreGetActiveOrgId());
|
|
388
|
-
}
|
|
389
|
-
|
|
390
|
-
/** Record the profile's selected organization. */
|
|
391
|
-
export function setSelectedOrgId(orgId, origin = "orgs use") {
|
|
392
|
-
coreSetActiveOrgId(assertOrgIdShape(orgId, origin));
|
|
393
|
-
}
|
|
394
|
-
|
|
395
|
-
/** Clear the profile's selected organization. */
|
|
396
|
-
export function clearSelectedOrgId() {
|
|
397
|
-
coreClearActiveOrgId();
|
|
398
|
-
}
|
|
399
|
-
|
|
400
|
-
/**
|
|
401
|
-
* Stamp a project's owning organization as the profile selection.
|
|
402
|
-
*
|
|
403
|
-
* Called by `projects use`: a project determines its organization
|
|
404
|
-
* unambiguously, so selecting one keeps the two lowest classes from ever
|
|
405
|
-
* disagreeing in practice — and gives an existing user a current org without
|
|
406
|
-
* reading a changelog. Best-effort: a failure here must never fail the project
|
|
407
|
-
* selection the caller actually asked for.
|
|
408
|
-
*/
|
|
409
|
-
export async function stampOrgFromProject(projectId) {
|
|
410
|
-
try {
|
|
411
|
-
const orgId = await orgOfProject(projectId, { required: false });
|
|
412
|
-
if (orgId) {
|
|
413
|
-
coreSetActiveOrgId(orgId);
|
|
414
|
-
return orgId;
|
|
415
|
-
}
|
|
416
|
-
} catch {
|
|
417
|
-
/* best-effort */
|
|
418
|
-
}
|
|
419
|
-
return null;
|
|
420
|
-
}
|
|
421
|
-
|
|
422
|
-
/** Provenance pair for command output: bounded, never a resolution trace. */
|
|
423
|
-
export function orgProvenance(resolved) {
|
|
424
|
-
return resolved
|
|
425
|
-
? { org_id: resolved.orgId, org_source: resolved.source, org_source_detail: resolved.sourceDetail }
|
|
426
|
-
: { org_id: null, org_source: null, org_source_detail: null };
|
|
427
|
-
}
|
|
428
|
-
|
|
429
|
-
/**
|
|
430
|
-
* The organization that OWNS a specific project, or `null`.
|
|
431
|
-
*
|
|
432
|
-
* Distinct from {@link resolveOrgId}: that one answers "which org is this
|
|
433
|
-
* command addressed at" (flag → env → active project); this one answers "who
|
|
434
|
-
* owns THIS project id", which is what a project-scoped scaffold needs and
|
|
435
|
-
* where an `--org` override would be a mis-binding, not a convenience.
|
|
436
|
-
*
|
|
437
|
-
* WHY IT CACHES (task 5.12c): the vault scaffold is the one part of
|
|
438
|
-
* `run402 init` the client-surface spec says adds no network dependency to the
|
|
439
|
-
* cold-start path, and resolving the org through `projects.list()` quietly made
|
|
440
|
-
* that untrue. `org_id` is a non-secret routing identifier the control plane
|
|
441
|
-
* already hands back with every listing, so the honest fix is to remember it —
|
|
442
|
-
* the FIRST call on a machine still asks, every returning one reads it locally.
|
|
443
|
-
*
|
|
444
|
-
* Cached in the local project entry after the first lookup, and an EXACT id
|
|
445
|
-
* match is required: a near-miss must never bind a vault or a git remote to
|
|
446
|
-
* somebody else's project. `updateProject` is a no-op for a project this
|
|
447
|
-
* machine holds no credentials for, so such a project asks again next time
|
|
448
|
-
* rather than being silently mis-cached. Returns `null` rather than throwing —
|
|
449
|
-
* every caller has something better to say than a stack trace.
|
|
450
|
-
*/
|
|
451
|
-
export async function resolveOwningOrgId(projectId) {
|
|
452
|
-
const cached = getProject(projectId)?.org_id;
|
|
453
|
-
if (typeof cached === "string" && cached.length > 0) return cached;
|
|
454
128
|
try {
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
const orgId = row?.org_id;
|
|
459
|
-
if (typeof orgId !== "string" || orgId.length === 0) return null;
|
|
460
|
-
try {
|
|
461
|
-
updateProject(projectId, { org_id: orgId });
|
|
462
|
-
} catch {
|
|
463
|
-
// Caching is an optimization; a read-only or contended keystore costs a
|
|
464
|
-
// round trip next time and must never fail the caller.
|
|
465
|
-
}
|
|
466
|
-
return orgId;
|
|
467
|
-
} catch {
|
|
468
|
-
return null;
|
|
129
|
+
return assertOrgIdShape(orgId, origin);
|
|
130
|
+
} catch (err) {
|
|
131
|
+
return failResolver(err);
|
|
469
132
|
}
|
|
470
133
|
}
|
package/lib/orgs.mjs
CHANGED
|
@@ -1,17 +1,6 @@
|
|
|
1
1
|
import { getSdk } from "./sdk.mjs";
|
|
2
|
-
import { reportSdkError, fail } from "./sdk-errors.mjs";
|
|
3
|
-
import {
|
|
4
|
-
import { nextAction } from "./next-actions.mjs";
|
|
5
|
-
import {
|
|
6
|
-
requireOrgIdShape,
|
|
7
|
-
resolveOrg,
|
|
8
|
-
takeOrgPositional,
|
|
9
|
-
requireRest,
|
|
10
|
-
orgProvenance,
|
|
11
|
-
getSelectedOrgId,
|
|
12
|
-
setSelectedOrgId,
|
|
13
|
-
clearSelectedOrgId,
|
|
14
|
-
} from "./org-context.mjs";
|
|
2
|
+
import { reportSdkError, fail, failLocal } from "./sdk-errors.mjs";
|
|
3
|
+
import { resolveOrg, takeOrgPositional, requireRest } from "./org-context.mjs";
|
|
15
4
|
import {
|
|
16
5
|
normalizeArgv,
|
|
17
6
|
assertKnownFlags,
|
|
@@ -370,130 +359,57 @@ async function use(args) {
|
|
|
370
359
|
requirePositionalCount(a, [], {
|
|
371
360
|
min: 1, max: 1, command: "run402 orgs use <org_id>", missing: "<org_id>",
|
|
372
361
|
});
|
|
373
|
-
|
|
374
|
-
setSelectedOrgId(orgId);
|
|
375
|
-
console.log(JSON.stringify({ org_id: orgId, selected: true, scope: "wallet_profile" }, null, 2));
|
|
362
|
+
await printOrgContext((orgs) => orgs.use(positionalArgs(a, [])[0]));
|
|
376
363
|
}
|
|
377
364
|
|
|
378
365
|
async function clear(args) {
|
|
379
366
|
const a = normalizeArgv(args);
|
|
380
367
|
assertKnownFlags(a, ["--help", "-h"]);
|
|
381
368
|
requirePositionalCount(a, [], { min: 0, max: 0, command: "run402 orgs clear" });
|
|
382
|
-
|
|
383
|
-
clearSelectedOrgId();
|
|
384
|
-
console.log(JSON.stringify({ org_id: null, selected: false, previous_org_id: previous ?? null }, null, 2));
|
|
369
|
+
await printOrgContext((orgs) => orgs.clear());
|
|
385
370
|
}
|
|
386
371
|
|
|
387
372
|
/**
|
|
388
|
-
*
|
|
389
|
-
*
|
|
390
|
-
*
|
|
391
|
-
*/
|
|
392
|
-
function roomKeyFromDir(dir) {
|
|
393
|
-
const base = dir.split("/").filter(Boolean).pop() ?? "";
|
|
394
|
-
const slug = base.toLowerCase().replace(/[^a-z0-9._-]+/g, "-").replace(/^-+/, "").slice(0, 64);
|
|
395
|
-
return /^[a-z0-9]/.test(slug) ? slug : null;
|
|
396
|
-
}
|
|
397
|
-
|
|
398
|
-
/**
|
|
399
|
-
* Write this checkout's org (and room) into `.run402.json`.
|
|
400
|
-
*
|
|
401
|
-
* WHY THIS PICKS FOR YOU WHEN YOU OWN EXACTLY ONE ORG, while the resolution
|
|
402
|
-
* chain never does: they are different acts. The chain runs on EVERY command
|
|
403
|
-
* and must not change meaning the day you are invited to a second org — so it
|
|
404
|
-
* refuses to infer. This runs ONCE, because you asked it to, and it WRITES THE
|
|
405
|
-
* ANSWER DOWN. Nothing is inferred afterwards; the file is read verbatim
|
|
406
|
-
* forever after. An explicit act with a recorded result is not a heuristic.
|
|
407
|
-
*
|
|
408
|
-
* With two or more orgs there is nothing to pick, so it lists them and stops.
|
|
373
|
+
* Write this checkout's org (and room) into `.run402.json`. With no `--org`,
|
|
374
|
+
* the caller's sole membership is written down; the resolution chain itself
|
|
375
|
+
* never infers one.
|
|
409
376
|
*/
|
|
410
377
|
async function bind(args) {
|
|
411
378
|
const a = normalizeArgv(args);
|
|
412
379
|
const valueFlags = ["--org", "--room"];
|
|
413
380
|
assertKnownFlags(a, [...valueFlags, "--help", "-h"], valueFlags);
|
|
414
381
|
requirePositionalCount(a, valueFlags, { min: 0, max: 0, command: "run402 orgs bind [--org <org_id>] [--room <key>]" });
|
|
415
|
-
|
|
416
|
-
let orgId = flagValue(a, "--org");
|
|
417
|
-
let picked = "flag";
|
|
418
|
-
if (!orgId) {
|
|
419
|
-
let orgs;
|
|
420
|
-
try {
|
|
421
|
-
orgs = await getSdk().orgs.list();
|
|
422
|
-
} catch (err) {
|
|
423
|
-
reportSdkError(err);
|
|
424
|
-
return;
|
|
425
|
-
}
|
|
426
|
-
const rows = Array.isArray(orgs) ? orgs : (orgs?.orgs ?? []);
|
|
427
|
-
if (rows.length === 0) {
|
|
428
|
-
fail({
|
|
429
|
-
code: "NO_ORGS",
|
|
430
|
-
message: "This wallet is a member of no organization yet.",
|
|
431
|
-
hint: "Run 'run402 init' to provision one, or ask an owner to add you with 'run402 orgs members add'.",
|
|
432
|
-
next_actions: [nextAction("initialize_wallet", { command: "run402 init", why: "Provision this wallet's organization, then retry." })],
|
|
433
|
-
});
|
|
434
|
-
}
|
|
435
|
-
if (rows.length > 1) {
|
|
436
|
-
fail({
|
|
437
|
-
code: "AMBIGUOUS_ORG",
|
|
438
|
-
message: `This wallet belongs to ${rows.length} organizations — name the one to bind.`,
|
|
439
|
-
hint: "run402 orgs bind --org <org_id>",
|
|
440
|
-
details: { orgs: rows.map((o) => ({ org_id: o.org_id, display_name: o.display_name ?? null, role: o.role ?? null })) },
|
|
441
|
-
next_actions: [nextAction("edit_request", { command: "run402 orgs bind --org <org_id>", why: "Name which organization this checkout coordinates in." })],
|
|
442
|
-
});
|
|
443
|
-
}
|
|
444
|
-
orgId = rows[0].org_id;
|
|
445
|
-
picked = "sole_membership";
|
|
446
|
-
}
|
|
447
|
-
|
|
448
|
-
const room = flagValue(a, "--room") ?? roomKeyFromDir(process.cwd());
|
|
449
|
-
const { contents, file } = updateBindingFile(process.cwd(), {
|
|
450
|
-
org: requireOrgIdShape(orgId, picked === "flag" ? "--org" : "orgs list"),
|
|
451
|
-
...(room ? { room } : {}),
|
|
452
|
-
});
|
|
453
|
-
console.log(JSON.stringify({
|
|
454
|
-
org_id: orgId,
|
|
455
|
-
room_key: room ?? null,
|
|
456
|
-
org_source: picked,
|
|
457
|
-
file: ".run402.json",
|
|
458
|
-
path: file,
|
|
459
|
-
bound: true,
|
|
460
|
-
safe_to_commit: true,
|
|
461
|
-
note: "Safe to commit — an org id is an identifier, not a credential; authorization stays server-side.",
|
|
462
|
-
binding: contents,
|
|
463
|
-
}, null, 2));
|
|
382
|
+
await printOrgContext((orgs) => orgs.bind(flagValue(a, "--org"), { room: flagValue(a, "--room") }));
|
|
464
383
|
}
|
|
465
384
|
|
|
466
385
|
async function unbind(args) {
|
|
467
386
|
const a = normalizeArgv(args);
|
|
468
387
|
assertKnownFlags(a, ["--help", "-h"]);
|
|
469
388
|
requirePositionalCount(a, [], { min: 0, max: 0, command: "run402 orgs unbind" });
|
|
470
|
-
|
|
471
|
-
const { contents, removed } = updateBindingFile(process.cwd(), { org: null, room: null });
|
|
472
|
-
console.log(JSON.stringify({
|
|
473
|
-
file: ".run402.json",
|
|
474
|
-
unbound: previous.org !== undefined || previous.room !== undefined,
|
|
475
|
-
removed,
|
|
476
|
-
binding: contents,
|
|
477
|
-
}, null, 2));
|
|
389
|
+
await printOrgContext((orgs) => orgs.unbind());
|
|
478
390
|
}
|
|
479
391
|
|
|
480
392
|
async function current(args) {
|
|
481
393
|
const a = normalizeArgv(args);
|
|
482
394
|
assertKnownFlags(a, ["--help", "-h"]);
|
|
483
395
|
requirePositionalCount(a, [], { min: 0, max: 0, command: "run402 orgs current" });
|
|
396
|
+
await printOrgContext((orgs) => orgs.current());
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
/** The organization-context refusals `r.orgs` raises locally; anything else is the server's answer. */
|
|
400
|
+
const ORG_CONTEXT_CODES = new Set(["BAD_ORG_ID", "AMBIGUOUS_ORG", "ORG_REQUIRED", "NO_ORGS"]);
|
|
401
|
+
|
|
402
|
+
/** Run one `r.orgs` context verb and print its result, or the refusal envelope. */
|
|
403
|
+
async function printOrgContext(fn) {
|
|
404
|
+
let result;
|
|
484
405
|
try {
|
|
485
|
-
|
|
486
|
-
// command that reports the selection must stay usable while it is ambiguous.
|
|
487
|
-
// `optional` keeps an empty selection an explicit null state rather than a
|
|
488
|
-
// failure — reporting is not acting.
|
|
489
|
-
const resolved = await resolveOrg([], { cmd: "orgs", optional: true });
|
|
490
|
-
console.log(JSON.stringify({
|
|
491
|
-
...orgProvenance(resolved),
|
|
492
|
-
selected_org_id: getSelectedOrgId() ?? null,
|
|
493
|
-
}, null, 2));
|
|
406
|
+
result = await fn(getSdk().orgs);
|
|
494
407
|
} catch (err) {
|
|
408
|
+
if (err?.kind === "local_error" && ORG_CONTEXT_CODES.has(err?.code)) failLocal(err);
|
|
495
409
|
reportSdkError(err);
|
|
410
|
+
return;
|
|
496
411
|
}
|
|
412
|
+
console.log(JSON.stringify(result, null, 2));
|
|
497
413
|
}
|
|
498
414
|
|
|
499
415
|
async function get(args) {
|
package/lib/projects.mjs
CHANGED
|
@@ -4,7 +4,6 @@ import { loadLiveControlPlaneSession } from "../core-dist/control-plane-session.
|
|
|
4
4
|
import { withAutoApprove } from "./sign-in.mjs";
|
|
5
5
|
import { getSdk } from "./sdk.mjs";
|
|
6
6
|
import { reportSdkError, fail, parseFlagJson } from "./sdk-errors.mjs";
|
|
7
|
-
import { stampOrgFromProject } from "./org-context.mjs";
|
|
8
7
|
import { assertKnownFlags, failBadProjectId, flagValue, hasHelp, normalizeArgv, positionalArgs, resolveProjectSelector, validateRegularFile, failUnknownSubcommand } from "./argparse.mjs";
|
|
9
8
|
|
|
10
9
|
const HELP = `run402 projects — Manage your deployed Run402 projects
|
|
@@ -805,7 +804,7 @@ async function use(projectId) {
|
|
|
805
804
|
// A project determines its org unambiguously, so selecting one also makes
|
|
806
805
|
// that org current (add-cli-current-org D4). Best-effort by construction:
|
|
807
806
|
// the project selection the caller asked for must not fail on this.
|
|
808
|
-
const orgId = await
|
|
807
|
+
const orgId = await getSdk().orgs.selectFromProject(projectId);
|
|
809
808
|
console.log(JSON.stringify({ active_project_id: projectId, set: true, org_id: orgId ?? null }));
|
|
810
809
|
} catch (err) {
|
|
811
810
|
reportSdkError(err);
|