@2kw/ai 7.1.0-dev.3 → 7.1.0-dev.7

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 CHANGED
@@ -85,7 +85,8 @@ kubectl-style contexts switch between organizations and environments:
85
85
  ```bash
86
86
  2kw context list # List all contexts
87
87
  2kw context use acme-corp # Switch active context
88
- 2kw context create staging # Create a new context
88
+ 2kw context create staging --base-url https://api.staging.example # Create a context, sign in through the browser
89
+ 2kw context create ci --api-key sk_... # Or store an API key instead
89
90
  ```
90
91
 
91
92
  `2kw config set memory true` makes an API-key context send `X-Backbone-Memory: enabled` on `ai respond` and `agents run` calls, which opts those runs into agent memory; `AI_2KW_MEMORY=true|false` overrides it per shell. Runs from a browser-login (session) context are always eligible for agent memory regardless of this setting, so `memory false` only affects API-key contexts.
@@ -14,6 +14,8 @@ type SessionConfig = Extract<ResolvedConfig, {
14
14
  * or a code that already carries its own separator) is shown untouched.
15
15
  */
16
16
  export declare function formatUserCode(code: string): string;
17
+ /** Trailing slashes make `${authUrl}/api/auth/...` double up — drop them once, here. */
18
+ export declare function stripTrailingSlash(url: string): string;
17
19
  /** Injection points for {@link deviceFlowLogin}; all default to the real thing. */
