@volter/supercode-teams 0.2.163 → 0.2.164
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 +4 -4
- package/bin/workspace/args.mjs +2 -2
- package/bin/workspace.mjs +23 -1
- package/package.json +1 -1
- package/server/event-routes.mjs +4 -0
- package/server/harness-routes.mjs +2 -1
- package/server/index.mjs +3 -2
- package/server/store-core.mjs +5 -0
- package/worker/index.mjs +3 -4
- package/worker/wrangler.toml +2 -12
- package/workspace-client.d.ts +15 -0
- package/workspace-client.mjs +13 -0
package/README.md
CHANGED
|
@@ -46,10 +46,10 @@ a kind another product serves on the same machine. `title.set` needs `control` o
|
|
|
46
46
|
`observe` on a `root:/path` reads it (stat, list, read, search); `files` also changes it.
|
|
47
47
|
|
|
48
48
|
A workplace can open one pane or one folder for someone who is not a member of the team. A
|
|
49
|
-
server
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
49
|
+
server trusts only admin-consented [app installations](../../docs/guides/teams-apps.md) with
|
|
50
|
+
`delegation.surfaces` or `delegation.sessions` for that team. Register the exact issuer origin
|
|
51
|
+
on the app; install the needed module separately. Operator issuer allowlists grant no access.
|
|
52
|
+
Keys are cached separately per origin, and each token must match a
|
|
53
53
|
publication or session binding for that exact issuer. Staging never opens a production
|
|
54
54
|
binding or publication. Tokens use Ed25519 (keys at `ORIGIN/.well-known/jwks.json`, audience this server's
|
|
55
55
|
public origin), each naming a pane (`pane:<key>`) or a folder (`root:/path`) on one machine of one
|
package/bin/workspace/args.mjs
CHANGED
|
@@ -10,8 +10,8 @@ const VALUES = new Set([
|
|
|
10
10
|
'source', 'cursor', 'limit', 'tail', 'before', 'after', 'revision', 'role',
|
|
11
11
|
'out', 'ttl', 'type', 'name', 'visibility', 'backfill', 'format', 'to',
|
|
12
12
|
'grant', 'binding', 'caps', 'fidelity', 'cwd', 'root', 'workspace-from', 'since', 'until', 'session',
|
|
13
|
-
'on', 'timeout', 'plan', 'tmux-socket', 'volter-issuer', '
|
|
14
|
-
'conversation', 'rh2', 'scope', 'agent',
|
|
13
|
+
'on', 'timeout', 'plan', 'tmux-socket', 'volter-issuer', 'workplace', 'organization', 'key', 'room',
|
|
14
|
+
'conversation', 'rh2', 'scope', 'agent', 'body-file',
|
|
15
15
|
]);
|
|
16
16
|
|
|
17
17
|
export function parseArgs(argv) {
|
package/bin/workspace.mjs
CHANGED
|
@@ -57,6 +57,7 @@ export async function workspaceMain(argv = process.argv.slice(2), env = process.
|
|
|
57
57
|
const team = client.team(context.team_id);
|
|
58
58
|
const command = { flags, rest, output, client, context, team, explicitContext, home, contextName: selected, env, dependencies };
|
|
59
59
|
if (teamsPrefix && ['list', 'create'].includes(verb)) return teamsCommand(verb, sub, command);
|
|
60
|
+
if (verb === 'apps' || verb === 'app-installations') return appsCommand(verb,sub,{...command,output:value=>output(flags.json?value:redactAppSecrets(value))});
|
|
60
61
|
if (verb === 'sessions') return sessionsCommand(sub, command);
|
|
61
62
|
if (verb === 'members') return membersCommand(sub, command);
|
|
62
63
|
if (verb === 'groups') return groupsCommand(sub, command);
|
|
@@ -69,6 +70,27 @@ export async function workspaceMain(argv = process.argv.slice(2), env = process.
|
|
|
69
70
|
usage(`unsupported workspace command ${verb}`);
|
|
70
71
|
}
|
|
71
72
|
|
|
73
|
+
function redactAppSecrets(value) {
|
|
74
|
+
if(!value||typeof value!=='object')return value;
|
|
75
|
+
if(Array.isArray(value))return value.map(redactAppSecrets);
|
|
76
|
+
return Object.fromEntries(Object.entries(value).filter(([key])=>!['secret','webhook_secret','code','token'].includes(key)).map(([key,item])=>[key,redactAppSecrets(item)]));
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
async function appsCommand(verb,sub,{flags,rest,output,team}) {
|
|
80
|
+
only(flags,['json','help','context','body_file']);
|
|
81
|
+
const body=()=>{if(!flags.body_file)usage('--body-file is required (use - for stdin)');return JSON.parse(readFileSync(flags.body_file==='-'?0:flags.body_file,'utf8'));};
|
|
82
|
+
if(verb==='apps') {
|
|
83
|
+
if(sub==='catalogue'||sub==='list'){requireArgs(rest,0,0,'teams apps catalogue|list');return output(await team.apps[sub]());}
|
|
84
|
+
if(sub==='get'||sub==='rotate-secret'){requireArgs(rest,1,1,'teams apps get|rotate-secret APP');return output(await team.apps[sub==='get'?'get':'rotateSecret'](rest[0]));}
|
|
85
|
+
if(sub==='register'){requireArgs(rest,0,0,'teams apps register --body-file FILE');return output(await team.apps.register(body()));}
|
|
86
|
+
if(sub==='install'){requireArgs(rest,1,1,'teams apps install APP --body-file FILE');return output(await team.apps.install(rest[0],body()));}
|
|
87
|
+
} else {
|
|
88
|
+
if(sub==='list'){requireArgs(rest,0,0,'teams app-installations list');return output(await team.appInstallations.list());}
|
|
89
|
+
if(['get','uninstall','delegate'].includes(sub)){requireArgs(rest,1,1,'teams app-installations get|uninstall|delegate INSTALLATION');return output(await team.appInstallations[sub](rest[0],...(sub==='delegate'?[body()]:[])));}
|
|
90
|
+
}
|
|
91
|
+
usage('teams apps catalogue|list|get|register|install|rotate-secret; app-installations list|get|uninstall|delegate');
|
|
92
|
+
}
|
|
93
|
+
|
|
72
94
|
async function teamsCommand(verb, sub, { flags, rest, output, client }) {
|
|
73
95
|
if (verb === 'list') { only(flags, ['json', 'help', 'context', 'cursor', 'limit']); requireArgs(rest, 0, 0, 'teams list'); return output(await client.teams({ cursor: flags.cursor, limit: positiveInteger(flags.limit, 50, 'limit') })); }
|
|
74
96
|
const names = [sub, ...rest].filter(value => value !== undefined);
|
|
@@ -120,7 +142,7 @@ async function serverCommand({ flags, sub, rest, home, output }) {
|
|
|
120
142
|
// The Node host's store is node:sqlite (Node 22.5+); only this verb loads it, so every client
|
|
121
143
|
// verb runs on the Node 20 the package declares.
|
|
122
144
|
const { createTeamsServer } = await import('../server/index.mjs');
|
|
123
|
-
const server = createTeamsServer({ dataDir: flags.data_dir ?? resolve(home, 'teams', 'server'), listen: hostPort(flags.listen ?? '127.0.0.1:7331'), publicOrigin: flags.public_origin ?? null,
|
|
145
|
+
const server = createTeamsServer({ dataDir: flags.data_dir ?? resolve(home, 'teams', 'server'), listen: hostPort(flags.listen ?? '127.0.0.1:7331'), publicOrigin: flags.public_origin ?? null, ...(flags.init_team ? { initTeam: flags.init_team } : {}) });
|
|
124
146
|
output(await server.start());
|
|
125
147
|
await new Promise(resolveStop => { const stop = async () => { await server.close(); resolveStop(); }; process.once('SIGINT', stop); process.once('SIGTERM', stop); });
|
|
126
148
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@volter/supercode-teams",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.164",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "A machine daemon serving this machine's doors, and a local or remote Teams server for shared session discovery, OpenTelemetry ingestion and access to enrolled machines.",
|
|
6
6
|
"license": "MIT",
|
package/server/event-routes.mjs
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { appAuthentication } from '@volter/teams/server/apps';
|
|
1
2
|
import { createHash } from 'node:crypto';
|
|
2
3
|
import { validateListQuery } from '../contracts/index.mjs';
|
|
3
4
|
import { cursorHash, cursorToken } from './catalog.mjs';
|
|
@@ -82,6 +83,7 @@ export async function sessionNames(api, teamId, descriptor) {
|
|
|
82
83
|
}
|
|
83
84
|
|
|
84
85
|
async function readForEvent(api, auth, ref, tail) {
|
|
86
|
+
if(!auth.bound&&!await appAuthentication(api,auth))throw Object.assign(new Error('app authorization changed'),{status:401});
|
|
85
87
|
const nextCursorToken = cursorToken();
|
|
86
88
|
const snapshot = await api.store.readSessionSnapshot({
|
|
87
89
|
...sessionArgs(api, auth, ref), tail,
|
|
@@ -121,6 +123,7 @@ export async function handleSessionEvents({ api, req, res, auth, id, url, reques
|
|
|
121
123
|
const stream = openStream(api, req, res, auth, requestId, fail);
|
|
122
124
|
if (after === null) stream.write({ type: 'snapshot', cursor: initial.event_cursor, revision: initial.revision, data: initial });
|
|
123
125
|
const enqueue = streamQueue(stream, async () => {
|
|
126
|
+
if(!auth.bound&&!await appAuthentication(api,auth))fail(401,'authentication_required','app authorization changed');
|
|
124
127
|
// This worker operation reads current credential, membership, groups and every constituent ACL together.
|
|
125
128
|
await api.store.authorizedSession(sessionArgs(api, auth, sessionId));
|
|
126
129
|
const replay = await api.store.query('SELECT revision,kind FROM journal WHERE team_id=? AND session_id=? AND revision>? ORDER BY revision', [auth.team_id, sessionId, delivered]);
|
|
@@ -153,6 +156,7 @@ async function catalogWindow(api, auth, query) {
|
|
|
153
156
|
{ kind: 'query', sql: 'SELECT s.*,v.descriptor_json,v.sort_at FROM sessions s LEFT JOIN session_versions v ON v.team_id=s.team_id AND v.session_id=s.id AND v.revision=s.revision WHERE s.team_id=? ORDER BY v.sort_at DESC,s.id ASC', params: [auth.team_id] },
|
|
154
157
|
{ kind: 'query', sql: 'SELECT * FROM session_grants WHERE team_id=?', params: [auth.team_id] },
|
|
155
158
|
]);
|
|
159
|
+
if(!auth.bound&&!await appAuthentication(api,auth))throw Object.assign(new Error('app authorization changed'),{status:401});
|
|
156
160
|
const now = api.clock.nowMs();
|
|
157
161
|
if (!membership?.active || !credential || credential.revoked_at != null || credential.expires_at != null && credential.expires_at <= now) throw Object.assign(new Error('authorization changed'), { status: 401 });
|
|
158
162
|
const byId = new Map(rows.map(row => [row.id, row]));
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { installedIssuer } from '@volter/teams/server/apps';
|
|
1
2
|
// The harness's routes on a Teams server (company RFC 0008 §6): the session catalog,
|
|
2
3
|
// search, reads, access and correlations; exports, continuations, effects and the
|
|
3
4
|
// attachment relay; sync rules; OTLP ingest, captures and uploads. The neutral handler
|
|
@@ -424,7 +425,7 @@ async function viewBindingRoute(api,req,res,auth,ref,requestId){
|
|
|
424
425
|
if(session.custodian_id!==auth.principal_id)fail(403,'forbidden',"only the session's owner binds it to a view");
|
|
425
426
|
const body=await parseJson(req);exact(body,['issuer','room','conversation']);
|
|
426
427
|
if(typeof body.room!=='string'||!body.room||typeof body.conversation!=='string'||!body.conversation)fail(400,'invalid_request','room and conversation are required');
|
|
427
|
-
if(!api.
|
|
428
|
+
if(!await installedIssuer(api,body.issuer,'delegation.sessions',auth.team_id))fail(400,'invalid_request','this issuer is not an installed session integration');
|
|
428
429
|
const key=[auth.team_id,session.id,body.issuer,body.room,body.conversation];
|
|
429
430
|
await api.store.transaction(req.method==='POST'
|
|
430
431
|
?[{sql:'INSERT OR IGNORE INTO session_view_bindings(team_id,session_id,issuer,room,conversation,bound_by,created_at) VALUES (?,?,?,?,?,?,?)',params:[...key,auth.principal_id,api.clock.nowMs()]}]
|
package/server/index.mjs
CHANGED
|
@@ -16,12 +16,12 @@ import { createTeamsApi, bootstrapServer, maintain, normalizePublicOrigin, produ
|
|
|
16
16
|
|
|
17
17
|
function atomicJson(path,value){const tmp=`${path}.${process.pid}.${randomBytes(12).toString('hex')}.tmp`;let fd;try{fd=openSync(tmp,'wx',0o600);writeFileSync(fd,JSON.stringify(value,null,2)+'\n');closeSync(fd);fd=undefined;renameSync(tmp,path);chmodSync(path,0o600);}catch(error){if(fd!==undefined)closeSync(fd);try{unlinkSync(tmp);}catch{}throw error;}}
|
|
18
18
|
|
|
19
|
-
export function createTeamsServer({dataDir,listen={host:'127.0.0.1',port:7331},publicOrigin=null,
|
|
19
|
+
export function createTeamsServer({dataDir,listen={host:'127.0.0.1',port:7331},publicOrigin=null,clock=productionClock,ids=productionIds,initTeam,uploadLimitBytes=256*1024*1024}={}) {
|
|
20
20
|
if(!dataDir) throw new TypeError('dataDir is required');
|
|
21
21
|
const loopback=['127.0.0.1','::1','localhost'].includes(listen.host??'127.0.0.1');
|
|
22
22
|
publicOrigin=normalizePublicOrigin(publicOrigin);
|
|
23
23
|
if(!loopback&&!publicOrigin?.startsWith('https:'))throw new TypeError('non-loopback Teams servers require an HTTPS publicOrigin');
|
|
24
|
-
const api=createTeamsApi({clock,ids,publicOrigin,
|
|
24
|
+
const api=createTeamsApi({clock,ids,publicOrigin,identityOrganizations:identityOrganizationsFromEnv(process.env),uploadLimitBytes});
|
|
25
25
|
// The harness's own routes on this server (harness-routes.mjs); the handler owns the neutral ones.
|
|
26
26
|
api.attach({harnessRoutes});
|
|
27
27
|
const events=api.events;
|
|
@@ -43,6 +43,7 @@ export function createTeamsServer({dataDir,listen={host:'127.0.0.1',port:7331},p
|
|
|
43
43
|
if(!existsSync(credentialPath)&&existsSync(pendingPath)){const pending=JSON.parse(readFileSync(pendingPath,'utf8'));if(pending.server_id!==serverId)throw new Error('bootstrap credential recovery does not match server identity');renameSync(pendingPath,credentialPath);chmodSync(credentialPath,0o600);}
|
|
44
44
|
}
|
|
45
45
|
api.attach({serverId});
|
|
46
|
+
if(existsSync(credentialPath))api.appRootSecret=JSON.parse(readFileSync(credentialPath,'utf8')).token;
|
|
46
47
|
const maintenance=async()=>{
|
|
47
48
|
let next=60000;
|
|
48
49
|
try{
|
package/server/store-core.mjs
CHANGED
|
@@ -28,10 +28,15 @@ export function createStoreHandlers({ all, get, run, tx }) {
|
|
|
28
28
|
// session its owner bound to that Room and Conversation; it holds no credential and is no member.
|
|
29
29
|
let membership=null;
|
|
30
30
|
if(bound){
|
|
31
|
+
if(!get(`SELECT 1 FROM app_installations i JOIN apps a ON a.id=i.app_id JOIN memberships m ON m.team_id=i.team_id AND m.principal_id=i.principal_id JOIN memberships installer ON installer.team_id=i.team_id AND installer.principal_id=i.installer_id WHERE i.team_id=? AND a.issuer=? AND i.state='active' AND a.state='active' AND m.active=1 AND installer.active=1 AND installer.role IN ('owner','admin') AND EXISTS (SELECT 1 FROM json_each(i.actions_json) WHERE value='delegation.sessions')`,[teamId,bound.issuer]))domainError(401,'authentication_required','integration was uninstalled');
|
|
31
32
|
if(!(bound.expiresAt>now))domainError(401,'authentication_required','authentication required');
|
|
32
33
|
}else{
|
|
33
34
|
const credential=get("SELECT id FROM credentials WHERE id=? AND principal_id=? AND kind IN ('user','service') AND (team_id IS NULL OR team_id=?) AND revoked_at IS NULL AND (expires_at IS NULL OR expires_at>?)",[credentialId,principalId,teamId,now]);
|
|
34
35
|
membership=get('SELECT role FROM memberships WHERE team_id=? AND principal_id=? AND active=1',[teamId,principalId]);
|
|
36
|
+
const identity=get(`SELECT i.state,a.state app_state,m.active,m.role FROM identity_app_credentials c JOIN app_installations i ON i.id=c.installation_id JOIN apps a ON a.id=i.app_id JOIN memberships m ON m.team_id=i.team_id AND m.principal_id=i.installer_id WHERE c.credential_id=?`,[credentialId]);
|
|
37
|
+
if(identity&&(identity.state!=='active'||identity.app_state!=='active'||!identity.active||!['owner','admin'].includes(identity.role)))domainError(401,'authentication_required','identity integration revoked');
|
|
38
|
+
const app=get('SELECT ac.*,i.state,a.state app_state,installer.active,installer.role FROM app_credentials ac JOIN app_installations i ON i.id=ac.installation_id JOIN apps a ON a.id=i.app_id JOIN memberships installer ON installer.team_id=i.team_id AND installer.principal_id=i.installer_id WHERE ac.credential_id=?',[credentialId]);
|
|
39
|
+
if(app){if(app.state!=='active'||app.app_state!=='active'||!app.active||!['owner','admin'].includes(app.role)||!JSON.parse(app.actions_json).includes('sessions.read'))domainError(403,'forbidden','app session read authorization changed');const revoked=get(`WITH RECURSIVE lineage(id,parent_id) AS (SELECT credential_id,parent_id FROM app_credentials WHERE credential_id=? UNION ALL SELECT ac.credential_id,ac.parent_id FROM app_credentials ac JOIN lineage l ON ac.credential_id=l.parent_id) SELECT 1 FROM lineage l JOIN credentials c ON c.id=l.id WHERE c.revoked_at IS NOT NULL OR c.expires_at<=? LIMIT 1`,[credentialId,now]);if(revoked)domainError(401,'authentication_required','parent credential revoked');}
|
|
35
40
|
if(!credential||!membership)domainError(401,'authentication_required','authentication required');
|
|
36
41
|
}
|
|
37
42
|
if(typeof ref!=='string'||ref.length<8)domainError(404,'not_found','not found');
|
package/worker/index.mjs
CHANGED
|
@@ -5,11 +5,10 @@
|
|
|
5
5
|
// Configuration (wrangler.toml [vars] and secrets):
|
|
6
6
|
// PUBLIC_ORIGIN the https origin the Worker is served at
|
|
7
7
|
// INIT_TEAM the first team's name (used once, at bootstrap)
|
|
8
|
-
//
|
|
8
|
+
// Identity and delegation issuers are registered apps installed by team admins through the public doors.
|
|
9
9
|
// VOLTER_ORG_CLIENT_ID, VOLTER_ORG_CLIENT_SECRET, VOLTER_TEAM_ORGS
|
|
10
|
-
// optional: this server's product credential at
|
|
10
|
+
// optional: this server's product credential at the organization's identity issuer, and the teams whose
|
|
11
11
|
// organization there admits its members with no invitation (`team=slug,…`)
|
|
12
|
-
// DELEGATED_ISSUER optional: a workplace (RH2) whose delegated tokens open one pane or folder at /view
|
|
13
12
|
// SUPERCODE_TEAMS_BOOTSTRAP_TOKEN secret: the owner's first credential, minted with
|
|
14
13
|
// `supercode teams server token`
|
|
15
14
|
import { DurableObject } from 'cloudflare:workers';
|
|
@@ -81,7 +80,7 @@ export class TeamsServer extends DurableObject {
|
|
|
81
80
|
console.log(`storage: database_bytes=${ctx.storage.sql.databaseSize}`);
|
|
82
81
|
const db = durableSql(ctx.storage);
|
|
83
82
|
migrate({ exec: db.exec, tx: db.tx, version: () => ctx.storage.kv.get('schema_version') ?? 0, setVersion: (n) => ctx.storage.kv.put('schema_version', n) });
|
|
84
|
-
this.api = createTeamsApi({ publicOrigin: normalizePublicOrigin(env.PUBLIC_ORIGIN),
|
|
83
|
+
this.api = createTeamsApi({ publicOrigin: normalizePublicOrigin(env.PUBLIC_ORIGIN), appRootSecret: env.SUPERCODE_TEAMS_BOOTSTRAP_TOKEN || null, identityOrganizations: identityOrganizationsFromEnv(env) });
|
|
85
84
|
this.api.storageUsage = () => {
|
|
86
85
|
const sql = ctx.storage.sql;
|
|
87
86
|
let freeBytes = null;
|
package/worker/wrangler.toml
CHANGED
|
@@ -4,24 +4,14 @@
|
|
|
4
4
|
# --var PUBLIC_ORIGIN:https://<worker>.<account>.workers.dev --var INIT_TEAM:<first team>
|
|
5
5
|
# The token printed above is the owner's first credential (`supercode teams login <origin> --token-stdin`).
|
|
6
6
|
#
|
|
7
|
-
#
|
|
8
|
-
#
|
|
9
|
-
# PUBLIC_ORIGIN the https origin the Worker is served at (required)
|
|
10
|
-
# INIT_TEAM the first team's name, used once at bootstrap
|
|
11
|
-
# VOLTER_ISSUER optional: members sign in with Volter (supercode teams login --volter); the issuer
|
|
12
|
-
# must have this origin registered as a resource
|
|
13
|
-
# DELEGATED_ISSUER optional: a workplace (RH2) whose delegated tokens open published panes and folders and
|
|
14
|
-
# bound sessions; its keys are at <issuer>/.well-known/jwks.json
|
|
15
|
-
# DELEGATED_ISSUERS comma-separated exact origins, additive with legacy DELEGATED_ISSUER
|
|
7
|
+
# Identity and delegated access are admin-consented app installations, never operator allowlists.
|
|
8
|
+
# PUBLIC_ORIGIN and INIT_TEAM supply the listener and bootstrap team.
|
|
16
9
|
name = "supercode-teams"
|
|
17
10
|
main = "index.mjs"
|
|
18
11
|
compatibility_date = "2026-09-01"
|
|
19
12
|
compatibility_flags = ["nodejs_compat"]
|
|
20
13
|
keep_vars = true
|
|
21
14
|
|
|
22
|
-
[vars]
|
|
23
|
-
DELEGATED_ISSUERS = "https://pilot.runhuman.com,https://rh2-staging.voltertest.xyz"
|
|
24
|
-
|
|
25
15
|
# Workers Logs: the Worker's log lines (viewer-error, session-view refused, maintain) kept and searchable after the fact.
|
|
26
16
|
[observability]
|
|
27
17
|
enabled = true
|
package/workspace-client.d.ts
CHANGED
|
@@ -36,9 +36,24 @@ export declare class TeamsClient {
|
|
|
36
36
|
capabilities(options?:RequestOptions):Promise<Capabilities>;
|
|
37
37
|
teams(query?:{limit?:number;cursor?:string},options?:RequestOptions):Promise<ServerPage<Team>>;
|
|
38
38
|
createTeam(body:{name:string},options?:RequestOptions):Promise<Result<Team>>;
|
|
39
|
+
redeemAppCode(body:{app_id:string;app_secret:string;code:string;redirect_url:string},options?:RequestOptions):Promise<AppCodeExchange>;
|
|
39
40
|
team(id:string):TeamClient;
|
|
40
41
|
}
|
|
42
|
+
export interface TeamsApp {
|
|
43
|
+
id:string; team_id:string; principal_id:string; name:string; description:string;
|
|
44
|
+
actions:string[]; event_kinds:string[]; redirect_urls:string[]; webhook_url:string|null;
|
|
45
|
+
issuer:string|null; state:'active'|'retired'; created_at:number;
|
|
46
|
+
}
|
|
47
|
+
export type RegisterTeamsApp=Pick<TeamsApp,'name'|'actions'|'redirect_urls'> & Partial<Pick<TeamsApp,'description'|'event_kinds'|'webhook_url'|'issuer'>>;
|
|
48
|
+
export interface AppInstallation {
|
|
49
|
+
id:string; app_id:string; team_id:string; principal_id:string; installer_id:string;
|
|
50
|
+
actions:string[]; state:'active'|'uninstalled'; created_at:number; uninstalled_at:number|null;
|
|
51
|
+
}
|
|
52
|
+
export interface AppToken { token:string; kind:'service'; expires_at:number; actions:string[]; subject:string; act:string[] }
|
|
53
|
+
export interface AppCodeExchange { version:1; installation:AppInstallation; credential:AppToken; webhook_secret:string }
|
|
41
54
|
export interface TeamClient {
|
|
55
|
+
apps:{catalogue(options?:RequestOptions):Promise<Result<{actions:string[];event_kinds:string[]}>>;list(options?:RequestOptions):Promise<Page<TeamsApp>>;get(id:string,options?:RequestOptions):Promise<Result<TeamsApp>>;register(body:RegisterTeamsApp,options?:RequestOptions):Promise<Result<TeamsApp & {secret?:string}>>;rotateSecret(id:string,options?:RequestOptions):Promise<Result<{id:string;secret?:string}>>;install(id:string,body:{redirect_url:string;state:string;actions?:string[]},options?:RequestOptions):Promise<Result<{installation:AppInstallation;code?:string;state:string;redirect_url:string;expires_at:number}>>};
|
|
56
|
+
appInstallations:{list(options?:RequestOptions):Promise<Page<AppInstallation>>;get(id:string,options?:RequestOptions):Promise<Result<AppInstallation>>;uninstall(id:string,options?:RequestOptions):Promise<null>;delegate(id:string,body:{actions:string[];actor:string;expires_in_ms?:number},options?:RequestOptions):Promise<Result<AppToken|null>>};
|
|
42
57
|
sessions:{list(query?:ListQuery,options?:RequestOptions):Promise<Page<Descriptor>>;search(query:SearchQuery,options?:RequestOptions):Promise<SearchPage>;watch(query?:ListQuery,options?:StreamOptions):AsyncIterable<Event>;get(id:string):SessionClient};
|
|
43
58
|
members:{list(query?:Record<string,unknown>,options?:RequestOptions):Promise<Page<Membership>>;update(id:string,body:{role:Membership['role']},options?:RequestOptions):Promise<Result<Membership>>;remove(id:string,options?:RequestOptions):Promise<null>};
|
|
44
59
|
groups:{list(query?:Record<string,unknown>,options?:RequestOptions):Promise<Page<Group>>;create(body:{name:string},options?:RequestOptions):Promise<Result<Group>>;remove(id:string,options?:RequestOptions):Promise<null>;addMember(groupId:string,memberId:string,options?:RequestOptions):Promise<null>;removeMember(groupId:string,memberId:string,options?:RequestOptions):Promise<null>};
|
package/workspace-client.mjs
CHANGED
|
@@ -12,6 +12,7 @@ export class TeamsClient {
|
|
|
12
12
|
throw last;
|
|
13
13
|
}
|
|
14
14
|
me(options){return this.request('GET','/api/v1/me',options);} capabilities(options){return this.request('GET','/api/v1/capabilities',options);} team(id){return new TeamClient(this,id);}
|
|
15
|
+
redeemAppCode(body,options){return this.request('POST','/api/v1/apps/token',{...options,body});}
|
|
15
16
|
teams(queryValue={},options){return this.request('GET','/api/v1/teams'+query(queryValue),options);}
|
|
16
17
|
createTeam(body,options){return this.request('POST','/api/v1/teams',{...options,body});}
|
|
17
18
|
}
|
|
@@ -28,6 +29,18 @@ class TeamClient {
|
|
|
28
29
|
const patch = (path, body, options) => root.request('PATCH', base + path, { ...options, body });
|
|
29
30
|
const remove = (path, options) => root.request('DELETE', base + path, options);
|
|
30
31
|
const resource = (path, id) => `${path}/${segment(id)}`;
|
|
32
|
+
this.apps = {
|
|
33
|
+
catalogue: o => get('/apps/catalogue', o),
|
|
34
|
+
list: o => get('/apps', o), get: (id, o) => get(resource('/apps', id), o),
|
|
35
|
+
register: (body, o) => post('/apps', body, o),
|
|
36
|
+
rotateSecret: (id, o) => post(resource('/apps', id) + '/secret', {}, o),
|
|
37
|
+
install: (id, body, o) => post(resource('/apps', id) + '/installations', body, o),
|
|
38
|
+
};
|
|
39
|
+
this.appInstallations = {
|
|
40
|
+
list: o => get('/app-installations', o), get: (id, o) => get(resource('/app-installations', id), o),
|
|
41
|
+
uninstall: (id, o) => remove(resource('/app-installations', id), o),
|
|
42
|
+
delegate: (id, body, o) => post(resource('/app-installations', id) + '/delegations', body, o),
|
|
43
|
+
};
|
|
31
44
|
this.sessions = {
|
|
32
45
|
list: (q = {}, o) => get('/sessions' + query(q), o),
|
|
33
46
|
search: (q, o) => get('/sessions/search' + query(q), o),
|