@tealbrick/kit 0.3.0-rc.3 → 0.3.0-rc.4
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/BOOTSTRAP.md +3 -3
- package/NATIVE.md +3 -3
- package/README.md +115 -2
- package/dist/cli.js +1 -1
- package/dist/native-claude.d.ts +49 -0
- package/dist/native-claude.js +287 -0
- package/dist/native-enroll.d.ts +22 -0
- package/dist/native-enroll.js +85 -0
- package/dist/native-serve-config.d.ts +160 -0
- package/dist/native-serve-config.js +190 -0
- package/dist/native-serve.d.ts +50 -0
- package/dist/native-serve.js +124 -0
- package/dist/native.js +63 -1
- package/dist/onboarding.d.ts +5 -0
- package/dist/onboarding.js +1 -1
- package/package.json +6 -6
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
export declare const hostPattern: RegExp;
|
|
3
|
+
declare const stdio: z.ZodObject<{
|
|
4
|
+
type: z.ZodOptional<z.ZodLiteral<"stdio">>;
|
|
5
|
+
command: z.ZodString;
|
|
6
|
+
args: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
7
|
+
env: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
|
|
8
|
+
}, z.core.$strict>;
|
|
9
|
+
export declare const tealbrickCallGrant: z.ZodObject<{
|
|
10
|
+
registrationId: z.ZodString;
|
|
11
|
+
operation: z.ZodString;
|
|
12
|
+
inputEquals: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodBoolean, z.ZodNull]>>>;
|
|
13
|
+
}, z.core.$strict>;
|
|
14
|
+
declare const effort: z.ZodEnum<{
|
|
15
|
+
low: "low";
|
|
16
|
+
high: "high";
|
|
17
|
+
medium: "medium";
|
|
18
|
+
xhigh: "xhigh";
|
|
19
|
+
max: "max";
|
|
20
|
+
}>;
|
|
21
|
+
/** Tools an agent may add to its default (Restricted) session access: research and expert delegation, never shell or writes. */
|
|
22
|
+
export declare const defaultAccessTools: readonly ["WebSearch", "WebFetch", "Agent", "TodoWrite"];
|
|
23
|
+
export declare const serveConfigSchema: z.ZodObject<{
|
|
24
|
+
version: z.ZodLiteral<1>;
|
|
25
|
+
port: z.ZodNumber;
|
|
26
|
+
publicUrl: z.ZodOptional<z.ZodString>;
|
|
27
|
+
allowedHosts: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
28
|
+
maxActiveTurns: z.ZodOptional<z.ZodNumber>;
|
|
29
|
+
agent: z.ZodOptional<z.ZodObject<{
|
|
30
|
+
id: z.ZodString;
|
|
31
|
+
name: z.ZodString;
|
|
32
|
+
ownerId: z.ZodString;
|
|
33
|
+
}, z.core.$strict>>;
|
|
34
|
+
claude: z.ZodOptional<z.ZodObject<{
|
|
35
|
+
sdkModule: z.ZodOptional<z.ZodString>;
|
|
36
|
+
cwd: z.ZodString;
|
|
37
|
+
instructionsFile: z.ZodOptional<z.ZodString>;
|
|
38
|
+
recipe: z.ZodOptional<z.ZodObject<{
|
|
39
|
+
path: z.ZodString;
|
|
40
|
+
agent: z.ZodString;
|
|
41
|
+
}, z.core.$strict>>;
|
|
42
|
+
model: z.ZodOptional<z.ZodString>;
|
|
43
|
+
effort: z.ZodOptional<z.ZodEnum<{
|
|
44
|
+
low: "low";
|
|
45
|
+
high: "high";
|
|
46
|
+
medium: "medium";
|
|
47
|
+
xhigh: "xhigh";
|
|
48
|
+
max: "max";
|
|
49
|
+
}>>;
|
|
50
|
+
maxTurns: z.ZodOptional<z.ZodNumber>;
|
|
51
|
+
maxBudgetUsd: z.ZodOptional<z.ZodNumber>;
|
|
52
|
+
timeoutSeconds: z.ZodOptional<z.ZodNumber>;
|
|
53
|
+
defaultTools: z.ZodOptional<z.ZodArray<z.ZodEnum<{
|
|
54
|
+
WebSearch: "WebSearch";
|
|
55
|
+
WebFetch: "WebFetch";
|
|
56
|
+
Agent: "Agent";
|
|
57
|
+
TodoWrite: "TodoWrite";
|
|
58
|
+
}>>>;
|
|
59
|
+
experts: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodObject<{
|
|
60
|
+
promptFile: z.ZodString;
|
|
61
|
+
description: z.ZodOptional<z.ZodString>;
|
|
62
|
+
tools: z.ZodOptional<z.ZodArray<z.ZodEnum<{
|
|
63
|
+
WebSearch: "WebSearch";
|
|
64
|
+
WebFetch: "WebFetch";
|
|
65
|
+
Read: "Read";
|
|
66
|
+
Glob: "Glob";
|
|
67
|
+
Grep: "Grep";
|
|
68
|
+
}>>>;
|
|
69
|
+
maxTurns: z.ZodOptional<z.ZodNumber>;
|
|
70
|
+
effort: z.ZodOptional<z.ZodEnum<{
|
|
71
|
+
low: "low";
|
|
72
|
+
high: "high";
|
|
73
|
+
medium: "medium";
|
|
74
|
+
xhigh: "xhigh";
|
|
75
|
+
max: "max";
|
|
76
|
+
}>>;
|
|
77
|
+
}, z.core.$strict>>>;
|
|
78
|
+
nativeTools: z.ZodOptional<z.ZodArray<z.ZodUnion<readonly [z.ZodString, z.ZodString]>>>;
|
|
79
|
+
readTools: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
80
|
+
mcpServers: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodObject<{
|
|
81
|
+
type: z.ZodOptional<z.ZodLiteral<"stdio">>;
|
|
82
|
+
command: z.ZodString;
|
|
83
|
+
args: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
84
|
+
env: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
|
|
85
|
+
}, z.core.$strict>>>;
|
|
86
|
+
tealbrickCalls: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
87
|
+
registrationId: z.ZodString;
|
|
88
|
+
operation: z.ZodString;
|
|
89
|
+
inputEquals: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodBoolean, z.ZodNull]>>>;
|
|
90
|
+
}, z.core.$strict>>>;
|
|
91
|
+
requireSubscription: z.ZodOptional<z.ZodBoolean>;
|
|
92
|
+
envPassthrough: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
93
|
+
}, z.core.$strict>>;
|
|
94
|
+
codex: z.ZodOptional<z.ZodObject<{
|
|
95
|
+
codexBin: z.ZodString;
|
|
96
|
+
cwd: z.ZodString;
|
|
97
|
+
}, z.core.$strict>>;
|
|
98
|
+
}, z.core.$strict>;
|
|
99
|
+
export type ServeConfig = z.infer<typeof serveConfigSchema>;
|
|
100
|
+
export interface ClaudeProfile {
|
|
101
|
+
cwd: string;
|
|
102
|
+
sdkModule?: string;
|
|
103
|
+
instructions?: string;
|
|
104
|
+
model?: string;
|
|
105
|
+
effort?: z.infer<typeof effort>;
|
|
106
|
+
maxTurns: number;
|
|
107
|
+
maxBudgetUsd?: number;
|
|
108
|
+
timeoutSeconds: number;
|
|
109
|
+
defaultTools: string[];
|
|
110
|
+
experts: Record<string, ClaudeExpert>;
|
|
111
|
+
nativeTools: string[];
|
|
112
|
+
readTools: string[];
|
|
113
|
+
mcpServers: Record<string, z.infer<typeof stdio>>;
|
|
114
|
+
tealbrickCalls: Array<z.infer<typeof tealbrickCallGrant>>;
|
|
115
|
+
requireSubscription: boolean;
|
|
116
|
+
envPassthrough: string[];
|
|
117
|
+
}
|
|
118
|
+
export interface ClaudeExpert {
|
|
119
|
+
description: string;
|
|
120
|
+
prompt: string;
|
|
121
|
+
tools: string[];
|
|
122
|
+
maxTurns: number;
|
|
123
|
+
effort?: z.infer<typeof effort>;
|
|
124
|
+
}
|
|
125
|
+
export declare const servePaths: (root: string) => {
|
|
126
|
+
config: string;
|
|
127
|
+
state: string;
|
|
128
|
+
sdk: string;
|
|
129
|
+
};
|
|
130
|
+
export declare function normalizeOrigin(value: string): string;
|
|
131
|
+
/** Host header values a TLS-terminating proxy for `publicUrl` may present. */
|
|
132
|
+
export declare function publicHosts(config: ServeConfig): string[];
|
|
133
|
+
export declare function readServeConfig(root: string): Promise<ServeConfig | undefined>;
|
|
134
|
+
export declare function writeServeConfig(root: string, config: ServeConfig): Promise<void>;
|
|
135
|
+
/** The kit-generated Claude attachment: tealbrick MCP server plus the child guard hook. */
|
|
136
|
+
declare const generatedClaude: z.ZodObject<{
|
|
137
|
+
settings: z.ZodObject<{
|
|
138
|
+
hooks: z.ZodObject<{
|
|
139
|
+
PreToolUse: z.ZodArray<z.ZodObject<{
|
|
140
|
+
matcher: z.ZodLiteral<"mcp__tealbrick__.*">;
|
|
141
|
+
hooks: z.ZodArray<z.ZodObject<{
|
|
142
|
+
type: z.ZodLiteral<"command">;
|
|
143
|
+
command: z.ZodString;
|
|
144
|
+
args: z.ZodArray<z.ZodString>;
|
|
145
|
+
}, z.core.$strict>>;
|
|
146
|
+
}, z.core.$strict>>;
|
|
147
|
+
}, z.core.$strict>;
|
|
148
|
+
}, z.core.$strict>;
|
|
149
|
+
mcpServers: z.ZodObject<{
|
|
150
|
+
tealbrick: z.ZodObject<{
|
|
151
|
+
command: z.ZodString;
|
|
152
|
+
args: z.ZodArray<z.ZodString>;
|
|
153
|
+
}, z.core.$strict>;
|
|
154
|
+
}, z.core.$strict>;
|
|
155
|
+
}, z.core.$strict>;
|
|
156
|
+
export type ClaudeAttachment = z.infer<typeof generatedClaude>;
|
|
157
|
+
export declare function readClaudeAttachment(root: string): Promise<ClaudeAttachment>;
|
|
158
|
+
/** Resolve the effective Claude profile. Explicit serve fields override the imported recipe. */
|
|
159
|
+
export declare function resolveClaudeProfile(root: string, config: ServeConfig): Promise<ClaudeProfile>;
|
|
160
|
+
export {};
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { lstat, readFile, realpath, writeFile, rename, mkdir } from 'node:fs/promises';
|
|
3
|
+
import { isAbsolute, join, resolve } from 'node:path';
|
|
4
|
+
import { randomUUID } from 'node:crypto';
|
|
5
|
+
/** Operator configuration for `tealbrick native serve`. Contains no credentials. */
|
|
6
|
+
const absolute = z.string().min(1).refine(v => isAbsolute(v) && !v.includes('\0'), 'absolute path required');
|
|
7
|
+
export const hostPattern = /^(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?)(?:\.[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?)*(?::\d{1,5})?$/i;
|
|
8
|
+
const mcpTool = z.string().regex(/^mcp__[a-zA-Z0-9_-]+__[a-zA-Z0-9_-]+$/);
|
|
9
|
+
const tool = z.union([z.string().regex(/^[A-Z][A-Za-z]{1,40}$/), mcpTool]);
|
|
10
|
+
const serverName = z.string().regex(/^[a-zA-Z0-9_-]{1,64}$/).refine(n => n !== 'tealbrick', 'the tealbrick MCP server comes from .tealbrick/native-sdk.json');
|
|
11
|
+
const stdio = z.object({ type: z.literal('stdio').optional(), command: z.string().min(1), args: z.array(z.string()).optional(), env: z.record(z.string(), z.string()).optional() }).strict();
|
|
12
|
+
export const tealbrickCallGrant = z.object({ registrationId: z.string().min(1), operation: z.string().min(1), inputEquals: z.record(z.string(), z.union([z.string(), z.number(), z.boolean(), z.null()])).default({}) }).strict();
|
|
13
|
+
const effort = z.enum(['low', 'medium', 'high', 'xhigh', 'max']);
|
|
14
|
+
/** Tools an agent may add to its default (Restricted) session access: research and expert delegation, never shell or writes. */
|
|
15
|
+
export const defaultAccessTools = ['WebSearch', 'WebFetch', 'Agent', 'TodoWrite'];
|
|
16
|
+
/** Tools an expert subagent may hold; workspace reads stay contained and network content stays untrusted. */
|
|
17
|
+
const expertTool = z.enum(['Read', 'Glob', 'Grep', 'WebSearch', 'WebFetch']);
|
|
18
|
+
const expertName = z.string().regex(/^[a-z][a-z0-9-]{0,63}$/);
|
|
19
|
+
const expert = z.object({ promptFile: absolute, description: z.string().min(1).max(300).optional(), tools: z.array(expertTool).max(8).optional(), maxTurns: z.number().int().min(1).max(50).optional(), effort: effort.optional() }).strict();
|
|
20
|
+
const claude = z.object({
|
|
21
|
+
/** Package directory or entry file of @anthropic-ai/claude-agent-sdk. Default: resolved from the kit root. */
|
|
22
|
+
sdkModule: absolute.optional(),
|
|
23
|
+
/** Working directory. Claude session transcripts are keyed by it; keep it stable. */
|
|
24
|
+
cwd: absolute,
|
|
25
|
+
instructionsFile: absolute.optional(),
|
|
26
|
+
/** Import model/limits/MCP/read tools/app-call ceilings from an existing tealbrick-native recipe file. */
|
|
27
|
+
recipe: z.object({ path: absolute, agent: z.string().regex(/^[a-zA-Z0-9_-]{1,64}$/) }).strict().optional(),
|
|
28
|
+
model: z.string().min(1).max(100).optional(),
|
|
29
|
+
effort: effort.optional(),
|
|
30
|
+
maxTurns: z.number().int().min(1).max(200).optional(),
|
|
31
|
+
maxBudgetUsd: z.number().positive().max(100).optional(),
|
|
32
|
+
timeoutSeconds: z.number().int().min(10).max(7200).optional(),
|
|
33
|
+
/** Extra tools in this agent's default (Restricted) session access. */
|
|
34
|
+
defaultTools: z.array(z.enum(defaultAccessTools)).max(4).optional(),
|
|
35
|
+
/** Expert subagents the Agent tool may delegate to, foreground only, inheriting the session model. */
|
|
36
|
+
experts: z.record(expertName, expert).optional(),
|
|
37
|
+
/** Tool allowlist for the opt-in "Native defaults" session access mode. */
|
|
38
|
+
nativeTools: z.array(tool).max(64).optional(),
|
|
39
|
+
/** Operator-vetted read-only MCP tools, also available in Restricted mode. */
|
|
40
|
+
readTools: z.array(mcpTool).max(32).optional(),
|
|
41
|
+
mcpServers: z.record(serverName, stdio).optional(),
|
|
42
|
+
tealbrickCalls: z.array(tealbrickCallGrant).max(64).optional(),
|
|
43
|
+
/** Refuse turns unless the SDK reports first-party subscription sign-in (no API-key fallback). */
|
|
44
|
+
requireSubscription: z.boolean().optional(),
|
|
45
|
+
/** Extra environment variable names passed to the SDK process. TEALBRICK_* is never passed. */
|
|
46
|
+
envPassthrough: z.array(z.string().regex(/^[A-Z][A-Z0-9_]{0,63}$/).refine(n => !n.startsWith('TEALBRICK_'), 'Teal Brick credentials are never passed to the harness')).max(32).optional(),
|
|
47
|
+
}).strict();
|
|
48
|
+
const codex = z.object({ codexBin: absolute, cwd: absolute }).strict();
|
|
49
|
+
export const serveConfigSchema = z.object({
|
|
50
|
+
version: z.literal(1),
|
|
51
|
+
port: z.number().int().min(1024).max(65535),
|
|
52
|
+
/** Public HTTPS origin registered on Portal (for example a tailscale serve URL). */
|
|
53
|
+
publicUrl: z.string().url().optional(),
|
|
54
|
+
/** Extra exact Host header values. The public URL host and loopback are added automatically. */
|
|
55
|
+
allowedHosts: z.array(z.string().regex(hostPattern)).max(8).optional(),
|
|
56
|
+
maxActiveTurns: z.number().int().min(1).max(16).optional(),
|
|
57
|
+
/** Written by `native enroll`: Portal identity used to verify every request. */
|
|
58
|
+
agent: z.object({ id: z.string().min(1), name: z.string().min(1).max(80), ownerId: z.string().min(1) }).strict().optional(),
|
|
59
|
+
claude: claude.optional(),
|
|
60
|
+
codex: codex.optional(),
|
|
61
|
+
}).strict();
|
|
62
|
+
export const servePaths = (root) => ({ config: join(root, '.tealbrick', 'native-serve.json'), state: join(root, '.tealbrick', 'native-serve'), sdk: join(root, '.tealbrick', 'native-sdk.json') });
|
|
63
|
+
async function regularFile(path, { ownerOnly }) {
|
|
64
|
+
const stat = await lstat(path);
|
|
65
|
+
if (!stat.isFile() || stat.isSymbolicLink())
|
|
66
|
+
throw Error('kit_native_serve_unsafe_file');
|
|
67
|
+
if (process.platform !== 'win32' && (stat.mode & (ownerOnly ? 0o077 : 0o022)))
|
|
68
|
+
throw Error('kit_native_serve_file_permissions');
|
|
69
|
+
}
|
|
70
|
+
/** Stable, value-free error codes for operator files; never echo file contents. */
|
|
71
|
+
function parsed(schema, text, code) {
|
|
72
|
+
let value;
|
|
73
|
+
try {
|
|
74
|
+
value = JSON.parse(text);
|
|
75
|
+
}
|
|
76
|
+
catch {
|
|
77
|
+
throw Error(code);
|
|
78
|
+
}
|
|
79
|
+
const result = schema.safeParse(value);
|
|
80
|
+
if (!result.success)
|
|
81
|
+
throw Error(code + ':' + result.error.issues.map(i => i.path.join('.') || 'root').slice(0, 5).join(','));
|
|
82
|
+
return result.data;
|
|
83
|
+
}
|
|
84
|
+
export function normalizeOrigin(value) {
|
|
85
|
+
const url = new URL(value);
|
|
86
|
+
const loopback = ['127.0.0.1', 'localhost', '[::1]'].includes(url.hostname);
|
|
87
|
+
if (!(url.protocol === 'https:' || url.protocol === 'http:' && loopback) || url.username || url.password || url.search || url.hash || url.pathname !== '/')
|
|
88
|
+
throw Error('kit_native_public_url_https_origin_required');
|
|
89
|
+
return url.origin;
|
|
90
|
+
}
|
|
91
|
+
/** Host header values a TLS-terminating proxy for `publicUrl` may present. */
|
|
92
|
+
export function publicHosts(config) {
|
|
93
|
+
const hosts = [...(config.allowedHosts ?? [])];
|
|
94
|
+
if (config.publicUrl) {
|
|
95
|
+
const url = new URL(config.publicUrl);
|
|
96
|
+
hosts.push(url.host);
|
|
97
|
+
if (!url.port)
|
|
98
|
+
hosts.push(url.hostname + ':' + (url.protocol === 'https:' ? 443 : 80));
|
|
99
|
+
}
|
|
100
|
+
return [...new Set(hosts.map(h => h.toLowerCase()))];
|
|
101
|
+
}
|
|
102
|
+
export async function readServeConfig(root) {
|
|
103
|
+
const path = servePaths(root).config;
|
|
104
|
+
try {
|
|
105
|
+
await regularFile(path, { ownerOnly: true });
|
|
106
|
+
}
|
|
107
|
+
catch (e) {
|
|
108
|
+
if (e.code === 'ENOENT')
|
|
109
|
+
return undefined;
|
|
110
|
+
throw e;
|
|
111
|
+
}
|
|
112
|
+
const config = parsed(serveConfigSchema, await readFile(path, 'utf8'), 'kit_native_serve_config_invalid');
|
|
113
|
+
if (config.publicUrl && normalizeOrigin(config.publicUrl) !== config.publicUrl)
|
|
114
|
+
throw Error('kit_native_public_url_https_origin_required');
|
|
115
|
+
return config;
|
|
116
|
+
}
|
|
117
|
+
export async function writeServeConfig(root, config) {
|
|
118
|
+
const value = serveConfigSchema.parse(config), { config: path } = servePaths(root);
|
|
119
|
+
await mkdir(join(root, '.tealbrick'), { recursive: true, mode: 0o700 });
|
|
120
|
+
try {
|
|
121
|
+
if ((await lstat(path)).isSymbolicLink())
|
|
122
|
+
throw Error('kit_symlink_path');
|
|
123
|
+
}
|
|
124
|
+
catch (e) {
|
|
125
|
+
if (e.code !== 'ENOENT')
|
|
126
|
+
throw e;
|
|
127
|
+
}
|
|
128
|
+
const tmp = path + '.' + randomUUID();
|
|
129
|
+
await writeFile(tmp, JSON.stringify(value, null, 2) + '\n', { flag: 'wx', mode: 0o600 });
|
|
130
|
+
await rename(tmp, path);
|
|
131
|
+
}
|
|
132
|
+
/** The kit-generated Claude attachment: tealbrick MCP server plus the child guard hook. */
|
|
133
|
+
const generatedClaude = z.object({
|
|
134
|
+
settings: z.object({ hooks: z.object({ PreToolUse: z.array(z.object({ matcher: z.literal('mcp__tealbrick__.*'), hooks: z.array(z.object({ type: z.literal('command'), command: z.string().min(1), args: z.array(z.string()).min(1) }).strict()).min(1) }).strict()).min(1) }).strict() }).strict(),
|
|
135
|
+
mcpServers: z.object({ tealbrick: z.object({ command: z.string().min(1), args: z.array(z.string()).min(1) }).strict() }).strict(),
|
|
136
|
+
}).strict();
|
|
137
|
+
export async function readClaudeAttachment(root) {
|
|
138
|
+
const path = servePaths(root).sdk;
|
|
139
|
+
await regularFile(path, { ownerOnly: true });
|
|
140
|
+
const value = parsed(generatedClaude, await readFile(path, 'utf8'), 'kit_native_sdk_config_invalid');
|
|
141
|
+
const args = value.mcpServers.tealbrick.args, at = args.indexOf('--root');
|
|
142
|
+
if (args.slice(-4, -2).join(' ') !== 'native mcp' || at < 0 || resolve(args[at + 1] ?? '') !== await realpath(root))
|
|
143
|
+
throw Error('kit_native_sdk_config_root_mismatch');
|
|
144
|
+
return value;
|
|
145
|
+
}
|
|
146
|
+
const recipeSchema = z.object({
|
|
147
|
+
model: z.string().min(1).optional(), effort: effort.optional(), maxTurns: z.number().int().min(1).max(200).optional(),
|
|
148
|
+
maxBudgetUsd: z.number().positive().max(100).optional(), timeoutSeconds: z.number().int().min(10).max(7200).optional(),
|
|
149
|
+
agents: z.record(z.string(), z.object({ mcpServers: z.record(serverName, stdio).optional(), readTools: z.array(mcpTool).max(32).optional(), tealbrickCalls: z.array(tealbrickCallGrant).max(64).optional(), tealbrickSdkConfig: absolute.optional() }).passthrough()),
|
|
150
|
+
}).passthrough();
|
|
151
|
+
/** Resolve the effective Claude profile. Explicit serve fields override the imported recipe. */
|
|
152
|
+
export async function resolveClaudeProfile(root, config) {
|
|
153
|
+
const c = config.claude;
|
|
154
|
+
if (!c)
|
|
155
|
+
throw Error('kit_native_serve_claude_profile_required');
|
|
156
|
+
let recipe, agent;
|
|
157
|
+
if (c.recipe) {
|
|
158
|
+
await regularFile(c.recipe.path, { ownerOnly: false });
|
|
159
|
+
recipe = parsed(recipeSchema, await readFile(c.recipe.path, 'utf8'), 'kit_native_serve_recipe_invalid');
|
|
160
|
+
agent = recipe.agents[c.recipe.agent];
|
|
161
|
+
if (!agent)
|
|
162
|
+
throw Error('kit_native_serve_recipe_agent_missing');
|
|
163
|
+
// The recipe must describe THIS kit root's Portal connection, never another agent's.
|
|
164
|
+
if (agent.tealbrickSdkConfig && await realpath(agent.tealbrickSdkConfig) !== await realpath(servePaths(root).sdk))
|
|
165
|
+
throw Error('kit_native_serve_recipe_connection_mismatch');
|
|
166
|
+
}
|
|
167
|
+
const cwd = resolve(c.cwd), protectedRoot = join(await realpath(root), '.tealbrick');
|
|
168
|
+
if (protectedRoot.startsWith(cwd + '/') || cwd === protectedRoot || cwd.startsWith(protectedRoot + '/'))
|
|
169
|
+
throw Error('kit_native_serve_cwd_contains_credentials');
|
|
170
|
+
if (!(await lstat(cwd).catch(() => undefined))?.isDirectory())
|
|
171
|
+
throw Error('kit_native_serve_cwd_missing');
|
|
172
|
+
const instructions = c.instructionsFile ? (await regularFile(c.instructionsFile, { ownerOnly: false }), await readFile(c.instructionsFile, 'utf8')) : undefined;
|
|
173
|
+
if (instructions && instructions.length > 200000)
|
|
174
|
+
throw Error('kit_native_serve_instructions_too_large');
|
|
175
|
+
const readTools = c.readTools ?? agent?.readTools ?? [];
|
|
176
|
+
const experts = {};
|
|
177
|
+
for (const [name, e] of Object.entries(c.experts ?? {})) {
|
|
178
|
+
await regularFile(e.promptFile, { ownerOnly: false });
|
|
179
|
+
const prompt = await readFile(e.promptFile, 'utf8');
|
|
180
|
+
if (!prompt.trim() || prompt.length > 50000)
|
|
181
|
+
throw Error('kit_native_serve_expert_prompt_invalid:' + name);
|
|
182
|
+
experts[name] = { description: e.description ?? name.replaceAll('-', ' ') + ' for a bounded assignment', prompt, tools: [...new Set(e.tools ?? [])], maxTurns: e.maxTurns ?? 8, ...(e.effort ? { effort: e.effort } : {}) };
|
|
183
|
+
}
|
|
184
|
+
return { cwd, sdkModule: c.sdkModule, instructions, model: c.model ?? recipe?.model, effort: c.effort ?? recipe?.effort,
|
|
185
|
+
maxTurns: c.maxTurns ?? recipe?.maxTurns ?? 24, maxBudgetUsd: c.maxBudgetUsd ?? recipe?.maxBudgetUsd, timeoutSeconds: c.timeoutSeconds ?? recipe?.timeoutSeconds ?? 900,
|
|
186
|
+
defaultTools: [...new Set(c.defaultTools ?? [])], experts,
|
|
187
|
+
nativeTools: c.nativeTools ?? ['Read', 'Glob', 'Grep', 'TodoWrite'], readTools: readTools.filter(t => !t.startsWith('mcp__tealbrick__')),
|
|
188
|
+
mcpServers: c.mcpServers ?? agent?.mcpServers ?? {}, tealbrickCalls: c.tealbrickCalls ?? agent?.tealbrickCalls ?? [],
|
|
189
|
+
requireSubscription: c.requireSubscription ?? false, envPassthrough: c.envPassthrough ?? [] };
|
|
190
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { type AuthResult } from '@tealbrick/portal';
|
|
2
|
+
import { type ClaudeSdk } from './native-claude.js';
|
|
3
|
+
/** LangChain has no stable session/stream/cancel contract to drive from here yet. */
|
|
4
|
+
export declare function langchainHarness(): never;
|
|
5
|
+
export interface ServeDependencies {
|
|
6
|
+
authenticate?: (request: Request) => Promise<AuthResult>;
|
|
7
|
+
claudeSdk?: ClaudeSdk;
|
|
8
|
+
log?: (event: Record<string, unknown>) => void;
|
|
9
|
+
env?: NodeJS.ProcessEnv;
|
|
10
|
+
}
|
|
11
|
+
/** Build (but do not bind) the owner-authenticated chat endpoint for this kit root. */
|
|
12
|
+
export declare function createNativeServe(root: string, deps?: ServeDependencies): Promise<{
|
|
13
|
+
port: number;
|
|
14
|
+
harness: "claude" | "codex" | "langchain";
|
|
15
|
+
close: () => Promise<void>;
|
|
16
|
+
server: import("http").Server;
|
|
17
|
+
sessions: Map<string, import("@tealbrick/portal/native-bridge").BridgeSession<any>>;
|
|
18
|
+
}>;
|
|
19
|
+
export declare function serveNative(root: string): Promise<void>;
|
|
20
|
+
export declare function launchdLabel(root: string): string;
|
|
21
|
+
/** User LaunchAgent: restarts on crash, logs to the kit root's private state directory. */
|
|
22
|
+
export declare function launchdPlist(root: string, options?: {
|
|
23
|
+
node?: string;
|
|
24
|
+
cli?: string;
|
|
25
|
+
}): string;
|
|
26
|
+
type Exec = (file: string, args: string[]) => Promise<unknown>;
|
|
27
|
+
export declare function installLaunchd(root: string, options?: {
|
|
28
|
+
home?: string;
|
|
29
|
+
exec?: Exec;
|
|
30
|
+
load?: boolean;
|
|
31
|
+
uid?: number;
|
|
32
|
+
platform?: string;
|
|
33
|
+
}): Promise<{
|
|
34
|
+
installed: boolean;
|
|
35
|
+
label: string;
|
|
36
|
+
plist: string;
|
|
37
|
+
loaded: boolean;
|
|
38
|
+
logs: string;
|
|
39
|
+
}>;
|
|
40
|
+
export declare function uninstallLaunchd(root: string, options?: {
|
|
41
|
+
home?: string;
|
|
42
|
+
exec?: Exec;
|
|
43
|
+
uid?: number;
|
|
44
|
+
platform?: string;
|
|
45
|
+
}): Promise<{
|
|
46
|
+
uninstalled: boolean;
|
|
47
|
+
label: string;
|
|
48
|
+
plist: string;
|
|
49
|
+
}>;
|
|
50
|
+
export {};
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
import { execFile } from 'node:child_process';
|
|
3
|
+
import { mkdir, realpath, writeFile, rename, unlink, lstat } from 'node:fs/promises';
|
|
4
|
+
import { homedir } from 'node:os';
|
|
5
|
+
import { dirname, join } from 'node:path';
|
|
6
|
+
import { fileURLToPath } from 'node:url';
|
|
7
|
+
import { promisify } from 'node:util';
|
|
8
|
+
import { createPortalVerifier } from '@tealbrick/portal';
|
|
9
|
+
import { createNativeBridge } from '@tealbrick/portal/native-bridge';
|
|
10
|
+
import { CodexAppServer, codexHarness } from '@tealbrick/portal/native-codex';
|
|
11
|
+
import { readKit } from './index.js';
|
|
12
|
+
import { nativeSdkConfig } from './native-setup.js';
|
|
13
|
+
import { claudeHarness, loadClaudeSdk } from './native-claude.js';
|
|
14
|
+
import { publicHosts, readClaudeAttachment, readServeConfig, resolveClaudeProfile, servePaths } from './native-serve-config.js';
|
|
15
|
+
/** LangChain has no stable session/stream/cancel contract to drive from here yet. */
|
|
16
|
+
export function langchainHarness() { throw Error('kit_native_serve_langchain_unsupported'); }
|
|
17
|
+
/** Build (but do not bind) the owner-authenticated chat endpoint for this kit root. */
|
|
18
|
+
export async function createNativeServe(root, deps = {}) {
|
|
19
|
+
root = await realpath(root);
|
|
20
|
+
const kit = await readKit(root);
|
|
21
|
+
if (!kit.native || !kit.runtime)
|
|
22
|
+
throw Error('kit_native_setup_required');
|
|
23
|
+
if (!kit.native.active)
|
|
24
|
+
throw Error('runtime_product_inactive');
|
|
25
|
+
const config = await readServeConfig(root);
|
|
26
|
+
if (!config)
|
|
27
|
+
throw Error('kit_native_serve_config_required');
|
|
28
|
+
if (!config.agent)
|
|
29
|
+
throw Error('kit_native_serve_enroll_required');
|
|
30
|
+
if (config.agent.id !== kit.runtime.agentId)
|
|
31
|
+
throw Error('kit_native_serve_identity_mismatch');
|
|
32
|
+
const paths = servePaths(root), log = deps.log;
|
|
33
|
+
await mkdir(paths.state, { recursive: true, mode: 0o700 });
|
|
34
|
+
let adapter;
|
|
35
|
+
let codex;
|
|
36
|
+
if (kit.native.harness === 'claude') {
|
|
37
|
+
const profile = await resolveClaudeProfile(root, config);
|
|
38
|
+
adapter = claudeHarness({ name: config.agent.name, profile, attachment: await readClaudeAttachment(root), sdk: deps.claudeSdk ?? await loadClaudeSdk(root, profile.sdkModule), protectedPaths: [join(root, '.tealbrick')], log, env: deps.env });
|
|
39
|
+
}
|
|
40
|
+
else if (kit.native.harness === 'codex') {
|
|
41
|
+
if (!config.codex)
|
|
42
|
+
throw Error('kit_native_serve_codex_profile_required');
|
|
43
|
+
codex = new CodexAppServer(config.codex.codexBin, config.codex.cwd);
|
|
44
|
+
await codex.start();
|
|
45
|
+
const mcp = nativeSdkConfig('codex', root, { codexDelegationDisabled: true }).config.mcp_servers.tealbrick;
|
|
46
|
+
adapter = codexHarness(codex, { name: config.agent.name, cwd: config.codex.cwd, mcp, features: { multi_agent_v2: false }, log });
|
|
47
|
+
}
|
|
48
|
+
else
|
|
49
|
+
adapter = langchainHarness();
|
|
50
|
+
const authenticate = deps.authenticate ?? createPortalVerifier({ issuer: kit.runtime.issuer, org: kit.runtime.org, agent: config.agent.name }).authenticate;
|
|
51
|
+
const bridge = await createNativeBridge({ name: config.agent.name, ownerId: config.agent.ownerId, root: paths.state, port: config.port, allowedHosts: publicHosts(config), authenticate, adapter, maxActiveTurns: config.maxActiveTurns ?? 2, log });
|
|
52
|
+
return { ...bridge, port: config.port, harness: kit.native.harness, close: async () => { await bridge.close(); await codex?.close(); } };
|
|
53
|
+
}
|
|
54
|
+
export async function serveNative(root) {
|
|
55
|
+
const serve = await createNativeServe(root);
|
|
56
|
+
// Loopback only. Remote reachability is a TLS-terminating proxy such as tailscale serve.
|
|
57
|
+
await new Promise((resolve, reject) => { serve.server.once('error', reject); serve.server.listen(serve.port, '127.0.0.1', () => resolve()); });
|
|
58
|
+
process.stderr.write(JSON.stringify({ event: 'native.serve.ready', harness: serve.harness, port: serve.port }) + '\n');
|
|
59
|
+
for (const signal of ['SIGTERM', 'SIGINT'])
|
|
60
|
+
process.once(signal, () => { void serve.close().finally(() => process.exit(0)); });
|
|
61
|
+
}
|
|
62
|
+
const xml = (v) => v.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"');
|
|
63
|
+
export function launchdLabel(root) { return 'com.tealbrick.native-serve.' + createHash('sha256').update(root).digest('hex').slice(0, 12); }
|
|
64
|
+
/** User LaunchAgent: restarts on crash, logs to the kit root's private state directory. */
|
|
65
|
+
export function launchdPlist(root, options = {}) {
|
|
66
|
+
const node = options.node ?? process.execPath, cli = options.cli ?? fileURLToPath(new URL('./cli.js', import.meta.url)), logs = join(servePaths(root).state, 'logs');
|
|
67
|
+
const args = [node, cli, 'native', 'serve', '--root', root];
|
|
68
|
+
return `<?xml version="1.0" encoding="UTF-8"?>
|
|
69
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
70
|
+
<plist version="1.0">
|
|
71
|
+
<dict>
|
|
72
|
+
<key>Label</key><string>${xml(launchdLabel(root))}</string>
|
|
73
|
+
<key>ProgramArguments</key><array>${args.map(a => `<string>${xml(a)}</string>`).join('')}</array>
|
|
74
|
+
<key>WorkingDirectory</key><string>${xml(root)}</string>
|
|
75
|
+
<key>EnvironmentVariables</key><dict><key>PATH</key><string>${xml(dirname(node))}:/usr/bin:/bin:/usr/sbin:/sbin</string></dict>
|
|
76
|
+
<key>RunAtLoad</key><true/>
|
|
77
|
+
<key>KeepAlive</key><dict><key>SuccessfulExit</key><false/></dict>
|
|
78
|
+
<key>ThrottleInterval</key><integer>30</integer>
|
|
79
|
+
<key>ProcessType</key><string>Background</string>
|
|
80
|
+
<key>Umask</key><integer>63</integer>
|
|
81
|
+
<key>StandardOutPath</key><string>${xml(join(logs, 'serve.out.log'))}</string>
|
|
82
|
+
<key>StandardErrorPath</key><string>${xml(join(logs, 'serve.err.log'))}</string>
|
|
83
|
+
</dict>
|
|
84
|
+
</plist>
|
|
85
|
+
`;
|
|
86
|
+
}
|
|
87
|
+
export async function installLaunchd(root, options = {}) {
|
|
88
|
+
if ((options.platform ?? process.platform) !== 'darwin')
|
|
89
|
+
throw Error('kit_launchd_macos_only');
|
|
90
|
+
root = await realpath(root);
|
|
91
|
+
const config = await readServeConfig(root);
|
|
92
|
+
if (!config?.agent)
|
|
93
|
+
throw Error('kit_native_serve_enroll_required');
|
|
94
|
+
const label = launchdLabel(root), dir = join(options.home ?? homedir(), 'Library', 'LaunchAgents'), path = join(dir, label + '.plist');
|
|
95
|
+
await mkdir(join(servePaths(root).state, 'logs'), { recursive: true, mode: 0o700 });
|
|
96
|
+
await mkdir(dir, { recursive: true });
|
|
97
|
+
try {
|
|
98
|
+
if ((await lstat(path)).isSymbolicLink())
|
|
99
|
+
throw Error('kit_symlink_path');
|
|
100
|
+
}
|
|
101
|
+
catch (e) {
|
|
102
|
+
if (e.code !== 'ENOENT')
|
|
103
|
+
throw e;
|
|
104
|
+
}
|
|
105
|
+
await writeFile(path + '.tmp', launchdPlist(root), { mode: 0o600 });
|
|
106
|
+
await rename(path + '.tmp', path);
|
|
107
|
+
const exec = options.exec ?? promisify(execFile), domain = 'gui/' + (options.uid ?? process.getuid?.() ?? 0);
|
|
108
|
+
if (options.load !== false) {
|
|
109
|
+
await exec('launchctl', ['bootout', domain + '/' + label]).catch(() => { });
|
|
110
|
+
await exec('launchctl', ['bootstrap', domain, path]);
|
|
111
|
+
}
|
|
112
|
+
return { installed: true, label, plist: path, loaded: options.load !== false, logs: join(servePaths(root).state, 'logs') };
|
|
113
|
+
}
|
|
114
|
+
export async function uninstallLaunchd(root, options = {}) {
|
|
115
|
+
if ((options.platform ?? process.platform) !== 'darwin')
|
|
116
|
+
throw Error('kit_launchd_macos_only');
|
|
117
|
+
root = await realpath(root);
|
|
118
|
+
const label = launchdLabel(root), path = join(options.home ?? homedir(), 'Library', 'LaunchAgents', label + '.plist');
|
|
119
|
+
const exec = options.exec ?? promisify(execFile);
|
|
120
|
+
await exec('launchctl', ['bootout', 'gui/' + (options.uid ?? process.getuid?.() ?? 0) + '/' + label]).catch(() => { });
|
|
121
|
+
await unlink(path).catch(e => { if (e.code !== 'ENOENT')
|
|
122
|
+
throw e; });
|
|
123
|
+
return { uninstalled: true, label, plist: path };
|
|
124
|
+
}
|
package/dist/native.js
CHANGED
|
@@ -47,7 +47,69 @@ export function nativeConnector() {
|
|
|
47
47
|
return { marketplace: async () => { const config = await readKit(process.cwd()); if (!config.native?.active || !config.native.marketplace || !config.runtime)
|
|
48
48
|
return undefined; const { credentialRef, ...identity } = config.runtime; return new MarketplaceRuntimeConnector({ ...identity, credential: () => runtimeSecret(credentialRef) }); }, capabilities: async () => (await current()).capabilities(true), call: async (registrationId, operation, input = {}, signal) => (await current()).call(registrationId, operation, input, signal) };
|
|
49
49
|
}
|
|
50
|
+
/** serve/enroll take boolean switches, so they are parsed separately from setup flags. */
|
|
51
|
+
function serveFlags(args, values, switches) {
|
|
52
|
+
const flags = {};
|
|
53
|
+
for (let i = 0; i < args.length; i++) {
|
|
54
|
+
const name = /^--([a-z-]+)$/.exec(args[i])?.[1];
|
|
55
|
+
if (!name || flags[name] !== undefined)
|
|
56
|
+
throw Error('kit_native_invalid_arguments');
|
|
57
|
+
if (switches.includes(name)) {
|
|
58
|
+
flags[name] = true;
|
|
59
|
+
continue;
|
|
60
|
+
}
|
|
61
|
+
if (!values.includes(name) || !args[i + 1] || args[i + 1].startsWith('--'))
|
|
62
|
+
throw Error('kit_native_invalid_arguments');
|
|
63
|
+
flags[name] = args[++i];
|
|
64
|
+
}
|
|
65
|
+
return flags;
|
|
66
|
+
}
|
|
50
67
|
export async function nativeCommand(args, root) {
|
|
68
|
+
if (args[0] === 'serve' || args[0] === 'enroll') {
|
|
69
|
+
const flags = args[0] === 'serve' ? serveFlags(args.slice(1), ['root'], ['install-launchd', 'uninstall', 'no-load']) : serveFlags(args.slice(1), ['root', 'url', 'port'], ['skip-endpoint-check']);
|
|
70
|
+
if (typeof flags.root === 'string') {
|
|
71
|
+
root = resolve(flags.root);
|
|
72
|
+
process.chdir(root);
|
|
73
|
+
}
|
|
74
|
+
if (args[0] === 'serve') {
|
|
75
|
+
const { serveNative, installLaunchd, uninstallLaunchd } = await import('./native-serve.js');
|
|
76
|
+
if (flags['install-launchd'] && flags.uninstall)
|
|
77
|
+
throw Error('kit_native_invalid_arguments');
|
|
78
|
+
if (flags.uninstall) {
|
|
79
|
+
console.log(JSON.stringify(await uninstallLaunchd(root)));
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
if (flags['install-launchd']) {
|
|
83
|
+
console.log(JSON.stringify(await installLaunchd(root, { load: !flags['no-load'] })));
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
if (flags['no-load'])
|
|
87
|
+
throw Error('kit_native_invalid_arguments');
|
|
88
|
+
await serveNative(root);
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
if (typeof flags.url !== 'string')
|
|
92
|
+
throw Error('kit_native_public_url_required');
|
|
93
|
+
const { enrollNative } = await import('./native-enroll.js');
|
|
94
|
+
const state = await readKit(root);
|
|
95
|
+
if (!state.native || !state.runtime)
|
|
96
|
+
throw Error('kit_native_setup_required');
|
|
97
|
+
const login = await loginPortal(portalOrigin(state.runtime.issuer), (url, code) => console.error(`Approve Tealbrick endpoint registration: ${url}\nCode: ${code}`));
|
|
98
|
+
try {
|
|
99
|
+
const result = await enrollNative(root, { url: flags.url, ...(typeof flags.port === 'string' ? { port: Number(flags.port) } : {}) }, login, { verifyEndpoint: !flags['skip-endpoint-check'] });
|
|
100
|
+
console.log(JSON.stringify(result));
|
|
101
|
+
if (!flags['skip-endpoint-check'] && !result.endpointVerified) {
|
|
102
|
+
console.error('Portal registration is saved. Start `tealbrick native serve` and the HTTPS proxy, then re-run enroll to verify the endpoint.');
|
|
103
|
+
process.exitCode = 2;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
catch (error) {
|
|
107
|
+
if (error instanceof PortalHttpError && /^[a-z_]+$/.test(error.code ?? ''))
|
|
108
|
+
throw Error('kit_portal_' + error.code);
|
|
109
|
+
throw error;
|
|
110
|
+
}
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
51
113
|
const command = args[0], flags = {};
|
|
52
114
|
for (let i = 1; i < args.length; i += 2) {
|
|
53
115
|
if (!/^--(root|harness|workspace|workspace-name|create-workspace|node|agent|agent-name|create-agent|delegation|issuer)$/.test(args[i]) || !args[i + 1] || flags[args[i].slice(2)])
|
|
@@ -109,6 +171,6 @@ export async function nativeCommand(args, root) {
|
|
|
109
171
|
console.log(JSON.stringify({ active: command === 'enable' }));
|
|
110
172
|
return;
|
|
111
173
|
}
|
|
112
|
-
throw Error('Usage: tealbrick native setup --harness claude|codex|langchain [--workspace-name NAME | --create-workspace NAME] [--agent-name NAME | --create-agent NAME] [--issuer HTTPS_ORIGIN] | native mcp [--root DIRECTORY] | native enable|disable | native marketplace-enable|marketplace-disable');
|
|
174
|
+
throw Error('Usage: tealbrick native setup --harness claude|codex|langchain [--workspace-name NAME | --create-workspace NAME] [--agent-name NAME | --create-agent NAME] [--issuer HTTPS_ORIGIN] | native mcp [--root DIRECTORY] | native serve [--root DIRECTORY] [--install-launchd [--no-load] | --uninstall] | native enroll --url HTTPS_ORIGIN [--port PORT] [--root DIRECTORY] [--skip-endpoint-check] | native enable|disable | native marketplace-enable|marketplace-disable');
|
|
113
175
|
}
|
|
114
176
|
export { guidedNativeSetup, nativeSdkConfig };
|
package/dist/onboarding.d.ts
CHANGED
|
@@ -8,6 +8,11 @@ export interface PortalLogin {
|
|
|
8
8
|
id: string;
|
|
9
9
|
name: string;
|
|
10
10
|
}>;
|
|
11
|
+
/** Ephemeral user identity for endpoint verification. Never persisted. */
|
|
12
|
+
identity?: {
|
|
13
|
+
token: string;
|
|
14
|
+
bundle: string;
|
|
15
|
+
};
|
|
11
16
|
}
|
|
12
17
|
/** Reject URLs that would leak credentials or be silently truncated by registration. */
|
|
13
18
|
export declare function portalOrigin(value: string): string;
|
package/dist/onboarding.js
CHANGED
|
@@ -53,7 +53,7 @@ export async function loginPortal(issuer, show, options = {}) {
|
|
|
53
53
|
const directory = await authenticated.request('/api/me/workspaces');
|
|
54
54
|
if (!account.user?.id || !account.user.orgId || directory.accountId !== account.user.id || directory.organizationId !== account.user.orgId || !Array.isArray(directory.workspaces) || directory.workspaces.some(w => !w.id || !w.name))
|
|
55
55
|
throw Error('kit_portal_account_mismatch');
|
|
56
|
-
return { issuer, client: authenticated, org: account.user.orgId, userId: account.user.id, workspaces: directory.workspaces };
|
|
56
|
+
return { issuer, client: authenticated, org: account.user.orgId, userId: account.user.id, workspaces: directory.workspaces, identity: { token: session.token, bundle: session.bundle } };
|
|
57
57
|
}
|
|
58
58
|
/** Reuse Portal's owned registration + revision-checked canvas APIs, not a parallel card store. */
|
|
59
59
|
export async function registerPortalCard(login, input) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tealbrick/kit",
|
|
3
|
-
"version": "0.3.0-rc.
|
|
3
|
+
"version": "0.3.0-rc.4",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"description": "Complete Teal Brick package kit with explicit Eve capability activation",
|
|
@@ -43,11 +43,11 @@
|
|
|
43
43
|
"test": "node --test test/*.test.mjs"
|
|
44
44
|
},
|
|
45
45
|
"dependencies": {
|
|
46
|
-
"@tealbrick/portal": "0.3.0-rc.
|
|
47
|
-
"@tealbrick/avm": "0.3.0-rc.
|
|
48
|
-
"@tealbrick/voice": "0.3.0-rc.
|
|
49
|
-
"@tealbrick/vision": "0.3.0-rc.
|
|
50
|
-
"@tealbrick/deliver": "0.3.0-rc.
|
|
46
|
+
"@tealbrick/portal": "0.3.0-rc.4",
|
|
47
|
+
"@tealbrick/avm": "0.3.0-rc.4",
|
|
48
|
+
"@tealbrick/voice": "0.3.0-rc.4",
|
|
49
|
+
"@tealbrick/vision": "0.3.0-rc.4",
|
|
50
|
+
"@tealbrick/deliver": "0.3.0-rc.4",
|
|
51
51
|
"@inquirer/prompts": "8.7.2",
|
|
52
52
|
"zod": "^4.0.0"
|
|
53
53
|
},
|