18
20
  export interface DeviceLoginDeps {
19
21
  requestCode?: typeof requestDeviceCode;
@@ -33,6 +35,9 @@ export interface DeviceLoginTarget {
33
35
  contextName: string;
34
36
  baseUrl: string;
35
37
  authUrl: string;
38
+ /** Make the context active once stored. Defaults to true; `context create`
39
+ * turns it off for every context but the first. */
40
+ activate?: boolean;
36
41
  }
37
42
  /**
38
43
  * The browser login, end to end: ask for a device code, wait for the user to
@@ -27,7 +27,7 @@ export function formatUserCode(code) {
27
27
  return code.length === 8 ? `${code.slice(0, 4)}-${code.slice(4)}` : code;
28
28
  }
29
29
  /** Trailing slashes make `${authUrl}/api/auth/...` double up — drop them once, here. */
30
- function stripTrailingSlash(url) {
30
+ export function stripTrailingSlash(url) {
31
31
  return url.replace(/\/+$/, "");
32
32
  }
33
33
  /**
@@ -65,7 +65,7 @@ async function tryOpenBrowser(url) {
65
65
  */
66
66
  export async function deviceFlowLogin(target, deps = {}) {
67
67
  const { requestCode = requestDeviceCode, poll = pollDeviceToken, runPolling = runDevicePolling, sessionInfo = getSessionInfo, listOrgs = listOrganizations, chooseOrg = selectOrganization, setActiveOrg = setActiveOrganization, revoke = revokeSession, openBrowser = tryOpenBrowser, saveContext = setContext, activateContext = setActiveContext, } = deps;
68
- const { contextName, baseUrl, authUrl } = target;
68
+ const { contextName, baseUrl, authUrl, activate = true } = target;
69
69
  const device = await requestCode(authUrl, CLI_CLIENT_ID);
70
70
  console.log("");
71
71
  console.log(`Open ${chalk.cyan(device.verification_uri_complete)}`);
@@ -121,10 +121,14 @@ export async function deviceFlowLogin(target, deps = {}) {
121
121
  }
122
122
  // Outside the guard on purpose: the session is on disk now, so a failure to
123
123
  // mark the context active still leaves a credential the user can reach.
124
- activateContext(contextName);
124
+ if (activate)
125
+ activateContext(contextName);
125
126
  console.log(chalk.green(`Logged in as ${email}`));
126
127
  console.log(chalk.dim(` — organization: ${org.name}`));
127
128
  console.log(chalk.dim(`Config stored at: ${store.path}`));
129
+ if (!activate) {
130
+ console.log(chalk.dim(`Switch to it with "2kw context use ${contextName}".`));
131
+ }
128
132
  }
129
133
  /**
130
134
  * The pre-device-flow login: take an API key from the flag or the terminal and
@@ -1,5 +1,6 @@
1
1
  import { Command } from "commander";
2
2
  import { updateContext } from "../lib/config.js";
3
+ import { type DeviceLoginDeps } from "./auth.js";
3
4
  import { listOrganizations, setActiveOrganization } from "../lib/auth-service.js";
4
5
  import { selectOrganization, type Asker } from "../lib/auth-session.js";
5
6
  /** Injection points for {@link performSetOrg}; all default to the real thing. */
@@ -24,5 +25,9 @@ export interface SetOrgDeps {
24
25
  * @param orgArg an organization id or slug; omitted means "prompt".
25
26
  */
26
27
  export declare function performSetOrg(orgArg: string | undefined, deps?: SetOrgDeps): Promise<void>;
27
- export declare function makeContextCommand(): Command;
28
+ /**
29
+ * @param deviceDeps forwarded to {@link deviceFlowLogin} by `context create` —
30
+ * a seam for tests, as on `makeAuthCommand`.
31
+ */
32
+ export declare function makeContextCommand(deviceDeps?: DeviceLoginDeps): Command;
28
33
  //# sourceMappingURL=context.d.ts.map
@@ -2,7 +2,8 @@ import { Command } from "commander";
2
2
  import chalk from "chalk";
3
3
  import Table from "cli-table3";
4
4
  import { createInterface } from "node:readline/promises";
5
- import { store, isJsonOutput, getAllContexts, getActiveContext, getActiveContextName, getContextCount, setContext, deleteContext, renameContext, setActiveContext, updateContext, validateContextName, DEFAULT_BASE_URL, } from "../lib/config.js";
5
+ import { store, isJsonOutput, getAllContexts, getActiveContext, getActiveContextName, getContextCount, setContext, deleteContext, renameContext, setActiveContext, updateContext, validateContextName, defaultAuthUrlFor, DEFAULT_BASE_URL, } from "../lib/config.js";
6
+ import { deviceFlowLogin, stripTrailingSlash } from "./auth.js";
6
7
  import { listOrganizations, setActiveOrganization, } from "../lib/auth-service.js";
7
8
  import { selectOrganization } from "../lib/auth-session.js";
8
9
  import { runAction } from "../lib/client.js";
@@ -76,7 +77,11 @@ export async function performSetOrg(orgArg, deps = {}) {
76
77
  }
77
78
  console.log(chalk.green(`Active organization: ${org.name} (${org.slug})`));
78
79
  }
79
- export function makeContextCommand() {
80
+ /**
81
+ * @param deviceDeps forwarded to {@link deviceFlowLogin} by `context create` —
82
+ * a seam for tests, as on `makeAuthCommand`.
83
+ */
84
+ export function makeContextCommand(deviceDeps = {}) {
80
85
  const cmd = new Command("context").description("Manage CLI contexts for multiple environments");
81
86
  cmd
82
87
  .command("list")
@@ -125,10 +130,12 @@ export function makeContextCommand() {
125
130
  });
126
131
  cmd
127
132
  .command("create")
128
- .description("Create a new context")
133
+ .description("Create a new context and sign in through the browser (or with an API key)")
129
134
  .argument("<name>", "Context name")
130
135
  .option("--base-url <url>", "Base URL")
131
- .option("--api-key <key>", "API key")
136
+ .option("--auth-url <url>", "Auth service URL (defaults to the one matching the base URL)")
137
+ .option("--api-key <key>", "Save an API key instead of signing in through the browser")
138
+ .option("--manual", "Prompt for an API key instead of signing in through the browser")
132
139
  .action(async (name, _opts, command) => {
133
140
  validateContextName(name);
134
141
  // Merged, not own — the root program declares --base-url and --api-key
@@ -140,6 +147,17 @@ export function makeContextCommand() {
140
147
  if (contexts[name]) {
141
148
  throw new Error(`Context "${name}" already exists. Delete it first or choose a different name.`);
142
149
  }
150
+ // The same browser login as `auth login`, written into the new context
151
+ // instead of the active one. Only the first context becomes active, as
152
+ // with an API key below: creating one is not switching to it.
153
+ if (!opts.apiKey && !opts.manual) {
154
+ const baseUrl = opts.baseUrl ?? DEFAULT_BASE_URL;
155
+ const authUrl = stripTrailingSlash(opts.authUrl ?? defaultAuthUrlFor(baseUrl));
156
+ const activate = getContextCount() === 0;
157
+ console.log(chalk.dim(`Auth service: ${authUrl} (override with --auth-url)`));
158
+ await runAction(command, () => deviceFlowLogin({ contextName: name, baseUrl, authUrl, activate }, deviceDeps));
159
+ return;
160
+ }
143
161
  let baseUrl = opts.baseUrl;
144
162
  let apiKey = opts.apiKey;
145
163
  if (!baseUrl || !apiKey) {
@@ -156,7 +156,8 @@ function relayDecideCommand(ref, responseId, calls, withApprovals) {
156
156
  /**
157
157
  * A connector tool's open approval, decoded from the standard `mcp_approval_request` item (#949). The item
158
158
  * is the MCP twin of `backbone:approval_request` (#805): it names the server and the tool, carries no call
159
- * id, policy class or status, and exists only while the decision is open, so its id doubles as the call id
159
+ * id or status (its policy class is an optional extension, #1084; "unknown" without it), and exists only
160
+ * while the decision is open, so its id doubles as the call id
160
161
  * and the tool is named as the approval row stores it, `mcp__<server_label>__<tool>`.
161
162
  */
162
163
  function connectorApproval(i) {
@@ -165,7 +166,7 @@ function connectorApproval(i) {
165
166
  callId: String(i.id),
166
167
  tool: `mcp__${String(i.server_label)}__${String(i.name)}`,
167
168
  arguments: parseArguments(i.arguments),
168
- policyClass: "unknown",
169
+ policyClass: typeof i.policy_class === "string" ? i.policy_class : "unknown",
169
170
  reason: null,
170
171
  preview: null,
171
172
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@2kw/ai",
3
- "version": "7.1.0-dev.3",
3
+ "version": "7.1.0-dev.7",
4
4
  "description": "CLI for 2kw.ai — schema-driven document extraction, an OpenAI-compatible EU LLM gateway, transcription, prompts, datasets, and experiments from your terminal or agentic workflows. Ships as 2kw, backbone, and bb.",
5
5
  "keywords": [
6
6
  "cli",