shadok-ai 0.17.1 → 0.17.2
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 +10 -4
- package/dist/server.js +69 -3
- package/dist/server.js.map +1 -1
- package/dist/setup-token.d.ts +71 -0
- package/dist/setup-token.js +239 -0
- package/dist/setup-token.js.map +1 -0
- package/dist/telegram.d.ts +1 -1
- package/dist/telegram.js +39 -3
- package/dist/telegram.js.map +1 -1
- package/package.json +1 -1
- package/public/index.html +128 -0
package/README.md
CHANGED
|
@@ -476,10 +476,16 @@ in to Claude, which happens after boot; the banner stays up until then and the
|
|
|
476
476
|
migration **finishes on its own the moment you sign in** (no restart needed).
|
|
477
477
|
**One-time provisioning:** isolated-group agents authenticate with a
|
|
478
478
|
subscription token injected into their environment, not a shared credential
|
|
479
|
-
file.
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
(
|
|
479
|
+
file. Until it is set, group spawns are refused with a message naming this step,
|
|
480
|
+
and the cockpit shows a red "no subscription token" banner. Provide it from the
|
|
481
|
+
cockpit itself — **no shell needed**: as an admin, click **Provide token** on
|
|
482
|
+
that banner (or **Replace subscription token…** in the version menu to rotate
|
|
483
|
+
it), authorise on your Claude subscription in the browser, and paste the code
|
|
484
|
+
back; the server mints and stores it. From Telegram, `/mttoken` then
|
|
485
|
+
`/mtcode <code>` do the same. The token is captured and vaulted server-side and
|
|
486
|
+
is never shown. It is valid for about a year; re-run the card (or `/mttoken`)
|
|
487
|
+
before it expires. The shell path still works as a fallback: `claude setup-token`
|
|
488
|
+
and store the result as the vault secret `MT_CLAUDE_OAUTH_TOKEN`.
|
|
483
489
|
|
|
484
490
|
Telegram stays single-owner, and there are no
|
|
485
491
|
disk quotas yet. Design: `docs/superpowers/specs/2026-10-29-multi-tenant-scoped-isolation-design.md`.
|
package/dist/server.js
CHANGED
|
@@ -20,7 +20,8 @@ import { findTransientErrors, newTransientErrors, RETRY_DELAYS_MS } from "./retr
|
|
|
20
20
|
import { backgroundTasks, screenShowsWork, moveToOption } from "./detect.js";
|
|
21
21
|
import { PtyPilot } from "./session.js";
|
|
22
22
|
import { ensureClaudeHome, ensureProjectTrusted, getDefaultModel, setDefaultModel, parseClaudeVersion, seedPlan, settingsPlan, } from "./claude-home.js";
|
|
23
|
-
import { authStatus, cancelLogin, startLogin, submitLoginCode } from "./claude-auth.js";
|
|
23
|
+
import { authStatus, cancelLogin, invalidateAuthStatus, startLogin, submitLoginCode } from "./claude-auth.js";
|
|
24
|
+
import { startSetupToken, submitSetupTokenCode, cancelSetupToken } from "./setup-token.js";
|
|
24
25
|
import { readTokenExpiry } from "./claude-creds.js";
|
|
25
26
|
import { starCount } from "./stars.js";
|
|
26
27
|
import { announceFirstAgent, countMainChannels, ensureFirstAgent, firstAgentStatus, settleFirstAgent, } from "./first-agent.js";
|
|
@@ -290,7 +291,7 @@ async function startFirstAgent() {
|
|
|
290
291
|
}
|
|
291
292
|
function restartTelegram() {
|
|
292
293
|
tgBridge.stop();
|
|
293
|
-
tgBridge = startTelegram(boundPort, tgCookie());
|
|
294
|
+
tgBridge = startTelegram(boundPort, tgCookie(), DEPLOYMENT_MODE === "multi-tenant");
|
|
294
295
|
}
|
|
295
296
|
// ── Cron scheduler ───────────────────────────────────────────────────────────
|
|
296
297
|
// Scheduled per-channel prompts (monitoring/reporting). The server drives the
|
|
@@ -1641,7 +1642,19 @@ app.get("/auth", async (_req, res) => res.json({
|
|
|
1641
1642
|
...readTokenExpiry(),
|
|
1642
1643
|
...longLivedTokenInfo(loadVault(), DEPLOYMENT_MODE),
|
|
1643
1644
|
...customBackendFields(),
|
|
1645
|
+
...mtTokenFields(),
|
|
1644
1646
|
}));
|
|
1647
|
+
/**
|
|
1648
|
+
* Whether this is a multi-tenant instance and, if so, whether its group agents'
|
|
1649
|
+
* subscription token is still missing — the signal the cockpit uses to offer the
|
|
1650
|
+
* in-cockpit provisioning card (and the blocking banner). Mono-tenant → both
|
|
1651
|
+
* false; it needs no token.
|
|
1652
|
+
*/
|
|
1653
|
+
function mtTokenFields() {
|
|
1654
|
+
const multiTenant = DEPLOYMENT_MODE === "multi-tenant";
|
|
1655
|
+
const present = !!secretsFor([MT_CLAUDE_OAUTH_TOKEN_NAME], MAIN_GROUP_ID)[MT_CLAUDE_OAUTH_TOKEN_NAME];
|
|
1656
|
+
return { multiTenant, mtTokenMissing: multiTenant && !present };
|
|
1657
|
+
}
|
|
1645
1658
|
function customBackendFields() {
|
|
1646
1659
|
const b = instanceBackend();
|
|
1647
1660
|
return b.kind === "custom" ? { backend: "custom", backendModel: b.model } : {};
|
|
@@ -1679,6 +1692,59 @@ app.delete("/auth/login", (_req, res) => {
|
|
|
1679
1692
|
cancelLogin();
|
|
1680
1693
|
res.json({ ok: true });
|
|
1681
1694
|
});
|
|
1695
|
+
// --- Subscription-token provisioning (src/setup-token.ts) -----------------
|
|
1696
|
+
// A multi-tenant instance injects the long-lived subscription token
|
|
1697
|
+
// (MT_CLAUDE_OAUTH_TOKEN) into its privilege-dropped agents; these two routes
|
|
1698
|
+
// let an ADMIN mint/rotate it from the cockpit instead of a hand-run shell
|
|
1699
|
+
// `claude setup-token`. Browser-origin + admin, exactly like the guardrail
|
|
1700
|
+
// writes — and server-owned: the token is captured and stored server-side, and
|
|
1701
|
+
// the response never carries it. Mono-tenant needs no token, so the routes are
|
|
1702
|
+
// inert there (the UI hides them too).
|
|
1703
|
+
function mtTokenGate(req, res) {
|
|
1704
|
+
if (DEPLOYMENT_MODE !== "multi-tenant") {
|
|
1705
|
+
res.status(400).json({ error: "not applicable: this instance is single-tenant" });
|
|
1706
|
+
return false;
|
|
1707
|
+
}
|
|
1708
|
+
if (!requestFromBrowser(req)) {
|
|
1709
|
+
res.status(403).json({ error: "browser only" });
|
|
1710
|
+
return false;
|
|
1711
|
+
}
|
|
1712
|
+
if (currentAccount(req)?.role !== "admin") {
|
|
1713
|
+
res.status(403).json({ error: "admins only" });
|
|
1714
|
+
return false;
|
|
1715
|
+
}
|
|
1716
|
+
return true;
|
|
1717
|
+
}
|
|
1718
|
+
app.post("/mt-token/start", async (req, res) => {
|
|
1719
|
+
if (!mtTokenGate(req, res))
|
|
1720
|
+
return;
|
|
1721
|
+
const r = await startSetupToken();
|
|
1722
|
+
if ("error" in r)
|
|
1723
|
+
return res.status(502).json(r);
|
|
1724
|
+
res.json(r); // {url}
|
|
1725
|
+
});
|
|
1726
|
+
app.post("/mt-token/code", async (req, res) => {
|
|
1727
|
+
if (!mtTokenGate(req, res))
|
|
1728
|
+
return;
|
|
1729
|
+
const code = typeof req.body?.code === "string" ? req.body.code : "";
|
|
1730
|
+
// The token is captured and written to the vault HERE and never leaves the
|
|
1731
|
+
// server: `store` is the only thing that ever touches the value.
|
|
1732
|
+
const r = await submitSetupTokenCode(code, (token) => setSecret(MT_CLAUDE_OAUTH_TOKEN_NAME, token, MAIN_GROUP_ID, "operator:setup-token"));
|
|
1733
|
+
if (r.ok) {
|
|
1734
|
+
mtTokenWarned = false; // a future absence gets warned again
|
|
1735
|
+
invalidateAuthStatus(); // so /auth re-reports longLived immediately
|
|
1736
|
+
}
|
|
1737
|
+
res.status(r.ok ? 200 : 400).json(r); // {ok} — never the token
|
|
1738
|
+
});
|
|
1739
|
+
app.delete("/mt-token", (req, res) => {
|
|
1740
|
+
// Same gate as start/code: a cancel is still a write on a credential flow, so
|
|
1741
|
+
// a non-admin/non-browser caller must not be able to kill an admin's live
|
|
1742
|
+
// mint mid-rotation.
|
|
1743
|
+
if (!mtTokenGate(req, res))
|
|
1744
|
+
return;
|
|
1745
|
+
cancelSetupToken();
|
|
1746
|
+
res.json({ ok: true });
|
|
1747
|
+
});
|
|
1682
1748
|
// --- Model backend (src/backend.ts) ---------------------------------------
|
|
1683
1749
|
// The instance-wide source for every agent's backend variables: the backend
|
|
1684
1750
|
// chosen in the cockpit if any, else shadok's own environment. Read on every
|
|
@@ -7223,7 +7289,7 @@ server.listen(port, HOST, () => {
|
|
|
7223
7289
|
// the internal auth cookie so it gets through the GUI password gate. The
|
|
7224
7290
|
// handle + bound port let /telegram restart it hot when the config changes.
|
|
7225
7291
|
boundPort = port;
|
|
7226
|
-
tgBridge = startTelegram(port, tgCookie());
|
|
7292
|
+
tgBridge = startTelegram(port, tgCookie(), DEPLOYMENT_MODE === "multi-tenant");
|
|
7227
7293
|
if (GUI_PASSWORD)
|
|
7228
7294
|
console.log("gui: password protection ON");
|
|
7229
7295
|
// Cron scheduler: prime nextRun then tick every 30s (unref'd so it never
|