@principal-ai/principal-studio-cli 0.35.3
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 +172 -0
- package/dist/commands/agent-session.d.ts +3 -0
- package/dist/commands/agent-session.d.ts.map +1 -0
- package/dist/commands/agent-session.js +67 -0
- package/dist/commands/agent-sessions.d.ts +11 -0
- package/dist/commands/agent-sessions.d.ts.map +1 -0
- package/dist/commands/agent-sessions.js +39 -0
- package/dist/commands/auxiliary/index.d.ts +3 -0
- package/dist/commands/auxiliary/index.d.ts.map +1 -0
- package/dist/commands/auxiliary/index.js +9 -0
- package/dist/commands/auxiliary/validate.d.ts +3 -0
- package/dist/commands/auxiliary/validate.d.ts.map +1 -0
- package/dist/commands/auxiliary/validate.js +123 -0
- package/dist/commands/collector/check.d.ts +6 -0
- package/dist/commands/collector/check.d.ts.map +1 -0
- package/dist/commands/collector/check.js +153 -0
- package/dist/commands/collector/diagnose.d.ts +6 -0
- package/dist/commands/collector/diagnose.d.ts.map +1 -0
- package/dist/commands/collector/diagnose.js +229 -0
- package/dist/commands/collector/index.d.ts +6 -0
- package/dist/commands/collector/index.d.ts.map +1 -0
- package/dist/commands/collector/index.js +16 -0
- package/dist/commands/collector/status.d.ts +6 -0
- package/dist/commands/collector/status.d.ts.map +1 -0
- package/dist/commands/collector/status.js +77 -0
- package/dist/commands/collector/utils.d.ts +92 -0
- package/dist/commands/collector/utils.d.ts.map +1 -0
- package/dist/commands/collector/utils.js +154 -0
- package/dist/commands/coverage.d.ts +9 -0
- package/dist/commands/coverage.d.ts.map +1 -0
- package/dist/commands/coverage.js +252 -0
- package/dist/commands/create.d.ts +6 -0
- package/dist/commands/create.d.ts.map +1 -0
- package/dist/commands/create.js +50 -0
- package/dist/commands/events/index.d.ts +3 -0
- package/dist/commands/events/index.d.ts.map +1 -0
- package/dist/commands/events/index.js +9 -0
- package/dist/commands/events/validate.d.ts +3 -0
- package/dist/commands/events/validate.d.ts.map +1 -0
- package/dist/commands/events/validate.js +124 -0
- package/dist/commands/formats.d.ts +6 -0
- package/dist/commands/formats.d.ts.map +1 -0
- package/dist/commands/formats.js +1118 -0
- package/dist/commands/hooks.d.ts +9 -0
- package/dist/commands/hooks.d.ts.map +1 -0
- package/dist/commands/hooks.js +295 -0
- package/dist/commands/inbox.d.ts +13 -0
- package/dist/commands/inbox.d.ts.map +1 -0
- package/dist/commands/inbox.js +179 -0
- package/dist/commands/init.d.ts +6 -0
- package/dist/commands/init.d.ts.map +1 -0
- package/dist/commands/init.js +220 -0
- package/dist/commands/lint.d.ts +11 -0
- package/dist/commands/lint.d.ts.map +1 -0
- package/dist/commands/lint.js +71 -0
- package/dist/commands/list.d.ts +6 -0
- package/dist/commands/list.d.ts.map +1 -0
- package/dist/commands/list.js +78 -0
- package/dist/commands/migrate-nodes.d.ts +9 -0
- package/dist/commands/migrate-nodes.d.ts.map +1 -0
- package/dist/commands/migrate-nodes.js +363 -0
- package/dist/commands/migrate-scopes-to-canvas.d.ts +3 -0
- package/dist/commands/migrate-scopes-to-canvas.d.ts.map +1 -0
- package/dist/commands/migrate-scopes-to-canvas.js +143 -0
- package/dist/commands/migration.d.ts +6 -0
- package/dist/commands/migration.d.ts.map +1 -0
- package/dist/commands/migration.js +247 -0
- package/dist/commands/open-studio.d.ts +10 -0
- package/dist/commands/open-studio.d.ts.map +1 -0
- package/dist/commands/open-studio.js +32 -0
- package/dist/commands/opencode/fetch.d.ts +3 -0
- package/dist/commands/opencode/fetch.d.ts.map +1 -0
- package/dist/commands/opencode/fetch.js +25 -0
- package/dist/commands/opencode/index.d.ts +3 -0
- package/dist/commands/opencode/index.d.ts.map +1 -0
- package/dist/commands/opencode/index.js +11 -0
- package/dist/commands/opencode/list-sessions.d.ts +3 -0
- package/dist/commands/opencode/list-sessions.d.ts.map +1 -0
- package/dist/commands/opencode/list-sessions.js +20 -0
- package/dist/commands/repo.d.ts +18 -0
- package/dist/commands/repo.d.ts.map +1 -0
- package/dist/commands/repo.js +233 -0
- package/dist/commands/schema.d.ts +6 -0
- package/dist/commands/schema.d.ts.map +1 -0
- package/dist/commands/schema.js +367 -0
- package/dist/commands/scopes/index.d.ts +3 -0
- package/dist/commands/scopes/index.d.ts.map +1 -0
- package/dist/commands/scopes/index.js +9 -0
- package/dist/commands/scopes/validate.d.ts +3 -0
- package/dist/commands/scopes/validate.d.ts.map +1 -0
- package/dist/commands/scopes/validate.js +187 -0
- package/dist/commands/setup.d.ts +6 -0
- package/dist/commands/setup.d.ts.map +1 -0
- package/dist/commands/setup.js +406 -0
- package/dist/commands/starred-collections.d.ts +12 -0
- package/dist/commands/starred-collections.d.ts.map +1 -0
- package/dist/commands/starred-collections.js +310 -0
- package/dist/commands/subsystem-model.d.ts +11 -0
- package/dist/commands/subsystem-model.d.ts.map +1 -0
- package/dist/commands/subsystem-model.js +249 -0
- package/dist/commands/topic.d.ts +15 -0
- package/dist/commands/topic.d.ts.map +1 -0
- package/dist/commands/topic.js +683 -0
- package/dist/commands/tour.d.ts +23 -0
- package/dist/commands/tour.d.ts.map +1 -0
- package/dist/commands/tour.js +783 -0
- package/dist/commands/trace/flow.d.ts +6 -0
- package/dist/commands/trace/flow.d.ts.map +1 -0
- package/dist/commands/trace/flow.js +158 -0
- package/dist/commands/trace/index.d.ts +6 -0
- package/dist/commands/trace/index.d.ts.map +1 -0
- package/dist/commands/trace/index.js +20 -0
- package/dist/commands/trace/inspect.d.ts +6 -0
- package/dist/commands/trace/inspect.d.ts.map +1 -0
- package/dist/commands/trace/inspect.js +187 -0
- package/dist/commands/trace/list.d.ts +6 -0
- package/dist/commands/trace/list.d.ts.map +1 -0
- package/dist/commands/trace/list.js +145 -0
- package/dist/commands/trace/registrations.d.ts +6 -0
- package/dist/commands/trace/registrations.d.ts.map +1 -0
- package/dist/commands/trace/registrations.js +81 -0
- package/dist/commands/trace/validate.d.ts +6 -0
- package/dist/commands/trace/validate.d.ts.map +1 -0
- package/dist/commands/trace/validate.js +276 -0
- package/dist/commands/trail.d.ts +36 -0
- package/dist/commands/trail.d.ts.map +1 -0
- package/dist/commands/trail.js +622 -0
- package/dist/commands/validate.d.ts +12 -0
- package/dist/commands/validate.d.ts.map +1 -0
- package/dist/commands/validate.js +3334 -0
- package/dist/commands/workflow/implementation.d.ts +3 -0
- package/dist/commands/workflow/implementation.d.ts.map +1 -0
- package/dist/commands/workflow/implementation.js +51 -0
- package/dist/commands/workflow/index.d.ts +3 -0
- package/dist/commands/workflow/index.d.ts.map +1 -0
- package/dist/commands/workflow/index.js +19 -0
- package/dist/commands/workflow/inspect.d.ts +3 -0
- package/dist/commands/workflow/inspect.d.ts.map +1 -0
- package/dist/commands/workflow/inspect.js +123 -0
- package/dist/commands/workflow/list.d.ts +3 -0
- package/dist/commands/workflow/list.d.ts.map +1 -0
- package/dist/commands/workflow/list.js +95 -0
- package/dist/commands/workflow/render.d.ts +3 -0
- package/dist/commands/workflow/render.d.ts.map +1 -0
- package/dist/commands/workflow/render.js +88 -0
- package/dist/commands/workflow/test.d.ts +3 -0
- package/dist/commands/workflow/test.d.ts.map +1 -0
- package/dist/commands/workflow/test.js +141 -0
- package/dist/commands/workflow/utils.d.ts +49 -0
- package/dist/commands/workflow/utils.d.ts.map +1 -0
- package/dist/commands/workflow/utils.js +164 -0
- package/dist/commands/workflow/validate.d.ts +3 -0
- package/dist/commands/workflow/validate.d.ts.map +1 -0
- package/dist/commands/workflow/validate.js +344 -0
- package/dist/file-utils.d.ts +15 -0
- package/dist/file-utils.d.ts.map +1 -0
- package/dist/file-utils.js +29 -0
- package/dist/index.cjs +285718 -0
- package/dist/index.cjs.map +7 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +81 -0
- package/dist/lib/bridge-ipc.d.ts +29 -0
- package/dist/lib/bridge-ipc.d.ts.map +1 -0
- package/dist/lib/bridge-ipc.js +128 -0
- package/dist/lib/github-user.d.ts +17 -0
- package/dist/lib/github-user.d.ts.map +1 -0
- package/dist/lib/github-user.js +53 -0
- package/dist/lib/open-url.d.ts +11 -0
- package/dist/lib/open-url.d.ts.map +1 -0
- package/dist/lib/open-url.js +45 -0
- package/dist/lib/subsystem-model-store.d.ts +74 -0
- package/dist/lib/subsystem-model-store.d.ts.map +1 -0
- package/dist/lib/subsystem-model-store.js +292 -0
- package/dist/lib/tour-cache.d.ts +26 -0
- package/dist/lib/tour-cache.d.ts.map +1 -0
- package/dist/lib/tour-cache.js +46 -0
- package/dist/lib/trail-cache.d.ts +40 -0
- package/dist/lib/trail-cache.d.ts.map +1 -0
- package/dist/lib/trail-cache.js +97 -0
- package/dist/lib/viewer-ipc.d.ts +44 -0
- package/dist/lib/viewer-ipc.d.ts.map +1 -0
- package/dist/lib/viewer-ipc.js +68 -0
- package/package.json +68 -0
|
@@ -0,0 +1,310 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Starred-collections command — manage starred-repo collections on web-ade.
|
|
3
|
+
*
|
|
4
|
+
* Collections are per-user curated groups of repos (with cached descriptions,
|
|
5
|
+
* star counts, and avatars). Each collection is owned by a user or an org.
|
|
6
|
+
*
|
|
7
|
+
* Resolves a GitHub token locally (gh CLI → git credential helper) and calls
|
|
8
|
+
* the web-ade starred-collections API with `Authorization: Bearer <token>`.
|
|
9
|
+
*/
|
|
10
|
+
import { Command } from 'commander';
|
|
11
|
+
import { spawnSync } from 'node:child_process';
|
|
12
|
+
const BASE_URL = process.env.PRINCIPAL_ADE_BASE_URL?.replace(/\/+$/, '') ||
|
|
13
|
+
'https://app.principal-ade.com';
|
|
14
|
+
// ============================================================================
|
|
15
|
+
// Auth
|
|
16
|
+
// ============================================================================
|
|
17
|
+
function resolveTokenViaGh() {
|
|
18
|
+
const result = spawnSync('gh', ['auth', 'token'], {
|
|
19
|
+
encoding: 'utf8',
|
|
20
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
21
|
+
});
|
|
22
|
+
if (result.status === 0 && result.stdout) {
|
|
23
|
+
const token = result.stdout.trim();
|
|
24
|
+
if (token)
|
|
25
|
+
return token;
|
|
26
|
+
}
|
|
27
|
+
return null;
|
|
28
|
+
}
|
|
29
|
+
function resolveTokenViaGitCredential() {
|
|
30
|
+
const result = spawnSync('git', ['credential', 'fill'], {
|
|
31
|
+
encoding: 'utf8',
|
|
32
|
+
input: 'protocol=https\nhost=github.com\n\n',
|
|
33
|
+
stdio: ['pipe', 'pipe', 'ignore'],
|
|
34
|
+
});
|
|
35
|
+
if (result.status !== 0 || !result.stdout)
|
|
36
|
+
return null;
|
|
37
|
+
for (const line of result.stdout.split('\n')) {
|
|
38
|
+
if (line.startsWith('password=')) {
|
|
39
|
+
const token = line.slice('password='.length).trim();
|
|
40
|
+
if (token)
|
|
41
|
+
return token;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
return null;
|
|
45
|
+
}
|
|
46
|
+
function resolveToken() {
|
|
47
|
+
return resolveTokenViaGh() ?? resolveTokenViaGitCredential();
|
|
48
|
+
}
|
|
49
|
+
function exitWithTokenError() {
|
|
50
|
+
process.stderr.write('Could not resolve a GitHub token. Run `gh auth login`, or configure a git credential helper for github.com.\n');
|
|
51
|
+
process.exit(2);
|
|
52
|
+
}
|
|
53
|
+
// ============================================================================
|
|
54
|
+
// HTTP helpers
|
|
55
|
+
// ============================================================================
|
|
56
|
+
async function describeHttpError(response) {
|
|
57
|
+
let serverMessage = '';
|
|
58
|
+
let code = '';
|
|
59
|
+
try {
|
|
60
|
+
const body = (await response.clone().json());
|
|
61
|
+
serverMessage = body.error ?? '';
|
|
62
|
+
code = body.code ?? '';
|
|
63
|
+
}
|
|
64
|
+
catch {
|
|
65
|
+
// body wasn't JSON — fall through to status-only message
|
|
66
|
+
}
|
|
67
|
+
const fallback = response.status === 404
|
|
68
|
+
? 'Collection not found'
|
|
69
|
+
: response.status === 403
|
|
70
|
+
? 'Not authorized'
|
|
71
|
+
: response.status === 401
|
|
72
|
+
? 'GitHub token rejected'
|
|
73
|
+
: response.status === 409
|
|
74
|
+
? 'Conflict (duplicate name or repo)'
|
|
75
|
+
: `HTTP ${response.status}`;
|
|
76
|
+
const human = serverMessage || fallback;
|
|
77
|
+
return `${human}${code ? ` [${code}]` : ''}`;
|
|
78
|
+
}
|
|
79
|
+
// ============================================================================
|
|
80
|
+
// Subcommands
|
|
81
|
+
// ============================================================================
|
|
82
|
+
async function listCollections() {
|
|
83
|
+
const token = resolveToken();
|
|
84
|
+
if (!token)
|
|
85
|
+
exitWithTokenError();
|
|
86
|
+
let response;
|
|
87
|
+
try {
|
|
88
|
+
response = await fetch(`${BASE_URL}/api/starred-collections?include_items=false`, {
|
|
89
|
+
headers: {
|
|
90
|
+
Authorization: `Bearer ${token}`,
|
|
91
|
+
Accept: 'application/json',
|
|
92
|
+
},
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
catch (err) {
|
|
96
|
+
process.stderr.write(`Network error listing collections: ${err.message}\n`);
|
|
97
|
+
process.exit(1);
|
|
98
|
+
}
|
|
99
|
+
if (!response.ok) {
|
|
100
|
+
process.stderr.write(`${await describeHttpError(response)}\n`);
|
|
101
|
+
process.exit(1);
|
|
102
|
+
}
|
|
103
|
+
const body = (await response.json());
|
|
104
|
+
process.stdout.write(JSON.stringify(body.collections, null, 2));
|
|
105
|
+
process.stdout.write('\n');
|
|
106
|
+
}
|
|
107
|
+
async function createCollection(name, options) {
|
|
108
|
+
const token = resolveToken();
|
|
109
|
+
if (!token)
|
|
110
|
+
exitWithTokenError();
|
|
111
|
+
const payload = { name };
|
|
112
|
+
if (options.description)
|
|
113
|
+
payload.description = options.description;
|
|
114
|
+
if (options.icon)
|
|
115
|
+
payload.icon = options.icon;
|
|
116
|
+
if (options.orgLogin)
|
|
117
|
+
payload.orgLogin = options.orgLogin;
|
|
118
|
+
let response;
|
|
119
|
+
try {
|
|
120
|
+
response = await fetch(`${BASE_URL}/api/starred-collections`, {
|
|
121
|
+
method: 'POST',
|
|
122
|
+
headers: {
|
|
123
|
+
Authorization: `Bearer ${token}`,
|
|
124
|
+
'Content-Type': 'application/json',
|
|
125
|
+
Accept: 'application/json',
|
|
126
|
+
},
|
|
127
|
+
body: JSON.stringify(payload),
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
catch (err) {
|
|
131
|
+
process.stderr.write(`Network error creating collection: ${err.message}\n`);
|
|
132
|
+
process.exit(1);
|
|
133
|
+
}
|
|
134
|
+
if (!response.ok) {
|
|
135
|
+
process.stderr.write(`${await describeHttpError(response)}\n`);
|
|
136
|
+
process.exit(1);
|
|
137
|
+
}
|
|
138
|
+
const collection = (await response.json());
|
|
139
|
+
process.stdout.write(JSON.stringify(collection, null, 2));
|
|
140
|
+
process.stdout.write('\n');
|
|
141
|
+
}
|
|
142
|
+
async function listRepos(collectionId) {
|
|
143
|
+
const token = resolveToken();
|
|
144
|
+
if (!token)
|
|
145
|
+
exitWithTokenError();
|
|
146
|
+
let response;
|
|
147
|
+
try {
|
|
148
|
+
response = await fetch(`${BASE_URL}/api/starred-collections/${encodeURIComponent(collectionId)}`, {
|
|
149
|
+
headers: {
|
|
150
|
+
Authorization: `Bearer ${token}`,
|
|
151
|
+
Accept: 'application/json',
|
|
152
|
+
},
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
catch (err) {
|
|
156
|
+
process.stderr.write(`Network error fetching collection: ${err.message}\n`);
|
|
157
|
+
process.exit(1);
|
|
158
|
+
}
|
|
159
|
+
if (!response.ok) {
|
|
160
|
+
process.stderr.write(`${await describeHttpError(response)}\n`);
|
|
161
|
+
process.exit(1);
|
|
162
|
+
}
|
|
163
|
+
const collection = (await response.json());
|
|
164
|
+
process.stdout.write(JSON.stringify(collection.repos, null, 2));
|
|
165
|
+
process.stdout.write('\n');
|
|
166
|
+
}
|
|
167
|
+
async function addRepo(collectionId, ownerRepo) {
|
|
168
|
+
const slashIndex = ownerRepo.indexOf('/');
|
|
169
|
+
if (slashIndex === -1 || slashIndex === 0 || slashIndex === ownerRepo.length - 1) {
|
|
170
|
+
process.stderr.write(`Invalid repo format: "${ownerRepo}". Use <owner>/<repo> (e.g. "principal-ai/web-ade").\n`);
|
|
171
|
+
process.exit(2);
|
|
172
|
+
}
|
|
173
|
+
const owner = ownerRepo.slice(0, slashIndex);
|
|
174
|
+
const repo = ownerRepo.slice(slashIndex + 1);
|
|
175
|
+
const token = resolveToken();
|
|
176
|
+
if (!token)
|
|
177
|
+
exitWithTokenError();
|
|
178
|
+
let response;
|
|
179
|
+
try {
|
|
180
|
+
response = await fetch(`${BASE_URL}/api/starred-collections/${encodeURIComponent(collectionId)}/repos`, {
|
|
181
|
+
method: 'POST',
|
|
182
|
+
headers: {
|
|
183
|
+
Authorization: `Bearer ${token}`,
|
|
184
|
+
'Content-Type': 'application/json',
|
|
185
|
+
Accept: 'application/json',
|
|
186
|
+
},
|
|
187
|
+
body: JSON.stringify({ owner, repo }),
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
catch (err) {
|
|
191
|
+
process.stderr.write(`Network error adding repo: ${err.message}\n`);
|
|
192
|
+
process.exit(1);
|
|
193
|
+
}
|
|
194
|
+
if (!response.ok) {
|
|
195
|
+
process.stderr.write(`${await describeHttpError(response)}\n`);
|
|
196
|
+
process.exit(1);
|
|
197
|
+
}
|
|
198
|
+
const repoEntry = (await response.json());
|
|
199
|
+
process.stdout.write(JSON.stringify(repoEntry, null, 2));
|
|
200
|
+
process.stdout.write('\n');
|
|
201
|
+
}
|
|
202
|
+
async function removeRepo(collectionId, ownerRepo) {
|
|
203
|
+
const slashIndex = ownerRepo.indexOf('/');
|
|
204
|
+
if (slashIndex === -1 || slashIndex === 0 || slashIndex === ownerRepo.length - 1) {
|
|
205
|
+
process.stderr.write(`Invalid repo format: "${ownerRepo}". Use <owner>/<repo> (e.g. "principal-ai/web-ade").\n`);
|
|
206
|
+
process.exit(2);
|
|
207
|
+
}
|
|
208
|
+
const owner = ownerRepo.slice(0, slashIndex);
|
|
209
|
+
const repo = ownerRepo.slice(slashIndex + 1);
|
|
210
|
+
const token = resolveToken();
|
|
211
|
+
if (!token)
|
|
212
|
+
exitWithTokenError();
|
|
213
|
+
let response;
|
|
214
|
+
try {
|
|
215
|
+
response = await fetch(`${BASE_URL}/api/starred-collections/${encodeURIComponent(collectionId)}/repos/${encodeURIComponent(owner)}/${encodeURIComponent(repo)}`, {
|
|
216
|
+
method: 'DELETE',
|
|
217
|
+
headers: {
|
|
218
|
+
Authorization: `Bearer ${token}`,
|
|
219
|
+
Accept: 'application/json',
|
|
220
|
+
},
|
|
221
|
+
});
|
|
222
|
+
}
|
|
223
|
+
catch (err) {
|
|
224
|
+
process.stderr.write(`Network error removing repo: ${err.message}\n`);
|
|
225
|
+
process.exit(1);
|
|
226
|
+
}
|
|
227
|
+
if (!response.ok && response.status !== 204) {
|
|
228
|
+
process.stderr.write(`${await describeHttpError(response)}\n`);
|
|
229
|
+
process.exit(1);
|
|
230
|
+
}
|
|
231
|
+
process.stdout.write(`Removed ${owner}/${repo} from collection ${collectionId}\n`);
|
|
232
|
+
}
|
|
233
|
+
async function deleteCollection(collectionId) {
|
|
234
|
+
const token = resolveToken();
|
|
235
|
+
if (!token)
|
|
236
|
+
exitWithTokenError();
|
|
237
|
+
let response;
|
|
238
|
+
try {
|
|
239
|
+
response = await fetch(`${BASE_URL}/api/starred-collections/${encodeURIComponent(collectionId)}`, {
|
|
240
|
+
method: 'DELETE',
|
|
241
|
+
headers: {
|
|
242
|
+
Authorization: `Bearer ${token}`,
|
|
243
|
+
Accept: 'application/json',
|
|
244
|
+
},
|
|
245
|
+
});
|
|
246
|
+
}
|
|
247
|
+
catch (err) {
|
|
248
|
+
process.stderr.write(`Network error deleting collection: ${err.message}\n`);
|
|
249
|
+
process.exit(1);
|
|
250
|
+
}
|
|
251
|
+
if (!response.ok && response.status !== 204) {
|
|
252
|
+
process.stderr.write(`${await describeHttpError(response)}\n`);
|
|
253
|
+
process.exit(1);
|
|
254
|
+
}
|
|
255
|
+
process.stdout.write(`Deleted collection ${collectionId}\n`);
|
|
256
|
+
}
|
|
257
|
+
// ============================================================================
|
|
258
|
+
// Command wiring
|
|
259
|
+
// ============================================================================
|
|
260
|
+
export function createStarredCollectionsCommand() {
|
|
261
|
+
const command = new Command('starred-collections');
|
|
262
|
+
command.description('Manage starred-repo collections on web-ade');
|
|
263
|
+
command
|
|
264
|
+
.command('list')
|
|
265
|
+
.description('List all collections for the authenticated user (including org collections)')
|
|
266
|
+
.action(async () => {
|
|
267
|
+
await listCollections();
|
|
268
|
+
});
|
|
269
|
+
command
|
|
270
|
+
.command('create')
|
|
271
|
+
.description('Create a new collection')
|
|
272
|
+
.argument('<name>', 'Collection name (1-100 chars)')
|
|
273
|
+
.option('--description <text>', 'Optional description')
|
|
274
|
+
.option('--icon <name>', 'Lucide icon name (e.g. Star, Sparkles, Rocket, Heart)')
|
|
275
|
+
.option('--org-login <login>', 'Create as an org-owned collection (must be a member)')
|
|
276
|
+
.action(async (name, options) => {
|
|
277
|
+
await createCollection(name, options);
|
|
278
|
+
});
|
|
279
|
+
command
|
|
280
|
+
.command('repos')
|
|
281
|
+
.description('List all repos in a collection')
|
|
282
|
+
.argument('<collection-id>', 'Collection id')
|
|
283
|
+
.action(async (collectionId) => {
|
|
284
|
+
await listRepos(collectionId);
|
|
285
|
+
});
|
|
286
|
+
command
|
|
287
|
+
.command('add')
|
|
288
|
+
.description('Add a repo to a collection (auto-fetches description, stars, avatar from GitHub)')
|
|
289
|
+
.argument('<collection-id>', 'Collection id')
|
|
290
|
+
.argument('<owner/repo>', 'Repo in owner/repo format (e.g. principal-ai/web-ade)')
|
|
291
|
+
.action(async (collectionId, ownerRepo) => {
|
|
292
|
+
await addRepo(collectionId, ownerRepo);
|
|
293
|
+
});
|
|
294
|
+
command
|
|
295
|
+
.command('remove')
|
|
296
|
+
.description('Remove a repo from a collection')
|
|
297
|
+
.argument('<collection-id>', 'Collection id')
|
|
298
|
+
.argument('<owner/repo>', 'Repo in owner/repo format (e.g. principal-ai/web-ade)')
|
|
299
|
+
.action(async (collectionId, ownerRepo) => {
|
|
300
|
+
await removeRepo(collectionId, ownerRepo);
|
|
301
|
+
});
|
|
302
|
+
command
|
|
303
|
+
.command('delete')
|
|
304
|
+
.description('Delete a collection')
|
|
305
|
+
.argument('<collection-id>', 'Collection id')
|
|
306
|
+
.action(async (collectionId) => {
|
|
307
|
+
await deleteCollection(collectionId);
|
|
308
|
+
});
|
|
309
|
+
return command;
|
|
310
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `principal-ai subsystem-model` — create / open / list subsystem models
|
|
3
|
+
* without requiring Principal Studio's HTTP bridge to already be running.
|
|
4
|
+
*
|
|
5
|
+
* Create validates + persists to `~/.principal/subsystem-models/` (same store
|
|
6
|
+
* Studio uses), then opens the model: IPC handoff if Studio is up, otherwise
|
|
7
|
+
* launches Studio with `SUBSYSTEM_MODEL_ID` so it opens the new tab on boot.
|
|
8
|
+
*/
|
|
9
|
+
import { Command } from 'commander';
|
|
10
|
+
export declare function createSubsystemModelCommand(): Command;
|
|
11
|
+
//# sourceMappingURL=subsystem-model.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"subsystem-model.d.ts","sourceRoot":"","sources":["../../src/commands/subsystem-model.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAIH,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AA4QpC,wBAAgB,2BAA2B,IAAI,OAAO,CAwCrD"}
|
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `principal-ai subsystem-model` — create / open / list subsystem models
|
|
3
|
+
* without requiring Principal Studio's HTTP bridge to already be running.
|
|
4
|
+
*
|
|
5
|
+
* Create validates + persists to `~/.principal/subsystem-models/` (same store
|
|
6
|
+
* Studio uses), then opens the model: IPC handoff if Studio is up, otherwise
|
|
7
|
+
* launches Studio with `SUBSYSTEM_MODEL_ID` so it opens the new tab on boot.
|
|
8
|
+
*/
|
|
9
|
+
import { spawn } from 'node:child_process';
|
|
10
|
+
import { readFileSync } from 'node:fs';
|
|
11
|
+
import { Command } from 'commander';
|
|
12
|
+
import { tryResolveViewerLaunch } from './trail.js';
|
|
13
|
+
import { handoffToRunning } from '../lib/viewer-ipc.js';
|
|
14
|
+
import { createSubsystemModel, findCreateProblems, getSubsystemModel, isRepoRoots, listSubsystemModels, subsystemModelFilePath, } from '../lib/subsystem-model-store.js';
|
|
15
|
+
async function readPayloadFromStdin() {
|
|
16
|
+
const chunks = [];
|
|
17
|
+
for await (const chunk of process.stdin) {
|
|
18
|
+
chunks.push(typeof chunk === 'string' ? Buffer.from(chunk) : chunk);
|
|
19
|
+
}
|
|
20
|
+
return Buffer.concat(chunks).toString('utf8');
|
|
21
|
+
}
|
|
22
|
+
async function readPayload(file) {
|
|
23
|
+
const useStdin = !file || file === '-';
|
|
24
|
+
const raw = useStdin ? await readPayloadFromStdin() : readFileSync(file, 'utf8');
|
|
25
|
+
if (!raw.trim()) {
|
|
26
|
+
process.stderr.write('Empty payload\n');
|
|
27
|
+
process.exit(2);
|
|
28
|
+
}
|
|
29
|
+
try {
|
|
30
|
+
return JSON.parse(raw);
|
|
31
|
+
}
|
|
32
|
+
catch (err) {
|
|
33
|
+
process.stderr.write(`Could not parse payload as JSON: ${err.message}\n`);
|
|
34
|
+
process.exit(2);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
function studioHttpBase() {
|
|
38
|
+
const port = process.env['PRINCIPAL_STUDIO_HTTP_PORT'] ?? '3045';
|
|
39
|
+
return `http://127.0.0.1:${port}`;
|
|
40
|
+
}
|
|
41
|
+
/** True when Studio's HTTP bridge answers /health. */
|
|
42
|
+
async function studioHttpUp() {
|
|
43
|
+
try {
|
|
44
|
+
const res = await fetch(`${studioHttpBase()}/health`, {
|
|
45
|
+
signal: AbortSignal.timeout(400),
|
|
46
|
+
});
|
|
47
|
+
return res.ok;
|
|
48
|
+
}
|
|
49
|
+
catch {
|
|
50
|
+
return false;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Prefer Studio's HTTP create when the bridge is up — that path runs full
|
|
55
|
+
* file/symbol verification. Falls back to null when unreachable so the CLI
|
|
56
|
+
* can persist locally.
|
|
57
|
+
*/
|
|
58
|
+
async function createViaHttp(body) {
|
|
59
|
+
try {
|
|
60
|
+
const res = await fetch(`${studioHttpBase()}/api/subsystem-model`, {
|
|
61
|
+
method: 'POST',
|
|
62
|
+
headers: { 'Content-Type': 'application/json' },
|
|
63
|
+
body: JSON.stringify(body),
|
|
64
|
+
signal: AbortSignal.timeout(30_000),
|
|
65
|
+
});
|
|
66
|
+
const json = (await res.json());
|
|
67
|
+
if (!res.ok || !json.ok || !json.graph) {
|
|
68
|
+
process.stderr.write(`Studio rejected model: ${json.error ?? `HTTP ${res.status}`}\n`);
|
|
69
|
+
process.exit(2);
|
|
70
|
+
}
|
|
71
|
+
return json.graph;
|
|
72
|
+
}
|
|
73
|
+
catch {
|
|
74
|
+
return null;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
async function openViaHttp(id) {
|
|
78
|
+
try {
|
|
79
|
+
const res = await fetch(`${studioHttpBase()}/api/subsystem-model/open`, {
|
|
80
|
+
method: 'POST',
|
|
81
|
+
headers: { 'Content-Type': 'application/json' },
|
|
82
|
+
body: JSON.stringify({ id }),
|
|
83
|
+
signal: AbortSignal.timeout(5_000),
|
|
84
|
+
});
|
|
85
|
+
const json = (await res.json());
|
|
86
|
+
return res.ok && json.ok === true;
|
|
87
|
+
}
|
|
88
|
+
catch {
|
|
89
|
+
return false;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Open a stored model in Studio. Order: HTTP /open → IPC → spawn Studio.
|
|
94
|
+
* Returns true when handed off to a running instance (caller can exit).
|
|
95
|
+
* Returns false when it spawned Studio (caller should wait on the child).
|
|
96
|
+
*/
|
|
97
|
+
async function openModel(id, options) {
|
|
98
|
+
if (await openViaHttp(id))
|
|
99
|
+
return 'handed';
|
|
100
|
+
if (await handoffToRunning({ kind: 'LOAD_SUBSYSTEM_GRAPH', graphId: id })) {
|
|
101
|
+
return 'handed';
|
|
102
|
+
}
|
|
103
|
+
const resolved = tryResolveViewerLaunch(options.viewerDir);
|
|
104
|
+
if (!resolved.ok) {
|
|
105
|
+
process.stderr.write(`${resolved.error}\n`);
|
|
106
|
+
return 'failed';
|
|
107
|
+
}
|
|
108
|
+
const launch = resolved.launch;
|
|
109
|
+
const env = {
|
|
110
|
+
...process.env,
|
|
111
|
+
SUBSYSTEM_MODEL_ID: id,
|
|
112
|
+
};
|
|
113
|
+
process.stderr.write(`Launching Principal Studio for ${id}…\n`);
|
|
114
|
+
const child = launch.kind === 'installed'
|
|
115
|
+
? spawn(launch.bin, [], { env, stdio: 'inherit' })
|
|
116
|
+
: spawn('bun', ['start'], { cwd: launch.dir, env, stdio: 'inherit' });
|
|
117
|
+
if (!options.waitOnSpawn) {
|
|
118
|
+
child.unref();
|
|
119
|
+
return 'spawned';
|
|
120
|
+
}
|
|
121
|
+
return await new Promise((resolve) => {
|
|
122
|
+
child.on('error', (err) => {
|
|
123
|
+
process.stderr.write(`Failed to launch Principal Studio: ${err.message}\n`);
|
|
124
|
+
resolve('failed');
|
|
125
|
+
});
|
|
126
|
+
child.on('exit', (code) => {
|
|
127
|
+
if (code && code !== 0) {
|
|
128
|
+
process.exit(code);
|
|
129
|
+
}
|
|
130
|
+
resolve('spawned');
|
|
131
|
+
});
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
async function createAction(options) {
|
|
135
|
+
const parsed = await readPayload(options.file);
|
|
136
|
+
if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
|
|
137
|
+
process.stderr.write('Payload must be a JSON object\n');
|
|
138
|
+
process.exit(2);
|
|
139
|
+
}
|
|
140
|
+
const body = parsed;
|
|
141
|
+
const problems = findCreateProblems(body);
|
|
142
|
+
if (problems.length > 0) {
|
|
143
|
+
process.stderr.write(`invalid model: ${problems.join('; ')}\n`);
|
|
144
|
+
process.exit(2);
|
|
145
|
+
}
|
|
146
|
+
let record = null;
|
|
147
|
+
if (await studioHttpUp()) {
|
|
148
|
+
record = await createViaHttp(body);
|
|
149
|
+
}
|
|
150
|
+
if (!record) {
|
|
151
|
+
record = await createSubsystemModel({
|
|
152
|
+
title: body['title'],
|
|
153
|
+
description: typeof body['description'] === 'string' ? body['description'] : undefined,
|
|
154
|
+
components: body['components'],
|
|
155
|
+
edges: body['edges'],
|
|
156
|
+
throughlines: Array.isArray(body['throughlines']) ? body['throughlines'] : undefined,
|
|
157
|
+
source: typeof body['source'] === 'string' ? body['source'] : undefined,
|
|
158
|
+
repo: body['repo'],
|
|
159
|
+
repoRoot: typeof body['repoRoot'] === 'string' ? body['repoRoot'] : undefined,
|
|
160
|
+
repoRoots: isRepoRoots(body['repoRoots']) ? body['repoRoots'] : undefined,
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
process.stderr.write(`Created subsystem model ${record.id} → ${subsystemModelFilePath(record.id)}\n`);
|
|
164
|
+
// Commander `--no-open` flips `open` to false (default true).
|
|
165
|
+
if (options.open !== false) {
|
|
166
|
+
const outcome = await openModel(record.id, {
|
|
167
|
+
viewerDir: options.viewerDir,
|
|
168
|
+
waitOnSpawn: false,
|
|
169
|
+
});
|
|
170
|
+
if (outcome === 'handed') {
|
|
171
|
+
process.stderr.write(`Opened in Principal Studio: ${record.id}\n`);
|
|
172
|
+
}
|
|
173
|
+
else if (outcome === 'spawned') {
|
|
174
|
+
process.stderr.write(`Principal Studio launching with model ${record.id}\n`);
|
|
175
|
+
}
|
|
176
|
+
else {
|
|
177
|
+
process.stderr.write(`Model saved but could not open Principal Studio. Run \`principal-ai open-studio\` or \`principal-ai subsystem-model open ${record.id}\`.\n`);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
process.stdout.write(JSON.stringify({ ok: true, graph: record }, null, 2) + '\n');
|
|
181
|
+
}
|
|
182
|
+
async function openAction(id, options) {
|
|
183
|
+
if (!id) {
|
|
184
|
+
process.stderr.write('Pass a model id.\n');
|
|
185
|
+
process.exit(2);
|
|
186
|
+
}
|
|
187
|
+
const existing = await getSubsystemModel(id);
|
|
188
|
+
if (!existing) {
|
|
189
|
+
process.stderr.write(`Model not found: ${id}\n`);
|
|
190
|
+
process.exit(2);
|
|
191
|
+
}
|
|
192
|
+
const outcome = await openModel(id, { viewerDir: options.viewerDir, waitOnSpawn: true });
|
|
193
|
+
if (outcome === 'handed') {
|
|
194
|
+
process.stderr.write(`Opened in Principal Studio: ${id}\n`);
|
|
195
|
+
return;
|
|
196
|
+
}
|
|
197
|
+
if (outcome === 'failed') {
|
|
198
|
+
process.stderr.write(`Could not open Principal Studio. Is @principal-ai/subsystems-studio installed?\n`);
|
|
199
|
+
process.exit(2);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
async function listAction() {
|
|
203
|
+
const graphs = await listSubsystemModels();
|
|
204
|
+
process.stdout.write(JSON.stringify({
|
|
205
|
+
ok: true,
|
|
206
|
+
graphs: graphs.map((g) => ({
|
|
207
|
+
...g,
|
|
208
|
+
path: subsystemModelFilePath(g.id),
|
|
209
|
+
})),
|
|
210
|
+
}, null, 2) + '\n');
|
|
211
|
+
}
|
|
212
|
+
async function getAction(id) {
|
|
213
|
+
if (!id) {
|
|
214
|
+
process.stderr.write('Pass a model id.\n');
|
|
215
|
+
process.exit(2);
|
|
216
|
+
}
|
|
217
|
+
const graph = await getSubsystemModel(id);
|
|
218
|
+
if (!graph) {
|
|
219
|
+
process.stderr.write(`Model not found: ${id}\n`);
|
|
220
|
+
process.exit(2);
|
|
221
|
+
}
|
|
222
|
+
process.stdout.write(JSON.stringify({ ok: true, graph }, null, 2) + '\n');
|
|
223
|
+
}
|
|
224
|
+
export function createSubsystemModelCommand() {
|
|
225
|
+
const cmd = new Command('subsystem-model').description('Create, open, and list subsystem models (works without Studio already running)');
|
|
226
|
+
cmd
|
|
227
|
+
.command('create')
|
|
228
|
+
.description('Validate + persist a subsystem model JSON, then open it in Principal Studio')
|
|
229
|
+
.option('-f, --file <path>', 'Path to model JSON (default: stdin; use - for stdin)')
|
|
230
|
+
.option('--no-open', 'Persist only; do not open Principal Studio')
|
|
231
|
+
.option('--viewer-dir <path>', 'Path to the @principal-ai/subsystems-studio package (overrides PRINCIPAL_STUDIO_DIR)')
|
|
232
|
+
.action(createAction);
|
|
233
|
+
cmd
|
|
234
|
+
.command('open')
|
|
235
|
+
.description('Open a stored subsystem model in Principal Studio')
|
|
236
|
+
.argument('[id]', 'Model id (sg-…)')
|
|
237
|
+
.option('--viewer-dir <path>', 'Path to the @principal-ai/subsystems-studio package (overrides PRINCIPAL_STUDIO_DIR)')
|
|
238
|
+
.action(openAction);
|
|
239
|
+
cmd
|
|
240
|
+
.command('list')
|
|
241
|
+
.description('List stored subsystem models')
|
|
242
|
+
.action(listAction);
|
|
243
|
+
cmd
|
|
244
|
+
.command('get')
|
|
245
|
+
.description('Print a stored subsystem model as JSON')
|
|
246
|
+
.argument('[id]', 'Model id (sg-…)')
|
|
247
|
+
.action(getAction);
|
|
248
|
+
return cmd;
|
|
249
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Topic command — create / list / view / add-trail on web-ade topics.
|
|
3
|
+
*
|
|
4
|
+
* A **topic** is a curated collection of trails on a shared subject. The
|
|
5
|
+
* topic record itself is `{ title, description, trailIds, owner }` — trails
|
|
6
|
+
* are referenced by id and continue to enforce their own repo-access checks
|
|
7
|
+
* on read. See docs/topics.md in web-ade for the full design.
|
|
8
|
+
*
|
|
9
|
+
* Resolves a GitHub token locally (gh CLI → git credential helper) and calls
|
|
10
|
+
* the web-ade topic API with `Authorization: Bearer <token>`. The token is
|
|
11
|
+
* never echoed to argv, env, stdout, or stderr.
|
|
12
|
+
*/
|
|
13
|
+
import { Command } from 'commander';
|
|
14
|
+
export declare function createTopicCommand(): Command;
|
|
15
|
+
//# sourceMappingURL=topic.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"topic.d.ts","sourceRoot":"","sources":["../../src/commands/topic.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAqsBpC,wBAAgB,kBAAkB,IAAI,OAAO,CAuH5C"}
|