@odla-ai/cli 0.27.17 → 0.28.1
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/dist/bin.cjs +297 -172
- package/dist/bin.cjs.map +1 -1
- package/dist/bin.js +1 -1
- package/dist/{chunk-3WVVHH3Y.js → chunk-STJCWZJU.js} +274 -159
- package/dist/chunk-STJCWZJU.js.map +1 -0
- package/dist/{cli-DCSVQAZ6.js → cli-2WZIFXA6.js} +2 -2
- package/dist/index.cjs +273 -158
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +1 -1
- package/package.json +1 -1
- package/dist/chunk-3WVVHH3Y.js.map +0 -1
- /package/dist/{cli-DCSVQAZ6.js.map → cli-2WZIFXA6.js.map} +0 -0
package/dist/bin.cjs
CHANGED
|
@@ -509,7 +509,7 @@ async function getDeveloperToken(cfg, options, doFetch, out, grantRequest = {})
|
|
|
509
509
|
const optionalProjectCapabilities = grantRequest.optionalProjectCapabilities ?? [];
|
|
510
510
|
const grantIntent = { projectIds: [cfg.app.id], optionalProjectCapabilities };
|
|
511
511
|
const cached = readJsonFile(cfg.local.tokenFile);
|
|
512
|
-
if (!grantRequest.forceReview) {
|
|
512
|
+
if (!grantRequest.forceReview && !grantRequest.freshLogin) {
|
|
513
513
|
if (options.token) return options.token;
|
|
514
514
|
if (import_node_process4.default.env.ODLA_DEV_TOKEN) {
|
|
515
515
|
const declared = import_node_process4.default.env.ODLA_DEV_TOKEN_AUDIENCE;
|
|
@@ -526,9 +526,9 @@ async function getDeveloperToken(cfg, options, doFetch, out, grantRequest = {})
|
|
|
526
526
|
}
|
|
527
527
|
} else {
|
|
528
528
|
if (options.token) {
|
|
529
|
-
throw new Error("--request-grant cannot be combined with --token; remove --token so the approved replacement credential can be collected and cached");
|
|
529
|
+
throw new Error(grantRequest.forceReview ? "--request-grant cannot be combined with --token; remove --token so the approved replacement credential can be collected and cached" : "a fresh authorization cannot be combined with --token; remove --token so the approved replacement credential can be collected and cached");
|
|
530
530
|
}
|
|
531
|
-
out.error(`auth: requesting fresh owner review for app.manage on exact project "${cfg.app.id}"`);
|
|
531
|
+
out.error(grantRequest.forceReview ? `auth: requesting fresh owner review for app.manage on exact project "${cfg.app.id}"` : `auth: requesting a fresh agent sign-in for exact project "${cfg.app.id}"`);
|
|
532
532
|
}
|
|
533
533
|
const ctx = {
|
|
534
534
|
cfg,
|
|
@@ -541,6 +541,7 @@ async function getDeveloperToken(cfg, options, doFetch, out, grantRequest = {})
|
|
|
541
541
|
grantIntent
|
|
542
542
|
};
|
|
543
543
|
const waitMs = handshakeWaitMs(options.wait);
|
|
544
|
+
if (grantRequest.freshLogin) clearPendingHandshake(ctx.pendingFile);
|
|
544
545
|
const { token, expiresAt } = await resumePendingHandshake(ctx, waitMs) ?? await freshHandshake(ctx, waitMs);
|
|
545
546
|
clearPendingHandshake(ctx.pendingFile);
|
|
546
547
|
writePrivateJson(cfg.local.tokenFile, {
|
|
@@ -598,11 +599,12 @@ async function resumePendingHandshake(ctx, waitMs) {
|
|
|
598
599
|
async function freshHandshake(ctx, waitMs) {
|
|
599
600
|
let started;
|
|
600
601
|
let stopReminder;
|
|
602
|
+
const provisioning = ctx.grantIntent.optionalProjectCapabilities.includes("app.manage");
|
|
601
603
|
try {
|
|
602
604
|
return await (0, import_db.requestToken)({
|
|
603
605
|
endpoint: ctx.cfg.platformUrl,
|
|
604
606
|
email: ctx.email,
|
|
605
|
-
label: `${ctx.cfg.app.id} provisioner`,
|
|
607
|
+
label: `${ctx.cfg.app.id} ${provisioning ? "provisioner" : "agent"}`,
|
|
606
608
|
agentHandle: projectAgentHandle(ctx.cfg.app.id),
|
|
607
609
|
projectIds: [ctx.cfg.app.id],
|
|
608
610
|
optionalProjectCapabilities: ctx.grantIntent.optionalProjectCapabilities,
|
|
@@ -663,8 +665,15 @@ function stillPending(pending, email) {
|
|
|
663
665
|
}
|
|
664
666
|
function handshakeEmail(value2, cached) {
|
|
665
667
|
const email = (value2 ?? import_node_process4.default.env.ODLA_USER_EMAIL ?? cached ?? "").trim().toLowerCase();
|
|
668
|
+
if (/@users\.noreply\.github\.com$/i.test(email)) {
|
|
669
|
+
throw new Error(
|
|
670
|
+
`"${email}" is a GitHub commit identity, not an odla account email; use --email <signed-in-odla-account> or ODLA_USER_EMAIL`
|
|
671
|
+
);
|
|
672
|
+
}
|
|
666
673
|
if (email.length > 254 || !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) {
|
|
667
|
-
throw new Error(
|
|
674
|
+
throw new Error(
|
|
675
|
+
"a fresh odla handshake requires --email <account> or ODLA_USER_EMAIL; use the signed-in email shown in odla Studio, not git or GitHub identity"
|
|
676
|
+
);
|
|
668
677
|
}
|
|
669
678
|
return email;
|
|
670
679
|
}
|
|
@@ -1958,6 +1967,223 @@ var init_admin_command = __esm({
|
|
|
1958
1967
|
}
|
|
1959
1968
|
});
|
|
1960
1969
|
|
|
1970
|
+
// src/whoami-command.ts
|
|
1971
|
+
function principalKind(value2, machine) {
|
|
1972
|
+
return value2 === "human" || value2 === "agent" || value2 === "service" ? value2 : machine ? "service" : "human";
|
|
1973
|
+
}
|
|
1974
|
+
function credentialKind(value2, machine, scopes) {
|
|
1975
|
+
if (value2 === "machine" || value2 === "device" || value2 === "clerk") return value2;
|
|
1976
|
+
if (machine) return "machine";
|
|
1977
|
+
if (scopes.length) return "device";
|
|
1978
|
+
return "unknown";
|
|
1979
|
+
}
|
|
1980
|
+
function managerOf(value2) {
|
|
1981
|
+
if (!value2 || typeof value2 !== "object") return null;
|
|
1982
|
+
const row = value2;
|
|
1983
|
+
const principalId = text(row.principalId);
|
|
1984
|
+
if (!principalId) return null;
|
|
1985
|
+
return {
|
|
1986
|
+
principalId,
|
|
1987
|
+
displayName: text(row.displayName) ?? "Unnamed member",
|
|
1988
|
+
handle: text(row.handle) ?? ""
|
|
1989
|
+
};
|
|
1990
|
+
}
|
|
1991
|
+
function unnamedPrincipal(kind) {
|
|
1992
|
+
if (kind === "agent") return "Unnamed agent";
|
|
1993
|
+
if (kind === "service") return "Unnamed service";
|
|
1994
|
+
return "Unnamed member";
|
|
1995
|
+
}
|
|
1996
|
+
async function fetchIdentity(platformUrl, token, doFetch) {
|
|
1997
|
+
const res = await doFetch(`${platformUrl.replace(/\/$/, "")}/registry/me`, {
|
|
1998
|
+
headers: { authorization: `Bearer ${token}` }
|
|
1999
|
+
});
|
|
2000
|
+
if (!res.ok) throw new Error(`could not resolve identity (HTTP ${res.status})`);
|
|
2001
|
+
const body = await res.json();
|
|
2002
|
+
const developerId = text(body.developerId) ?? "";
|
|
2003
|
+
const machine = body.machine === true;
|
|
2004
|
+
const scopes = Array.isArray(body.scopes) ? body.scopes.map(String) : [];
|
|
2005
|
+
const principalId = text(body.principalId) ?? developerId;
|
|
2006
|
+
const email = text(body.email);
|
|
2007
|
+
const kind = principalKind(body.principalKind, machine);
|
|
2008
|
+
const displayName = text(body.displayName) ?? email ?? unnamedPrincipal(kind);
|
|
2009
|
+
const handle = text(body.handle) ?? "";
|
|
2010
|
+
const credential2 = body.credential && typeof body.credential === "object" ? body.credential : {};
|
|
2011
|
+
return {
|
|
2012
|
+
developerId,
|
|
2013
|
+
principalId,
|
|
2014
|
+
principalKind: kind,
|
|
2015
|
+
displayName,
|
|
2016
|
+
handle,
|
|
2017
|
+
manager: managerOf(body.manager),
|
|
2018
|
+
credential: {
|
|
2019
|
+
id: text(credential2.id),
|
|
2020
|
+
kind: credentialKind(credential2.kind, machine, scopes)
|
|
2021
|
+
},
|
|
2022
|
+
email,
|
|
2023
|
+
admin: body.admin === true,
|
|
2024
|
+
machine,
|
|
2025
|
+
scopes
|
|
2026
|
+
};
|
|
2027
|
+
}
|
|
2028
|
+
function credentialLabel(identity) {
|
|
2029
|
+
if (identity.credential.kind === "machine") return "machine (platform admin secret)";
|
|
2030
|
+
if (identity.credential.kind === "device")
|
|
2031
|
+
return identity.scopes.length ? "device (scoped)" : "device";
|
|
2032
|
+
if (identity.credential.kind === "clerk") return "clerk";
|
|
2033
|
+
return "unknown (legacy server)";
|
|
2034
|
+
}
|
|
2035
|
+
function namedPrincipal(identity) {
|
|
2036
|
+
return identity.handle && identity.handle !== identity.displayName ? `${identity.displayName} (@${identity.handle})` : identity.displayName;
|
|
2037
|
+
}
|
|
2038
|
+
function namedManager(manager) {
|
|
2039
|
+
return manager.handle && manager.handle !== manager.displayName ? `${manager.displayName} (@${manager.handle})` : manager.displayName;
|
|
2040
|
+
}
|
|
2041
|
+
function accountableOwner(identity) {
|
|
2042
|
+
if (identity.principalKind === "agent" && identity.manager) {
|
|
2043
|
+
return namedManager(identity.manager);
|
|
2044
|
+
}
|
|
2045
|
+
if (identity.principalKind === "human" && identity.principalId === identity.developerId) {
|
|
2046
|
+
return namedPrincipal(identity);
|
|
2047
|
+
}
|
|
2048
|
+
return identity.email ?? "Unnamed member";
|
|
2049
|
+
}
|
|
2050
|
+
async function whoamiCommand(parsed, deps = {}) {
|
|
2051
|
+
assertArgs(
|
|
2052
|
+
parsed,
|
|
2053
|
+
["config", "context", "platform", "token", "email", "json", "open"],
|
|
2054
|
+
1
|
|
2055
|
+
);
|
|
2056
|
+
const out = deps.stdout ?? console;
|
|
2057
|
+
const doFetch = deps.fetch ?? fetch;
|
|
2058
|
+
const { cfg } = await resolveOperatorContext(parsed, {
|
|
2059
|
+
allowMissingConfig: true
|
|
2060
|
+
});
|
|
2061
|
+
const token = await getDeveloperToken(
|
|
2062
|
+
cfg,
|
|
2063
|
+
{
|
|
2064
|
+
configPath: cfg.configPath,
|
|
2065
|
+
token: stringOpt(parsed.options.token),
|
|
2066
|
+
email: stringOpt(parsed.options.email),
|
|
2067
|
+
// As above: never suppress the browser for an ordinary sign-in.
|
|
2068
|
+
open: parsed.options.open === false ? false : parsed.options.open === true ? true : void 0,
|
|
2069
|
+
openApprovalUrl: deps.openUrl
|
|
2070
|
+
},
|
|
2071
|
+
doFetch,
|
|
2072
|
+
out
|
|
2073
|
+
);
|
|
2074
|
+
const identity = await fetchIdentity(cfg.platformUrl, token, doFetch);
|
|
2075
|
+
if (parsed.options.json === true) {
|
|
2076
|
+
out.log(JSON.stringify(identity, null, 2));
|
|
2077
|
+
return;
|
|
2078
|
+
}
|
|
2079
|
+
out.log(`platform: ${cfg.platformUrl}`);
|
|
2080
|
+
out.log(`principal: ${namedPrincipal(identity)}`);
|
|
2081
|
+
out.log(`principal id: ${identity.principalId}`);
|
|
2082
|
+
out.log(`kind: ${identity.principalKind}`);
|
|
2083
|
+
if (identity.principalKind === "agent")
|
|
2084
|
+
out.log(
|
|
2085
|
+
`manager: ${identity.manager ? namedManager(identity.manager) : "(unknown)"}`
|
|
2086
|
+
);
|
|
2087
|
+
out.log(`owner: ${accountableOwner(identity)}`);
|
|
2088
|
+
out.log(`owner id: ${identity.developerId}`);
|
|
2089
|
+
out.log(`email: ${identity.email ?? "(none)"}`);
|
|
2090
|
+
out.log(`credential: ${credentialLabel(identity)}`);
|
|
2091
|
+
if (identity.credential.id)
|
|
2092
|
+
out.log(`credential id: ${identity.credential.id}`);
|
|
2093
|
+
out.log(`admin: ${identity.admin ? "yes" : "no"}`);
|
|
2094
|
+
if (identity.scopes.length) out.log(`scopes: ${identity.scopes.join(", ")}`);
|
|
2095
|
+
if (!identity.admin) {
|
|
2096
|
+
if (identity.scopes.includes("platform:runbook:write")) {
|
|
2097
|
+
out.log("\nThis exact scope can read and edit all platform runbook content.");
|
|
2098
|
+
out.log("The device token is not ambient admin and cannot change visibility.");
|
|
2099
|
+
} else {
|
|
2100
|
+
out.log("\nYou can read operator-visible platform runbooks but not edit them.");
|
|
2101
|
+
out.log("Use a platform-admin-approved runbook write request to edit.");
|
|
2102
|
+
}
|
|
2103
|
+
}
|
|
2104
|
+
}
|
|
2105
|
+
var text;
|
|
2106
|
+
var init_whoami_command = __esm({
|
|
2107
|
+
"src/whoami-command.ts"() {
|
|
2108
|
+
"use strict";
|
|
2109
|
+
init_cjs_shims();
|
|
2110
|
+
init_argv();
|
|
2111
|
+
init_operator_context();
|
|
2112
|
+
init_token();
|
|
2113
|
+
text = (value2) => typeof value2 === "string" && value2.trim() ? value2.trim() : null;
|
|
2114
|
+
}
|
|
2115
|
+
});
|
|
2116
|
+
|
|
2117
|
+
// src/auth-command.ts
|
|
2118
|
+
async function authCommand(parsed, deps = {}) {
|
|
2119
|
+
assertArgs(parsed, [
|
|
2120
|
+
"config",
|
|
2121
|
+
"context",
|
|
2122
|
+
"platform",
|
|
2123
|
+
"app",
|
|
2124
|
+
"email",
|
|
2125
|
+
"open",
|
|
2126
|
+
"wait",
|
|
2127
|
+
"json"
|
|
2128
|
+
], 2);
|
|
2129
|
+
const action2 = parsed.positionals[1] ?? "login";
|
|
2130
|
+
if (action2 !== "login") {
|
|
2131
|
+
throw new Error(`unknown auth action "${action2}". Try "odla-ai auth login --app <id> --email <odla-account>".`);
|
|
2132
|
+
}
|
|
2133
|
+
const context = await resolveOperatorContext(parsed, {
|
|
2134
|
+
allowMissingConfig: true,
|
|
2135
|
+
requireApp: true
|
|
2136
|
+
});
|
|
2137
|
+
const { cfg } = context;
|
|
2138
|
+
const out = deps.stdout ?? console;
|
|
2139
|
+
const doFetch = deps.fetch ?? fetch;
|
|
2140
|
+
const email = stringOpt(parsed.options.email) ?? import_node_process10.default.env.ODLA_USER_EMAIL?.trim();
|
|
2141
|
+
if (!email) {
|
|
2142
|
+
throw new Error(
|
|
2143
|
+
"auth login requires --email <odla-account> or ODLA_USER_EMAIL; confirm the signed-in odla email instead of using git or GitHub identity"
|
|
2144
|
+
);
|
|
2145
|
+
}
|
|
2146
|
+
const token = await getDeveloperToken(
|
|
2147
|
+
cfg,
|
|
2148
|
+
{
|
|
2149
|
+
configPath: cfg.configPath,
|
|
2150
|
+
email,
|
|
2151
|
+
open: parsed.options.open === false ? false : parsed.options.open === true ? true : void 0,
|
|
2152
|
+
wait: numberOpt(parsed.options.wait, "--wait"),
|
|
2153
|
+
openApprovalUrl: deps.openUrl
|
|
2154
|
+
},
|
|
2155
|
+
doFetch,
|
|
2156
|
+
out,
|
|
2157
|
+
{ freshLogin: true }
|
|
2158
|
+
);
|
|
2159
|
+
const identity = await fetchIdentity(cfg.platformUrl, token, doFetch);
|
|
2160
|
+
if (parsed.options.json === true) {
|
|
2161
|
+
out.log(JSON.stringify({
|
|
2162
|
+
principalId: identity.principalId,
|
|
2163
|
+
displayName: identity.displayName,
|
|
2164
|
+
handle: identity.handle,
|
|
2165
|
+
email: identity.email,
|
|
2166
|
+
appId: cfg.app.id
|
|
2167
|
+
}, null, 2));
|
|
2168
|
+
return;
|
|
2169
|
+
}
|
|
2170
|
+
const handle = identity.handle ? ` (@${identity.handle})` : "";
|
|
2171
|
+
out.log(`Authorized ${identity.displayName}${handle} for ${cfg.app.id}.`);
|
|
2172
|
+
out.log(`odla account: ${identity.email ?? "not returned"}`);
|
|
2173
|
+
}
|
|
2174
|
+
var import_node_process10;
|
|
2175
|
+
var init_auth_command = __esm({
|
|
2176
|
+
"src/auth-command.ts"() {
|
|
2177
|
+
"use strict";
|
|
2178
|
+
init_cjs_shims();
|
|
2179
|
+
import_node_process10 = __toESM(require("process"), 1);
|
|
2180
|
+
init_argv();
|
|
2181
|
+
init_operator_context();
|
|
2182
|
+
init_token();
|
|
2183
|
+
init_whoami_command();
|
|
2184
|
+
}
|
|
2185
|
+
});
|
|
2186
|
+
|
|
1961
2187
|
// src/tenant.ts
|
|
1962
2188
|
function resolveEnv(cfg, requested) {
|
|
1963
2189
|
const env = requested ?? (cfg.envs.includes("dev") ? "dev" : cfg.envs[0]);
|
|
@@ -4917,6 +5143,14 @@ and atomically claim a refined Ready task. Record decisions when you make them
|
|
|
4917
5143
|
and file bugs when you notice them. The conventions and the full command set are
|
|
4918
5144
|
in \`.agents/skills/odla/references/pm.md\`.
|
|
4919
5145
|
|
|
5146
|
+
Use the human's signed-in odla account email for device authorization; never
|
|
5147
|
+
infer it from git config, commit metadata, or GitHub. If authorization is not
|
|
5148
|
+
already active, run
|
|
5149
|
+
\`npx @odla-ai/cli auth login --app <appId> --email <odla-account>\` and open
|
|
5150
|
+
the exact Studio URL it prints. Never file an odla project or product defect in
|
|
5151
|
+
GitHub Issues: run \`npx @odla-ai/cli bug report --app <appId> ...\` so the bug
|
|
5152
|
+
lands in odla PM with the rest of the project's goals, tasks, and decisions.
|
|
5153
|
+
|
|
4920
5154
|
The setup runbooks and their references are installed in this repository, pinned
|
|
4921
5155
|
to this CLI version. Use them as your setup context.
|
|
4922
5156
|
|
|
@@ -9103,7 +9337,7 @@ function developerTokenStatus(context, parsed, now = Date.now()) {
|
|
|
9103
9337
|
const cacheStatus = !cached?.token ? "missing" : cached.platform !== context.platform.value ? "other-platform" : (cached.expiresAt ?? 0) <= now + 6e4 ? "expired" : "valid";
|
|
9104
9338
|
const source = clean3(
|
|
9105
9339
|
stringOpt(parsed.options.token)
|
|
9106
|
-
) ? "flag" : clean3(
|
|
9340
|
+
) ? "flag" : clean3(import_node_process11.default.env.ODLA_DEV_TOKEN) ? "environment" : cacheStatus === "valid" ? "cache" : "missing";
|
|
9107
9341
|
return {
|
|
9108
9342
|
source,
|
|
9109
9343
|
cacheFile: context.cfg.local.tokenFile,
|
|
@@ -9114,12 +9348,12 @@ function clean3(value2) {
|
|
|
9114
9348
|
const normalized = value2?.trim();
|
|
9115
9349
|
return normalized || void 0;
|
|
9116
9350
|
}
|
|
9117
|
-
var
|
|
9351
|
+
var import_node_process11;
|
|
9118
9352
|
var init_operator_credentials = __esm({
|
|
9119
9353
|
"src/operator-credentials.ts"() {
|
|
9120
9354
|
"use strict";
|
|
9121
9355
|
init_cjs_shims();
|
|
9122
|
-
|
|
9356
|
+
import_node_process11 = __toESM(require("process"), 1);
|
|
9123
9357
|
init_argv();
|
|
9124
9358
|
init_local();
|
|
9125
9359
|
}
|
|
@@ -9285,6 +9519,7 @@ Start here:
|
|
|
9285
9519
|
step it made wrong.
|
|
9286
9520
|
|
|
9287
9521
|
Usage:
|
|
9522
|
+
odla-ai auth login --app <id> --email <odla-account> [--platform https://odla.ai] [--no-open] [--wait <seconds>] [--json]
|
|
9288
9523
|
odla-ai setup [--dir <project>] [--agent <name>] [--global] [--force]
|
|
9289
9524
|
odla-ai init --app-id <id> --name <name> [--services db,ai,o11y,calendar] [--env dev --env prod] [--ai-provider <byok-provider>]
|
|
9290
9525
|
odla-ai doctor [--config odla.config.mjs]
|
|
@@ -9321,6 +9556,7 @@ Usage:
|
|
|
9321
9556
|
odla-ai pm task release <id> --expected-revision <n> [--mutation-id <id>] [--json]
|
|
9322
9557
|
odla-ai pm decision add --app <id> --title <t> --body <text> [--status <s>] [--mutation-id <id>] [--json]
|
|
9323
9558
|
odla-ai pm bug add --app <id> --title <t> (--description <text>|--body <text>) [--status <s>] [--severity <s>] [--goal <id>] [--assignee <id>] [--decision <id>] [--mutation-id <id>] [--json]
|
|
9559
|
+
odla-ai bug report --app <id> --title <t> (--description <text>|--body <text>) [--severity <s>] [--json]
|
|
9324
9560
|
odla-ai pm <goal|task|decision|bug> get <id> [--json]
|
|
9325
9561
|
odla-ai pm <goal|task|decision|bug> ref <id> [--json]
|
|
9326
9562
|
odla-ai pm goal set <id> [--title <t>|--status <s>|--proof <text>|--no-proof|--target <pct>|--no-target] [--mutation-id <id>] [--json]
|
|
@@ -9402,6 +9638,9 @@ function printHelp(output = console) {
|
|
|
9402
9638
|
output.log(`odla-ai
|
|
9403
9639
|
${USAGE_SECTION}
|
|
9404
9640
|
Commands:
|
|
9641
|
+
auth Start a fresh, exact-project agent authorization in the browser.
|
|
9642
|
+
The email is the signed-in odla account, never git or GitHub
|
|
9643
|
+
identity. The approval screen confirms the agent name first.
|
|
9405
9644
|
agent Inspect durable agent wakeups and explicitly requeue a
|
|
9406
9645
|
dead-lettered job; JSON output is stable for remote operators.
|
|
9407
9646
|
runbook odla's operational procedures, stored in the database and read at
|
|
@@ -9469,6 +9708,8 @@ Commands:
|
|
|
9469
9708
|
"app". Entities: goal (alias conformance), task (alias kanban),
|
|
9470
9709
|
decision, bug. Status changes and comments post to each item's
|
|
9471
9710
|
@odla-ai/chat discussion thread.
|
|
9711
|
+
bug Intent-first alias for PM bugs. "bug report" writes to
|
|
9712
|
+
odla PM; odla product defects do not belong in GitHub Issues.
|
|
9472
9713
|
discuss Group discussions (via @odla-ai/chat) for the apps you co-own:
|
|
9473
9714
|
one group per project, topics with replies, @-mentions of people,
|
|
9474
9715
|
agents, PM items, and projects. Built for unattended use \u2014 post a
|
|
@@ -9526,8 +9767,12 @@ Safety:
|
|
|
9526
9767
|
wait for approval, and re-run to collect.
|
|
9527
9768
|
A fresh device handshake requires --email <odla-account> or ODLA_USER_EMAIL.
|
|
9528
9769
|
The email is a non-secret identity hint: never provide a password or session
|
|
9529
|
-
token.
|
|
9770
|
+
token. It is the email shown by the signed-in odla account \u2014 never infer it
|
|
9771
|
+
from git config, a commit author, or GitHub. The matching account must already exist, be signed in, explicitly
|
|
9530
9772
|
review the exact code, and finish any current request before claiming another.
|
|
9773
|
+
Use "auth login --app <id> --email <odla-account>" when an outside agent needs
|
|
9774
|
+
a deliberate fresh request; it ignores cached credentials and opens the same
|
|
9775
|
+
focused authorization sequence used by every first-time command.
|
|
9531
9776
|
If provision reports that the current agent principal has no live app.manage
|
|
9532
9777
|
grant, run it once with --request-grant. That flag ignores ODLA_DEV_TOKEN and
|
|
9533
9778
|
the local cache, prints and opens a fresh exact-project owner-review URL, then
|
|
@@ -10064,7 +10309,8 @@ async function buildContext(parsed, deps) {
|
|
|
10064
10309
|
configPath: cfg.configPath,
|
|
10065
10310
|
token: stringOpt(parsed.options.token),
|
|
10066
10311
|
email: stringOpt(parsed.options.email),
|
|
10067
|
-
open: false
|
|
10312
|
+
open: parsed.options.open === false ? false : parsed.options.open === true ? true : void 0,
|
|
10313
|
+
openApprovalUrl: deps.openUrl
|
|
10068
10314
|
},
|
|
10069
10315
|
doFetch,
|
|
10070
10316
|
out
|
|
@@ -10144,7 +10390,8 @@ var init_discuss_command = __esm({
|
|
|
10144
10390
|
"jsonl",
|
|
10145
10391
|
"mutation-id",
|
|
10146
10392
|
"platform",
|
|
10147
|
-
"context"
|
|
10393
|
+
"context",
|
|
10394
|
+
"open"
|
|
10148
10395
|
];
|
|
10149
10396
|
}
|
|
10150
10397
|
});
|
|
@@ -10731,7 +10978,13 @@ async function buildContext2(parsed, deps) {
|
|
|
10731
10978
|
const out = deps.stdout ?? console;
|
|
10732
10979
|
const token = await getDeveloperToken(
|
|
10733
10980
|
cfg,
|
|
10734
|
-
{
|
|
10981
|
+
{
|
|
10982
|
+
configPath: cfg.configPath,
|
|
10983
|
+
token: stringOpt(parsed.options.token),
|
|
10984
|
+
email: stringOpt(parsed.options.email),
|
|
10985
|
+
open: parsed.options.open === false ? false : parsed.options.open === true ? true : void 0,
|
|
10986
|
+
openApprovalUrl: deps.openUrl
|
|
10987
|
+
},
|
|
10735
10988
|
doFetch,
|
|
10736
10989
|
out
|
|
10737
10990
|
);
|
|
@@ -10829,7 +11082,7 @@ var init_pm_command = __esm({
|
|
|
10829
11082
|
decision: "decision",
|
|
10830
11083
|
bug: "bug"
|
|
10831
11084
|
};
|
|
10832
|
-
COMMON_OPTIONS = ["config", "token", "email", "json", "platform", "context"];
|
|
11085
|
+
COMMON_OPTIONS = ["config", "token", "email", "json", "platform", "context", "open"];
|
|
10833
11086
|
ACTION_OPTIONS = {
|
|
10834
11087
|
list: ["app", "q", "limit", "offset"],
|
|
10835
11088
|
add: ["app", "title", "mutation-id"],
|
|
@@ -11285,7 +11538,8 @@ async function o11yCommand(parsed, deps = {}) {
|
|
|
11285
11538
|
"json",
|
|
11286
11539
|
"app",
|
|
11287
11540
|
"env",
|
|
11288
|
-
"minutes"
|
|
11541
|
+
"minutes",
|
|
11542
|
+
"open"
|
|
11289
11543
|
],
|
|
11290
11544
|
2
|
|
11291
11545
|
);
|
|
@@ -11312,7 +11566,8 @@ async function o11yCommand(parsed, deps = {}) {
|
|
|
11312
11566
|
configPath: cfg.configPath,
|
|
11313
11567
|
token: stringOpt(parsed.options.token),
|
|
11314
11568
|
email: stringOpt(parsed.options.email),
|
|
11315
|
-
open: false
|
|
11569
|
+
open: parsed.options.open === false ? false : parsed.options.open === true ? true : void 0,
|
|
11570
|
+
openApprovalUrl: deps.openUrl
|
|
11316
11571
|
},
|
|
11317
11572
|
doFetch,
|
|
11318
11573
|
out
|
|
@@ -11766,7 +12021,7 @@ ${env}: credentials are already saved; retry "odla-ai secrets push --env ${env}$
|
|
|
11766
12021
|
await provisionIntegrationSeeds(doFetch, cfg.dbEndpoint, tenantId, dbKey, database.integrations, env, out);
|
|
11767
12022
|
}
|
|
11768
12023
|
if (cfg.services.includes("ai") && cfg.ai?.provider && cfg.ai.keyEnv) {
|
|
11769
|
-
const key =
|
|
12024
|
+
const key = import_node_process12.default.env[cfg.ai.keyEnv];
|
|
11770
12025
|
if (key) {
|
|
11771
12026
|
const secretName = cfg.ai.secretName ?? defaultSecretName(cfg.ai.provider);
|
|
11772
12027
|
await (0, import_ai4.putSecret)({ endpoint: cfg.dbEndpoint, token, fetch: doFetch }, tenantId, secretName, key);
|
|
@@ -11805,14 +12060,14 @@ ${env}: credentials are already saved; retry "odla-ai secrets push --env ${env}$
|
|
|
11805
12060
|
}
|
|
11806
12061
|
}
|
|
11807
12062
|
}
|
|
11808
|
-
var import_apps12, import_ai4,
|
|
12063
|
+
var import_apps12, import_ai4, import_node_process12;
|
|
11809
12064
|
var init_provision = __esm({
|
|
11810
12065
|
"src/provision.ts"() {
|
|
11811
12066
|
"use strict";
|
|
11812
12067
|
init_cjs_shims();
|
|
11813
12068
|
import_apps12 = require("@odla-ai/apps");
|
|
11814
12069
|
import_ai4 = require("@odla-ai/ai");
|
|
11815
|
-
|
|
12070
|
+
import_node_process12 = __toESM(require("process"), 1);
|
|
11816
12071
|
init_config();
|
|
11817
12072
|
init_calendar();
|
|
11818
12073
|
init_calendar_errors();
|
|
@@ -11923,7 +12178,9 @@ var init_surface = __esm({
|
|
|
11923
12178
|
promote: {},
|
|
11924
12179
|
owners: { list: {}, add: {}, remove: {} }
|
|
11925
12180
|
},
|
|
12181
|
+
auth: { login: {} },
|
|
11926
12182
|
brand: { design: { unpack: {} } },
|
|
12183
|
+
bug: { create: {}, list: {}, report: {} },
|
|
11927
12184
|
calendar: { status: {}, calendars: {}, connect: {}, disconnect: {} },
|
|
11928
12185
|
capabilities: {},
|
|
11929
12186
|
code: { connect: {} },
|
|
@@ -11995,7 +12252,7 @@ var init_surface = __esm({
|
|
|
11995
12252
|
|
|
11996
12253
|
// src/record.ts
|
|
11997
12254
|
function recordInvocation(parsed) {
|
|
11998
|
-
const file =
|
|
12255
|
+
const file = import_node_process13.default.env.ODLA_CLI_RECORD;
|
|
11999
12256
|
if (!file) return;
|
|
12000
12257
|
try {
|
|
12001
12258
|
const entry = {
|
|
@@ -12008,13 +12265,13 @@ function recordInvocation(parsed) {
|
|
|
12008
12265
|
} catch {
|
|
12009
12266
|
}
|
|
12010
12267
|
}
|
|
12011
|
-
var import_node_fs17,
|
|
12268
|
+
var import_node_fs17, import_node_process13;
|
|
12012
12269
|
var init_record = __esm({
|
|
12013
12270
|
"src/record.ts"() {
|
|
12014
12271
|
"use strict";
|
|
12015
12272
|
init_cjs_shims();
|
|
12016
12273
|
import_node_fs17 = require("fs");
|
|
12017
|
-
|
|
12274
|
+
import_node_process13 = __toESM(require("process"), 1);
|
|
12018
12275
|
init_surface();
|
|
12019
12276
|
}
|
|
12020
12277
|
});
|
|
@@ -12673,7 +12930,7 @@ var init_runbook_search_command = __esm({
|
|
|
12673
12930
|
});
|
|
12674
12931
|
|
|
12675
12932
|
// src/runbook-editor.ts
|
|
12676
|
-
function resolveEditor(env =
|
|
12933
|
+
function resolveEditor(env = import_node_process14.default.env) {
|
|
12677
12934
|
for (const name of EDITOR_ENV) {
|
|
12678
12935
|
const value2 = env[name];
|
|
12679
12936
|
if (value2 && value2.trim()) return value2.trim();
|
|
@@ -12687,8 +12944,8 @@ function defaultRun(command, path) {
|
|
|
12687
12944
|
return result.status ?? 0;
|
|
12688
12945
|
}
|
|
12689
12946
|
function editText(initial, slug, deps = {}) {
|
|
12690
|
-
const env = deps.env ??
|
|
12691
|
-
const interactive = deps.interactive ?? (() => Boolean(
|
|
12947
|
+
const env = deps.env ?? import_node_process14.default.env;
|
|
12948
|
+
const interactive = deps.interactive ?? (() => Boolean(import_node_process14.default.stdin.isTTY));
|
|
12692
12949
|
const editor = resolveEditor(env);
|
|
12693
12950
|
if (!editor)
|
|
12694
12951
|
throw new Error(
|
|
@@ -12708,7 +12965,7 @@ function editText(initial, slug, deps = {}) {
|
|
|
12708
12965
|
(0, import_node_fs21.rmSync)(dir, { recursive: true, force: true });
|
|
12709
12966
|
}
|
|
12710
12967
|
}
|
|
12711
|
-
var import_node_child_process8, import_node_fs21, import_node_os5, import_node_path19,
|
|
12968
|
+
var import_node_child_process8, import_node_fs21, import_node_os5, import_node_path19, import_node_process14, EDITOR_ENV, defaultRunOrInjected;
|
|
12712
12969
|
var init_runbook_editor = __esm({
|
|
12713
12970
|
"src/runbook-editor.ts"() {
|
|
12714
12971
|
"use strict";
|
|
@@ -12717,7 +12974,7 @@ var init_runbook_editor = __esm({
|
|
|
12717
12974
|
import_node_fs21 = require("fs");
|
|
12718
12975
|
import_node_os5 = require("os");
|
|
12719
12976
|
import_node_path19 = require("path");
|
|
12720
|
-
|
|
12977
|
+
import_node_process14 = __toESM(require("process"), 1);
|
|
12721
12978
|
EDITOR_ENV = ["ODLA_EDITOR", "VISUAL", "EDITOR"];
|
|
12722
12979
|
defaultRunOrInjected = (deps) => deps.run ?? defaultRun;
|
|
12723
12980
|
}
|
|
@@ -12745,152 +13002,6 @@ var init_runbook_edit_flow = __esm({
|
|
|
12745
13002
|
}
|
|
12746
13003
|
});
|
|
12747
13004
|
|
|
12748
|
-
// src/whoami-command.ts
|
|
12749
|
-
function principalKind(value2, machine) {
|
|
12750
|
-
return value2 === "human" || value2 === "agent" || value2 === "service" ? value2 : machine ? "service" : "human";
|
|
12751
|
-
}
|
|
12752
|
-
function credentialKind(value2, machine, scopes) {
|
|
12753
|
-
if (value2 === "machine" || value2 === "device" || value2 === "clerk") return value2;
|
|
12754
|
-
if (machine) return "machine";
|
|
12755
|
-
if (scopes.length) return "device";
|
|
12756
|
-
return "unknown";
|
|
12757
|
-
}
|
|
12758
|
-
function managerOf(value2) {
|
|
12759
|
-
if (!value2 || typeof value2 !== "object") return null;
|
|
12760
|
-
const row = value2;
|
|
12761
|
-
const principalId = text(row.principalId);
|
|
12762
|
-
if (!principalId) return null;
|
|
12763
|
-
return {
|
|
12764
|
-
principalId,
|
|
12765
|
-
displayName: text(row.displayName) ?? "Unnamed member",
|
|
12766
|
-
handle: text(row.handle) ?? ""
|
|
12767
|
-
};
|
|
12768
|
-
}
|
|
12769
|
-
function unnamedPrincipal(kind) {
|
|
12770
|
-
if (kind === "agent") return "Unnamed agent";
|
|
12771
|
-
if (kind === "service") return "Unnamed service";
|
|
12772
|
-
return "Unnamed member";
|
|
12773
|
-
}
|
|
12774
|
-
async function fetchIdentity(platformUrl, token, doFetch) {
|
|
12775
|
-
const res = await doFetch(`${platformUrl.replace(/\/$/, "")}/registry/me`, {
|
|
12776
|
-
headers: { authorization: `Bearer ${token}` }
|
|
12777
|
-
});
|
|
12778
|
-
if (!res.ok) throw new Error(`could not resolve identity (HTTP ${res.status})`);
|
|
12779
|
-
const body = await res.json();
|
|
12780
|
-
const developerId = text(body.developerId) ?? "";
|
|
12781
|
-
const machine = body.machine === true;
|
|
12782
|
-
const scopes = Array.isArray(body.scopes) ? body.scopes.map(String) : [];
|
|
12783
|
-
const principalId = text(body.principalId) ?? developerId;
|
|
12784
|
-
const email = text(body.email);
|
|
12785
|
-
const kind = principalKind(body.principalKind, machine);
|
|
12786
|
-
const displayName = text(body.displayName) ?? email ?? unnamedPrincipal(kind);
|
|
12787
|
-
const handle = text(body.handle) ?? "";
|
|
12788
|
-
const credential2 = body.credential && typeof body.credential === "object" ? body.credential : {};
|
|
12789
|
-
return {
|
|
12790
|
-
developerId,
|
|
12791
|
-
principalId,
|
|
12792
|
-
principalKind: kind,
|
|
12793
|
-
displayName,
|
|
12794
|
-
handle,
|
|
12795
|
-
manager: managerOf(body.manager),
|
|
12796
|
-
credential: {
|
|
12797
|
-
id: text(credential2.id),
|
|
12798
|
-
kind: credentialKind(credential2.kind, machine, scopes)
|
|
12799
|
-
},
|
|
12800
|
-
email,
|
|
12801
|
-
admin: body.admin === true,
|
|
12802
|
-
machine,
|
|
12803
|
-
scopes
|
|
12804
|
-
};
|
|
12805
|
-
}
|
|
12806
|
-
function credentialLabel(identity) {
|
|
12807
|
-
if (identity.credential.kind === "machine") return "machine (platform admin secret)";
|
|
12808
|
-
if (identity.credential.kind === "device")
|
|
12809
|
-
return identity.scopes.length ? "device (scoped)" : "device";
|
|
12810
|
-
if (identity.credential.kind === "clerk") return "clerk";
|
|
12811
|
-
return "unknown (legacy server)";
|
|
12812
|
-
}
|
|
12813
|
-
function namedPrincipal(identity) {
|
|
12814
|
-
return identity.handle && identity.handle !== identity.displayName ? `${identity.displayName} (@${identity.handle})` : identity.displayName;
|
|
12815
|
-
}
|
|
12816
|
-
function namedManager(manager) {
|
|
12817
|
-
return manager.handle && manager.handle !== manager.displayName ? `${manager.displayName} (@${manager.handle})` : manager.displayName;
|
|
12818
|
-
}
|
|
12819
|
-
function accountableOwner(identity) {
|
|
12820
|
-
if (identity.principalKind === "agent" && identity.manager) {
|
|
12821
|
-
return namedManager(identity.manager);
|
|
12822
|
-
}
|
|
12823
|
-
if (identity.principalKind === "human" && identity.principalId === identity.developerId) {
|
|
12824
|
-
return namedPrincipal(identity);
|
|
12825
|
-
}
|
|
12826
|
-
return identity.email ?? "Unnamed member";
|
|
12827
|
-
}
|
|
12828
|
-
async function whoamiCommand(parsed, deps = {}) {
|
|
12829
|
-
assertArgs(
|
|
12830
|
-
parsed,
|
|
12831
|
-
["config", "context", "platform", "token", "email", "json"],
|
|
12832
|
-
1
|
|
12833
|
-
);
|
|
12834
|
-
const out = deps.stdout ?? console;
|
|
12835
|
-
const doFetch = deps.fetch ?? fetch;
|
|
12836
|
-
const { cfg } = await resolveOperatorContext(parsed, {
|
|
12837
|
-
allowMissingConfig: true
|
|
12838
|
-
});
|
|
12839
|
-
const token = await getDeveloperToken(
|
|
12840
|
-
cfg,
|
|
12841
|
-
{
|
|
12842
|
-
configPath: cfg.configPath,
|
|
12843
|
-
token: stringOpt(parsed.options.token),
|
|
12844
|
-
email: stringOpt(parsed.options.email),
|
|
12845
|
-
// As above: never suppress the browser for an ordinary sign-in.
|
|
12846
|
-
open: void 0
|
|
12847
|
-
},
|
|
12848
|
-
doFetch,
|
|
12849
|
-
out
|
|
12850
|
-
);
|
|
12851
|
-
const identity = await fetchIdentity(cfg.platformUrl, token, doFetch);
|
|
12852
|
-
if (parsed.options.json === true) {
|
|
12853
|
-
out.log(JSON.stringify(identity, null, 2));
|
|
12854
|
-
return;
|
|
12855
|
-
}
|
|
12856
|
-
out.log(`platform: ${cfg.platformUrl}`);
|
|
12857
|
-
out.log(`principal: ${namedPrincipal(identity)}`);
|
|
12858
|
-
out.log(`principal id: ${identity.principalId}`);
|
|
12859
|
-
out.log(`kind: ${identity.principalKind}`);
|
|
12860
|
-
if (identity.principalKind === "agent")
|
|
12861
|
-
out.log(
|
|
12862
|
-
`manager: ${identity.manager ? namedManager(identity.manager) : "(unknown)"}`
|
|
12863
|
-
);
|
|
12864
|
-
out.log(`owner: ${accountableOwner(identity)}`);
|
|
12865
|
-
out.log(`owner id: ${identity.developerId}`);
|
|
12866
|
-
out.log(`email: ${identity.email ?? "(none)"}`);
|
|
12867
|
-
out.log(`credential: ${credentialLabel(identity)}`);
|
|
12868
|
-
if (identity.credential.id)
|
|
12869
|
-
out.log(`credential id: ${identity.credential.id}`);
|
|
12870
|
-
out.log(`admin: ${identity.admin ? "yes" : "no"}`);
|
|
12871
|
-
if (identity.scopes.length) out.log(`scopes: ${identity.scopes.join(", ")}`);
|
|
12872
|
-
if (!identity.admin) {
|
|
12873
|
-
if (identity.scopes.includes("platform:runbook:write")) {
|
|
12874
|
-
out.log("\nThis exact scope can read and edit all platform runbook content.");
|
|
12875
|
-
out.log("The device token is not ambient admin and cannot change visibility.");
|
|
12876
|
-
} else {
|
|
12877
|
-
out.log("\nYou can read operator-visible platform runbooks but not edit them.");
|
|
12878
|
-
out.log("Use a platform-admin-approved runbook write request to edit.");
|
|
12879
|
-
}
|
|
12880
|
-
}
|
|
12881
|
-
}
|
|
12882
|
-
var text;
|
|
12883
|
-
var init_whoami_command = __esm({
|
|
12884
|
-
"src/whoami-command.ts"() {
|
|
12885
|
-
"use strict";
|
|
12886
|
-
init_cjs_shims();
|
|
12887
|
-
init_argv();
|
|
12888
|
-
init_operator_context();
|
|
12889
|
-
init_token();
|
|
12890
|
-
text = (value2) => typeof value2 === "string" && value2.trim() ? value2.trim() : null;
|
|
12891
|
-
}
|
|
12892
|
-
});
|
|
12893
|
-
|
|
12894
13005
|
// src/runbook-command.ts
|
|
12895
13006
|
function requireSlug(slug, action2) {
|
|
12896
13007
|
if (!slug) throw new Error(`"runbook ${action2}" needs a slug, e.g. "odla-ai runbook ${action2} release"`);
|
|
@@ -13863,6 +13974,10 @@ async function runCli(argv2 = process.argv.slice(2), dependencies = {}) {
|
|
|
13863
13974
|
await whoamiCommand(parsed, runtime);
|
|
13864
13975
|
return;
|
|
13865
13976
|
}
|
|
13977
|
+
if (command === "auth") {
|
|
13978
|
+
await authCommand(parsed, runtime);
|
|
13979
|
+
return;
|
|
13980
|
+
}
|
|
13866
13981
|
if (command === "context") {
|
|
13867
13982
|
await contextCommand(parsed, runtime);
|
|
13868
13983
|
return;
|
|
@@ -13903,6 +14018,15 @@ async function runCli(argv2 = process.argv.slice(2), dependencies = {}) {
|
|
|
13903
14018
|
await pmCommand(parsed, runtime);
|
|
13904
14019
|
return;
|
|
13905
14020
|
}
|
|
14021
|
+
if (command === "bug") {
|
|
14022
|
+
const action2 = parsed.positionals[1] ?? "list";
|
|
14023
|
+
const canonical = action2 === "report" || action2 === "create" ? "add" : action2;
|
|
14024
|
+
await pmCommand({
|
|
14025
|
+
...parsed,
|
|
14026
|
+
positionals: ["pm", "bug", canonical, ...parsed.positionals.slice(2)]
|
|
14027
|
+
}, runtime);
|
|
14028
|
+
return;
|
|
14029
|
+
}
|
|
13906
14030
|
if (command === "discuss") {
|
|
13907
14031
|
await discussCommand(parsed, runtime);
|
|
13908
14032
|
return;
|
|
@@ -13989,6 +14113,7 @@ var init_cli = __esm({
|
|
|
13989
14113
|
"use strict";
|
|
13990
14114
|
init_cjs_shims();
|
|
13991
14115
|
init_admin_command();
|
|
14116
|
+
init_auth_command();
|
|
13992
14117
|
init_agent_command();
|
|
13993
14118
|
init_app_lifecycle();
|
|
13994
14119
|
init_brand_command();
|