@venturewild/workspace 0.6.3 → 0.6.5
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/LICENSE +21 -21
- package/README.md +112 -112
- package/package.json +85 -85
- package/server/bin/wild-workspace.mjs +1096 -1096
- package/server/src/account.mjs +114 -114
- package/server/src/agent-login.mjs +146 -146
- package/server/src/agent-readiness.mjs +200 -200
- package/server/src/agent.mjs +468 -468
- package/server/src/bazaar/core.mjs +974 -974
- package/server/src/bazaar/index.mjs +88 -88
- package/server/src/bazaar/mcp-server.mjs +429 -429
- package/server/src/bazaar/mock-tickup.mjs +97 -97
- package/server/src/bazaar/preview-server.mjs +95 -95
- package/server/src/bazaar/seed-recipes/customer-feedback-form/know-how.md +23 -23
- package/server/src/bazaar/seed-recipes/customer-feedback-form/recipe.json +24 -24
- package/server/src/bazaar/seed-recipes/landing-page-launch/know-how.md +29 -29
- package/server/src/bazaar/seed-recipes/landing-page-launch/recipe.json +25 -25
- package/server/src/bazaar/seed-recipes/personal-portfolio/know-how.md +21 -21
- package/server/src/bazaar/seed-recipes/personal-portfolio/recipe.json +24 -24
- package/server/src/bazaar/seed-recipes/receipt-sorter/know-how.md +31 -31
- package/server/src/bazaar/seed-recipes/receipt-sorter/recipe.json +25 -25
- package/server/src/bazaar/seed-recipes/tickup-hr-matching/know-how.md +79 -79
- package/server/src/bazaar/seed-recipes/tickup-hr-matching/recipe.json +40 -40
- package/server/src/canvas/core.mjs +446 -446
- package/server/src/canvas/index.mjs +42 -42
- package/server/src/canvas/mcp-server.mjs +253 -253
- package/server/src/canvas-rails.mjs +108 -108
- package/server/src/config.mjs +404 -404
- package/server/src/daemon-bin.mjs +110 -110
- package/server/src/daemon-supervisor.mjs +285 -285
- package/server/src/doctor.mjs +375 -375
- package/server/src/inbox.mjs +86 -86
- package/server/src/index.mjs +3332 -3332
- package/server/src/listings-rails.mjs +156 -156
- package/server/src/logpaths.mjs +98 -98
- package/server/src/observability.mjs +45 -45
- package/server/src/operator.mjs +92 -92
- package/server/src/pairing.mjs +137 -137
- package/server/src/service.mjs +515 -515
- package/server/src/session-reporter.mjs +201 -201
- package/server/src/settings.mjs +145 -145
- package/server/src/share.mjs +182 -182
- package/server/src/skills.mjs +213 -213
- package/server/src/supervisor.mjs +647 -647
- package/server/src/support-consent.mjs +133 -133
- package/server/src/sync.mjs +248 -248
- package/server/src/transcript.mjs +121 -121
- package/server/src/turn-mcp.mjs +46 -46
- package/server/src/usage.mjs +405 -405
- package/server/src/workspace-registry.mjs +295 -295
- package/server/src/workspaces.mjs +145 -145
- package/web/dist/assets/index-nEl9swiQ.js +131 -0
- package/web/dist/index.html +1 -1
- package/web/dist/assets/index-DVflHhYJ.js +0 -131
|
@@ -1,145 +1,145 @@
|
|
|
1
|
-
// WorkspaceRails — the shared-workspace membership client (multi-host step 2).
|
|
2
|
-
//
|
|
3
|
-
// A thin client over bmo-sync's POST /api/workspaces/{create,list,members/add,
|
|
4
|
-
// members/remove} (account-token self-authed, exactly like canvas-rails.mjs).
|
|
5
|
-
// The caller (owner/member) is DERIVED server-side from the account token, so a
|
|
6
|
-
// caller can only act as itself.
|
|
7
|
-
//
|
|
8
|
-
// Unlike CanvasRails (which degrades silently to a local cache), these are
|
|
9
|
-
// operator/dev affordances driven by `wild-workspace workspace …`, so the
|
|
10
|
-
// methods SURFACE the rails' error code + message instead of collapsing to a
|
|
11
|
-
// bare false — the human running the command needs to see "slug_taken" /
|
|
12
|
-
// "not_owner" / "no_such_account". Network failures still never throw: they
|
|
13
|
-
// resolve to { ok:false, status:0, code:'unreachable' }.
|
|
14
|
-
//
|
|
15
|
-
// This is the FOUNDATION client — there is no App.jsx / lobby UI yet (that's the
|
|
16
|
-
// lobby's job). The CLI is the only consumer.
|
|
17
|
-
|
|
18
|
-
const DEFAULT_TIMEOUT_MS = 5000;
|
|
19
|
-
|
|
20
|
-
export class WorkspaceRails {
|
|
21
|
-
constructor({
|
|
22
|
-
bmoSyncUrl,
|
|
23
|
-
accountToken,
|
|
24
|
-
timeoutMs = DEFAULT_TIMEOUT_MS,
|
|
25
|
-
fetchImpl = (...a) => globalThis.fetch(...a),
|
|
26
|
-
} = {}) {
|
|
27
|
-
this.bmoSyncUrl = bmoSyncUrl ? bmoSyncUrl.replace(/\/$/, '') : null;
|
|
28
|
-
this.accountToken = accountToken || null;
|
|
29
|
-
this.timeoutMs = timeoutMs;
|
|
30
|
-
this.fetchImpl = fetchImpl;
|
|
31
|
-
// Inert without a token (can't key it) or without a server URL.
|
|
32
|
-
this.capable = Boolean(this.accountToken) && Boolean(this.bmoSyncUrl);
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
/**
|
|
36
|
-
* POST to a workspaces endpoint with the account token folded in.
|
|
37
|
-
* @returns {Promise<{ok:boolean, status:number, data:object|null, code?:string, message?:string}>}
|
|
38
|
-
* ok:true → 2xx; `data` is the parsed body.
|
|
39
|
-
* ok:false → a 4xx/5xx (carries the rails' {code,message}) OR a transport
|
|
40
|
-
* failure (status 0, code 'unreachable' / 'not_configured').
|
|
41
|
-
*/
|
|
42
|
-
async _post(path, body) {
|
|
43
|
-
if (!this.capable) {
|
|
44
|
-
return { ok: false, status: 0, data: null, code: 'not_configured', message: 'not logged in / no rails URL' };
|
|
45
|
-
}
|
|
46
|
-
const ctrl = new AbortController();
|
|
47
|
-
const timer = setTimeout(() => ctrl.abort(), this.timeoutMs);
|
|
48
|
-
if (timer.unref) timer.unref();
|
|
49
|
-
try {
|
|
50
|
-
const r = await this.fetchImpl(`${this.bmoSyncUrl}${path}`, {
|
|
51
|
-
method: 'POST',
|
|
52
|
-
headers: { 'content-type': 'application/json' },
|
|
53
|
-
body: JSON.stringify({ account_token: this.accountToken, ...body }),
|
|
54
|
-
signal: ctrl.signal,
|
|
55
|
-
});
|
|
56
|
-
const data = await r.json().catch(() => null);
|
|
57
|
-
if (r.ok) return { ok: true, status: r.status, data };
|
|
58
|
-
return {
|
|
59
|
-
ok: false,
|
|
60
|
-
status: r.status,
|
|
61
|
-
data,
|
|
62
|
-
code: data?.code || `http_${r.status}`,
|
|
63
|
-
message: data?.message || `HTTP ${r.status}`,
|
|
64
|
-
};
|
|
65
|
-
} catch (e) {
|
|
66
|
-
return { ok: false, status: 0, data: null, code: 'unreachable', message: String(e?.message || e) };
|
|
67
|
-
} finally {
|
|
68
|
-
clearTimeout(timer);
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
/** Create a shared workspace. `slug` is optional (the rails auto-suggest from name). */
|
|
73
|
-
create(name, slug = null) {
|
|
74
|
-
const body = { name };
|
|
75
|
-
if (slug) body.slug = slug;
|
|
76
|
-
return this._post('/api/workspaces/create', body);
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
/** The shared workspaces this account belongs to. */
|
|
80
|
-
list() {
|
|
81
|
-
return this._post('/api/workspaces/list', {});
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
/**
|
|
85
|
-
* Add a member by email (owner-only). If the email has no account yet the
|
|
86
|
-
* rails records a PENDING invite (`data.pending === true`) instead of an
|
|
87
|
-
* active membership; the person claims it by signing in + Join.
|
|
88
|
-
*/
|
|
89
|
-
addMember(slug, email) {
|
|
90
|
-
return this._post('/api/workspaces/members/add', { slug, member_email: email });
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
/**
|
|
94
|
-
* Claim membership at Join time. Activates a pending invite addressed to this
|
|
95
|
-
* account's own VERIFIED email; a no-op for an already-active member. Returns
|
|
96
|
-
* `{ok, data:{role, name, owner_email, claimed}}` or a 403 `not_a_member`.
|
|
97
|
-
*/
|
|
98
|
-
claim(slug) {
|
|
99
|
-
return this._post('/api/workspaces/claim', { slug });
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
/**
|
|
103
|
-
* Mint a file-sync pass for a shared workspace this account belongs to
|
|
104
|
-
* (Slice 3). Returns `{ok, data:{project_code, invite_code, expires_at}}` — the
|
|
105
|
-
* daemon redeems `invite_code` to pair the folder and start replicating. The
|
|
106
|
-
* project is provisioned lazily server-side; the code is stable, the invite is
|
|
107
|
-
* one-shot (each call mints a fresh one). A non-member → 403 `not_a_member`.
|
|
108
|
-
*/
|
|
109
|
-
syncCredential(slug) {
|
|
110
|
-
return this._post('/api/workspaces/sync-credential', { slug });
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
/**
|
|
114
|
-
* The roster of a shared workspace this account belongs to (R2). Returns
|
|
115
|
-
* `{ok, data:{members:[{account_id,email,role,status,added_at}]}}` — active
|
|
116
|
-
* members + pending invites (status:'invited'). `is_active_member`-gated; a
|
|
117
|
-
* non-member → 403 `not_a_member`.
|
|
118
|
-
*/
|
|
119
|
-
members(slug) {
|
|
120
|
-
return this._post('/api/workspaces/members/list', { slug });
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
/** Remove a member by email or accountId (owner-only; the owner can't be removed). */
|
|
124
|
-
removeMember(slug, ref) {
|
|
125
|
-
const body = { slug };
|
|
126
|
-
if (ref && typeof ref === 'object') {
|
|
127
|
-
if (ref.accountId) body.account_id = ref.accountId;
|
|
128
|
-
else if (ref.email) body.member_email = ref.email;
|
|
129
|
-
} else if (typeof ref === 'string') {
|
|
130
|
-
// Heuristic: an '@' means an email, otherwise treat it as an account id.
|
|
131
|
-
if (ref.includes('@')) body.member_email = ref;
|
|
132
|
-
else body.account_id = ref;
|
|
133
|
-
}
|
|
134
|
-
return this._post('/api/workspaces/members/remove', body);
|
|
135
|
-
}
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
/** Build the client from server config + account (or an inert one when logged out). */
|
|
139
|
-
export function createWorkspaceRails(config, account, fetchImpl) {
|
|
140
|
-
return new WorkspaceRails({
|
|
141
|
-
bmoSyncUrl: config?.bmoSyncServerUrl,
|
|
142
|
-
accountToken: account?.accountToken,
|
|
143
|
-
fetchImpl,
|
|
144
|
-
});
|
|
145
|
-
}
|
|
1
|
+
// WorkspaceRails — the shared-workspace membership client (multi-host step 2).
|
|
2
|
+
//
|
|
3
|
+
// A thin client over bmo-sync's POST /api/workspaces/{create,list,members/add,
|
|
4
|
+
// members/remove} (account-token self-authed, exactly like canvas-rails.mjs).
|
|
5
|
+
// The caller (owner/member) is DERIVED server-side from the account token, so a
|
|
6
|
+
// caller can only act as itself.
|
|
7
|
+
//
|
|
8
|
+
// Unlike CanvasRails (which degrades silently to a local cache), these are
|
|
9
|
+
// operator/dev affordances driven by `wild-workspace workspace …`, so the
|
|
10
|
+
// methods SURFACE the rails' error code + message instead of collapsing to a
|
|
11
|
+
// bare false — the human running the command needs to see "slug_taken" /
|
|
12
|
+
// "not_owner" / "no_such_account". Network failures still never throw: they
|
|
13
|
+
// resolve to { ok:false, status:0, code:'unreachable' }.
|
|
14
|
+
//
|
|
15
|
+
// This is the FOUNDATION client — there is no App.jsx / lobby UI yet (that's the
|
|
16
|
+
// lobby's job). The CLI is the only consumer.
|
|
17
|
+
|
|
18
|
+
const DEFAULT_TIMEOUT_MS = 5000;
|
|
19
|
+
|
|
20
|
+
export class WorkspaceRails {
|
|
21
|
+
constructor({
|
|
22
|
+
bmoSyncUrl,
|
|
23
|
+
accountToken,
|
|
24
|
+
timeoutMs = DEFAULT_TIMEOUT_MS,
|
|
25
|
+
fetchImpl = (...a) => globalThis.fetch(...a),
|
|
26
|
+
} = {}) {
|
|
27
|
+
this.bmoSyncUrl = bmoSyncUrl ? bmoSyncUrl.replace(/\/$/, '') : null;
|
|
28
|
+
this.accountToken = accountToken || null;
|
|
29
|
+
this.timeoutMs = timeoutMs;
|
|
30
|
+
this.fetchImpl = fetchImpl;
|
|
31
|
+
// Inert without a token (can't key it) or without a server URL.
|
|
32
|
+
this.capable = Boolean(this.accountToken) && Boolean(this.bmoSyncUrl);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* POST to a workspaces endpoint with the account token folded in.
|
|
37
|
+
* @returns {Promise<{ok:boolean, status:number, data:object|null, code?:string, message?:string}>}
|
|
38
|
+
* ok:true → 2xx; `data` is the parsed body.
|
|
39
|
+
* ok:false → a 4xx/5xx (carries the rails' {code,message}) OR a transport
|
|
40
|
+
* failure (status 0, code 'unreachable' / 'not_configured').
|
|
41
|
+
*/
|
|
42
|
+
async _post(path, body) {
|
|
43
|
+
if (!this.capable) {
|
|
44
|
+
return { ok: false, status: 0, data: null, code: 'not_configured', message: 'not logged in / no rails URL' };
|
|
45
|
+
}
|
|
46
|
+
const ctrl = new AbortController();
|
|
47
|
+
const timer = setTimeout(() => ctrl.abort(), this.timeoutMs);
|
|
48
|
+
if (timer.unref) timer.unref();
|
|
49
|
+
try {
|
|
50
|
+
const r = await this.fetchImpl(`${this.bmoSyncUrl}${path}`, {
|
|
51
|
+
method: 'POST',
|
|
52
|
+
headers: { 'content-type': 'application/json' },
|
|
53
|
+
body: JSON.stringify({ account_token: this.accountToken, ...body }),
|
|
54
|
+
signal: ctrl.signal,
|
|
55
|
+
});
|
|
56
|
+
const data = await r.json().catch(() => null);
|
|
57
|
+
if (r.ok) return { ok: true, status: r.status, data };
|
|
58
|
+
return {
|
|
59
|
+
ok: false,
|
|
60
|
+
status: r.status,
|
|
61
|
+
data,
|
|
62
|
+
code: data?.code || `http_${r.status}`,
|
|
63
|
+
message: data?.message || `HTTP ${r.status}`,
|
|
64
|
+
};
|
|
65
|
+
} catch (e) {
|
|
66
|
+
return { ok: false, status: 0, data: null, code: 'unreachable', message: String(e?.message || e) };
|
|
67
|
+
} finally {
|
|
68
|
+
clearTimeout(timer);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/** Create a shared workspace. `slug` is optional (the rails auto-suggest from name). */
|
|
73
|
+
create(name, slug = null) {
|
|
74
|
+
const body = { name };
|
|
75
|
+
if (slug) body.slug = slug;
|
|
76
|
+
return this._post('/api/workspaces/create', body);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/** The shared workspaces this account belongs to. */
|
|
80
|
+
list() {
|
|
81
|
+
return this._post('/api/workspaces/list', {});
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Add a member by email (owner-only). If the email has no account yet the
|
|
86
|
+
* rails records a PENDING invite (`data.pending === true`) instead of an
|
|
87
|
+
* active membership; the person claims it by signing in + Join.
|
|
88
|
+
*/
|
|
89
|
+
addMember(slug, email) {
|
|
90
|
+
return this._post('/api/workspaces/members/add', { slug, member_email: email });
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Claim membership at Join time. Activates a pending invite addressed to this
|
|
95
|
+
* account's own VERIFIED email; a no-op for an already-active member. Returns
|
|
96
|
+
* `{ok, data:{role, name, owner_email, claimed}}` or a 403 `not_a_member`.
|
|
97
|
+
*/
|
|
98
|
+
claim(slug) {
|
|
99
|
+
return this._post('/api/workspaces/claim', { slug });
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Mint a file-sync pass for a shared workspace this account belongs to
|
|
104
|
+
* (Slice 3). Returns `{ok, data:{project_code, invite_code, expires_at}}` — the
|
|
105
|
+
* daemon redeems `invite_code` to pair the folder and start replicating. The
|
|
106
|
+
* project is provisioned lazily server-side; the code is stable, the invite is
|
|
107
|
+
* one-shot (each call mints a fresh one). A non-member → 403 `not_a_member`.
|
|
108
|
+
*/
|
|
109
|
+
syncCredential(slug) {
|
|
110
|
+
return this._post('/api/workspaces/sync-credential', { slug });
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* The roster of a shared workspace this account belongs to (R2). Returns
|
|
115
|
+
* `{ok, data:{members:[{account_id,email,role,status,added_at}]}}` — active
|
|
116
|
+
* members + pending invites (status:'invited'). `is_active_member`-gated; a
|
|
117
|
+
* non-member → 403 `not_a_member`.
|
|
118
|
+
*/
|
|
119
|
+
members(slug) {
|
|
120
|
+
return this._post('/api/workspaces/members/list', { slug });
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/** Remove a member by email or accountId (owner-only; the owner can't be removed). */
|
|
124
|
+
removeMember(slug, ref) {
|
|
125
|
+
const body = { slug };
|
|
126
|
+
if (ref && typeof ref === 'object') {
|
|
127
|
+
if (ref.accountId) body.account_id = ref.accountId;
|
|
128
|
+
else if (ref.email) body.member_email = ref.email;
|
|
129
|
+
} else if (typeof ref === 'string') {
|
|
130
|
+
// Heuristic: an '@' means an email, otherwise treat it as an account id.
|
|
131
|
+
if (ref.includes('@')) body.member_email = ref;
|
|
132
|
+
else body.account_id = ref;
|
|
133
|
+
}
|
|
134
|
+
return this._post('/api/workspaces/members/remove', body);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/** Build the client from server config + account (or an inert one when logged out). */
|
|
139
|
+
export function createWorkspaceRails(config, account, fetchImpl) {
|
|
140
|
+
return new WorkspaceRails({
|
|
141
|
+
bmoSyncUrl: config?.bmoSyncServerUrl,
|
|
142
|
+
accountToken: account?.accountToken,
|
|
143
|
+
fetchImpl,
|
|
144
|
+
});
|
|
145
|
+
}
|