yourskills 0.4.4 → 0.5.0
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/dist/main.js +2016 -1596
- package/package.json +1 -1
package/dist/main.js
CHANGED
|
@@ -61,1128 +61,1436 @@ function buildHelp(lines) {
|
|
|
61
61
|
return `${PREAMBLE}${block}${REST}`;
|
|
62
62
|
}
|
|
63
63
|
|
|
64
|
-
// src/auth/
|
|
65
|
-
import {
|
|
64
|
+
// src/auth/screens/gate.tsx
|
|
65
|
+
import { Box as Box6, Text as Text8, useApp as useApp2, useInput as useInput3 } from "ink";
|
|
66
|
+
import {
|
|
67
|
+
useCallback,
|
|
68
|
+
useEffect as useEffect3,
|
|
69
|
+
useMemo,
|
|
70
|
+
useState as useState4
|
|
71
|
+
} from "react";
|
|
66
72
|
|
|
67
|
-
// src/
|
|
68
|
-
import {
|
|
69
|
-
import { homedir as homedir2 } from "node:os";
|
|
70
|
-
import { join as join2 } from "node:path";
|
|
73
|
+
// src/ui/back.tsx
|
|
74
|
+
import { useInput } from "ink";
|
|
71
75
|
|
|
72
|
-
// src/
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
76
|
+
// ../../packages/ui/src/lib/ascii.ts
|
|
77
|
+
var DEFAULT_ASCII_CHARS = {
|
|
78
|
+
top: "-",
|
|
79
|
+
bottom: "-",
|
|
80
|
+
left: "|",
|
|
81
|
+
right: "|",
|
|
82
|
+
divider: "-",
|
|
83
|
+
junction: "+"
|
|
84
|
+
};
|
|
85
|
+
function fill(n, seq = "-") {
|
|
86
|
+
const count = Math.max(n, 0);
|
|
87
|
+
if (count === 0) {
|
|
88
|
+
return "";
|
|
89
|
+
}
|
|
90
|
+
const unit = seq.length > 0 ? seq : " ";
|
|
91
|
+
return unit.repeat(Math.ceil(count / unit.length)).slice(0, count);
|
|
81
92
|
}
|
|
82
|
-
function
|
|
83
|
-
return
|
|
93
|
+
function junctionGlyph(chars) {
|
|
94
|
+
return chars.junction.length > 0 ? chars.junction[0] : DEFAULT_ASCII_CHARS.junction;
|
|
84
95
|
}
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
return {};
|
|
96
|
+
function topBorder(width, title, chars = DEFAULT_ASCII_CHARS) {
|
|
97
|
+
const j = junctionGlyph(chars);
|
|
98
|
+
if (!title) {
|
|
99
|
+
return `${j}${fill(width - 2, chars.top)}${j}`;
|
|
90
100
|
}
|
|
101
|
+
const label = ` ${title} `;
|
|
102
|
+
const left = 2;
|
|
103
|
+
const right = Math.max(width - 2 - left - label.length, 1);
|
|
104
|
+
return `${j}${fill(left, chars.top)}${label}${fill(right, chars.top)}${j}`;
|
|
91
105
|
}
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
106
|
+
|
|
107
|
+
// src/ui/frame.tsx
|
|
108
|
+
import { Box, Text, useWindowSize } from "ink";
|
|
109
|
+
|
|
110
|
+
// src/ui/theme.ts
|
|
111
|
+
var tone = {
|
|
112
|
+
primary: "cyan",
|
|
113
|
+
notice: "yellow",
|
|
114
|
+
danger: "red"
|
|
115
|
+
};
|
|
116
|
+
var stateTone = {
|
|
117
|
+
"state-active": undefined,
|
|
118
|
+
"state-experimental": "yellow",
|
|
119
|
+
"state-deprecated": "red"
|
|
120
|
+
};
|
|
121
|
+
var mark = {
|
|
122
|
+
ok: "+",
|
|
123
|
+
warn: "!",
|
|
124
|
+
fail: "x",
|
|
125
|
+
pending: ".",
|
|
126
|
+
none: "",
|
|
127
|
+
cursor: ">",
|
|
128
|
+
selected: "*",
|
|
129
|
+
unselected: " "
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
// src/ui/frame.tsx
|
|
133
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
134
|
+
var MAX_WIDTH = 96;
|
|
135
|
+
var MIN_WIDTH = 44;
|
|
136
|
+
var FRAME_CHROME = 4;
|
|
137
|
+
function useFrameWidth() {
|
|
138
|
+
const { columns } = useWindowSize();
|
|
139
|
+
return Math.max(MIN_WIDTH, Math.min(MAX_WIDTH, columns - 2));
|
|
103
140
|
}
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
141
|
+
var j = DEFAULT_ASCII_CHARS.junction;
|
|
142
|
+
var BORDER = {
|
|
143
|
+
topLeft: j,
|
|
144
|
+
top: DEFAULT_ASCII_CHARS.top,
|
|
145
|
+
topRight: j,
|
|
146
|
+
right: DEFAULT_ASCII_CHARS.right,
|
|
147
|
+
bottomRight: j,
|
|
148
|
+
bottom: DEFAULT_ASCII_CHARS.bottom,
|
|
149
|
+
bottomLeft: j,
|
|
150
|
+
left: DEFAULT_ASCII_CHARS.left
|
|
151
|
+
};
|
|
152
|
+
function Frame({
|
|
153
|
+
title,
|
|
154
|
+
width,
|
|
155
|
+
children
|
|
156
|
+
}) {
|
|
157
|
+
return /* @__PURE__ */ jsxs(Box, {
|
|
158
|
+
flexDirection: "column",
|
|
159
|
+
width,
|
|
160
|
+
children: [
|
|
161
|
+
/* @__PURE__ */ jsx(Text, {
|
|
162
|
+
color: tone.primary,
|
|
163
|
+
children: topBorder(width, title)
|
|
164
|
+
}),
|
|
165
|
+
/* @__PURE__ */ jsx(Box, {
|
|
166
|
+
flexDirection: "column",
|
|
167
|
+
width,
|
|
168
|
+
paddingLeft: 1,
|
|
169
|
+
paddingRight: 1,
|
|
170
|
+
borderStyle: BORDER,
|
|
171
|
+
borderColor: tone.primary,
|
|
172
|
+
borderTop: false,
|
|
173
|
+
children
|
|
174
|
+
})
|
|
175
|
+
]
|
|
176
|
+
});
|
|
109
177
|
}
|
|
110
|
-
function
|
|
111
|
-
|
|
178
|
+
function Divider({ width }) {
|
|
179
|
+
return /* @__PURE__ */ jsx(Box, {
|
|
180
|
+
marginLeft: -1,
|
|
181
|
+
marginRight: -1,
|
|
182
|
+
children: /* @__PURE__ */ jsx(Text, {
|
|
183
|
+
color: tone.primary,
|
|
184
|
+
children: fill(width - 2, DEFAULT_ASCII_CHARS.divider)
|
|
185
|
+
})
|
|
186
|
+
});
|
|
112
187
|
}
|
|
113
|
-
function
|
|
114
|
-
return
|
|
188
|
+
function Blank() {
|
|
189
|
+
return /* @__PURE__ */ jsx(Text, {
|
|
190
|
+
children: " "
|
|
191
|
+
});
|
|
115
192
|
}
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
const home = process.env.HOME;
|
|
123
|
-
return join2(home && home.length > 0 ? home : homedir2(), ".agents", ".skill-lock.json");
|
|
193
|
+
function clip(text, width) {
|
|
194
|
+
if (width <= 0)
|
|
195
|
+
return "";
|
|
196
|
+
if (text.length <= width)
|
|
197
|
+
return text;
|
|
198
|
+
return width <= 1 ? text.slice(0, width) : `${text.slice(0, width - 1)}~`;
|
|
124
199
|
}
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
200
|
+
|
|
201
|
+
// ../../packages/skills/src/lifecycle.ts
|
|
202
|
+
var LIFECYCLES = [
|
|
203
|
+
{ status: "active", word: "active", glyph: "#", tone: "state-active" },
|
|
204
|
+
{
|
|
205
|
+
status: "experimental",
|
|
206
|
+
word: "experimental",
|
|
207
|
+
glyph: "?",
|
|
208
|
+
tone: "state-experimental"
|
|
209
|
+
},
|
|
210
|
+
{
|
|
211
|
+
status: "deprecated",
|
|
212
|
+
word: "deprecated",
|
|
213
|
+
glyph: "x",
|
|
214
|
+
tone: "state-deprecated"
|
|
132
215
|
}
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
if (!
|
|
139
|
-
|
|
140
|
-
|
|
216
|
+
];
|
|
217
|
+
var LIFECYCLE_ORDER = LIFECYCLES.map((lifecycle) => lifecycle.status);
|
|
218
|
+
var BY_STATUS = new Map(LIFECYCLES.map((lifecycle) => [lifecycle.status, lifecycle]));
|
|
219
|
+
function lifecycleOf(status) {
|
|
220
|
+
const lifecycle = BY_STATUS.get(status);
|
|
221
|
+
if (!lifecycle) {
|
|
222
|
+
throw new Error(`No lifecycle vocabulary for "${status}". Add it to LIFECYCLES.`);
|
|
223
|
+
}
|
|
224
|
+
return lifecycle;
|
|
141
225
|
}
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
if (
|
|
147
|
-
return
|
|
148
|
-
return
|
|
226
|
+
// src/ui/rows.tsx
|
|
227
|
+
import { Box as Box2, Text as Text2 } from "ink";
|
|
228
|
+
import { jsx as jsx2, jsxs as jsxs2, Fragment } from "react/jsx-runtime";
|
|
229
|
+
function lifecycleTag(status) {
|
|
230
|
+
if (!status || status === "active")
|
|
231
|
+
return "";
|
|
232
|
+
return `[${lifecycleOf(status).word}]`;
|
|
149
233
|
}
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
const value = process.env.YOURSKILLS_NO_TRACK;
|
|
153
|
-
return value !== undefined && value !== "" && value !== "0";
|
|
234
|
+
function lifecycleColor(status) {
|
|
235
|
+
return stateTone[lifecycleOf(status ?? "active").tone];
|
|
154
236
|
}
|
|
155
|
-
|
|
156
|
-
|
|
237
|
+
var GUTTER = 4;
|
|
238
|
+
var GAP = 2;
|
|
239
|
+
function nameWidth(rows, max = 28) {
|
|
240
|
+
return Math.min(max, Math.max(8, ...rows.map((r) => r.name.length)));
|
|
157
241
|
}
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
242
|
+
function SkillRow({
|
|
243
|
+
row,
|
|
244
|
+
width,
|
|
245
|
+
names,
|
|
246
|
+
cursor,
|
|
247
|
+
selected,
|
|
248
|
+
selectable = false
|
|
249
|
+
}) {
|
|
250
|
+
const tag = lifecycleTag(row.status);
|
|
251
|
+
const meta = row.count !== undefined ? `${row.count} skills` : tag;
|
|
252
|
+
const gutter = selectable ? GUTTER : 0;
|
|
253
|
+
const rest = width - gutter - names - GAP - (meta ? meta.length + GAP : 0);
|
|
254
|
+
return /* @__PURE__ */ jsxs2(Box2, {
|
|
255
|
+
children: [
|
|
256
|
+
selectable ? /* @__PURE__ */ jsx2(Text2, {
|
|
257
|
+
color: cursor ? "cyan" : undefined,
|
|
258
|
+
children: `${cursor ? mark.cursor : " "} ${selected ? mark.selected : mark.unselected} `
|
|
259
|
+
}) : null,
|
|
260
|
+
/* @__PURE__ */ jsx2(Text2, {
|
|
261
|
+
bold: cursor,
|
|
262
|
+
color: lifecycleColor(row.status),
|
|
263
|
+
children: clip(row.name, names).padEnd(names)
|
|
264
|
+
}),
|
|
265
|
+
/* @__PURE__ */ jsx2(Text2, {
|
|
266
|
+
children: " ".repeat(GAP)
|
|
267
|
+
}),
|
|
268
|
+
/* @__PURE__ */ jsx2(Text2, {
|
|
269
|
+
dimColor: true,
|
|
270
|
+
children: clip(row.description ?? "", Math.max(rest, 0))
|
|
271
|
+
}),
|
|
272
|
+
meta ? /* @__PURE__ */ jsxs2(Fragment, {
|
|
273
|
+
children: [
|
|
274
|
+
/* @__PURE__ */ jsx2(Text2, {
|
|
275
|
+
children: " ".repeat(GAP)
|
|
276
|
+
}),
|
|
277
|
+
/* @__PURE__ */ jsx2(Text2, {
|
|
278
|
+
color: lifecycleColor(row.status),
|
|
279
|
+
dimColor: !tag,
|
|
280
|
+
children: meta
|
|
281
|
+
})
|
|
282
|
+
]
|
|
283
|
+
}) : null
|
|
284
|
+
]
|
|
285
|
+
});
|
|
195
286
|
}
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
287
|
+
function Hints({ hints }) {
|
|
288
|
+
return /* @__PURE__ */ jsx2(Box2, {
|
|
289
|
+
children: hints.map(([key, label], i) => /* @__PURE__ */ jsxs2(Text2, {
|
|
290
|
+
children: [
|
|
291
|
+
i > 0 ? /* @__PURE__ */ jsx2(Text2, {
|
|
292
|
+
dimColor: true,
|
|
293
|
+
children: " "
|
|
294
|
+
}) : null,
|
|
295
|
+
/* @__PURE__ */ jsx2(Text2, {
|
|
296
|
+
color: "cyan",
|
|
297
|
+
children: key
|
|
298
|
+
}),
|
|
299
|
+
/* @__PURE__ */ jsx2(Text2, {
|
|
300
|
+
dimColor: true,
|
|
301
|
+
children: ` ${label}`
|
|
302
|
+
})
|
|
303
|
+
]
|
|
304
|
+
}, key))
|
|
200
305
|
});
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
// src/ui/back.tsx
|
|
309
|
+
import { jsx as jsx3, jsxs as jsxs3, Fragment as Fragment2 } from "react/jsx-runtime";
|
|
310
|
+
function Back({
|
|
311
|
+
width,
|
|
312
|
+
onBack,
|
|
313
|
+
extra = []
|
|
314
|
+
}) {
|
|
315
|
+
useInput((input, key) => {
|
|
316
|
+
if (key.escape || input === "x")
|
|
317
|
+
onBack?.();
|
|
318
|
+
}, { isActive: Boolean(onBack) });
|
|
319
|
+
if (!onBack)
|
|
320
|
+
return null;
|
|
321
|
+
return /* @__PURE__ */ jsxs3(Fragment2, {
|
|
322
|
+
children: [
|
|
323
|
+
/* @__PURE__ */ jsx3(Divider, {
|
|
324
|
+
width
|
|
325
|
+
}),
|
|
326
|
+
/* @__PURE__ */ jsx3(Hints, {
|
|
327
|
+
hints: [...extra, ["esc/x", "back"]]
|
|
328
|
+
})
|
|
329
|
+
]
|
|
207
330
|
});
|
|
208
331
|
}
|
|
209
|
-
// src/
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
332
|
+
// src/ui/command.tsx
|
|
333
|
+
import { Box as Box3, Text as Text4 } from "ink";
|
|
334
|
+
|
|
335
|
+
// src/ui/text-input.tsx
|
|
336
|
+
import { Text as Text3 } from "ink";
|
|
337
|
+
import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
338
|
+
function SearchInput({
|
|
339
|
+
value,
|
|
340
|
+
placeholder = "",
|
|
341
|
+
focus = true
|
|
342
|
+
}) {
|
|
343
|
+
return /* @__PURE__ */ jsxs4(Text3, {
|
|
344
|
+
children: [
|
|
345
|
+
value.length > 0 ? /* @__PURE__ */ jsx4(Text3, {
|
|
346
|
+
children: value
|
|
347
|
+
}) : /* @__PURE__ */ jsx4(Text3, {
|
|
348
|
+
dimColor: true,
|
|
349
|
+
children: placeholder
|
|
350
|
+
}),
|
|
351
|
+
focus ? /* @__PURE__ */ jsx4(Text3, {
|
|
352
|
+
color: tone.primary,
|
|
353
|
+
children: "_"
|
|
354
|
+
}) : null
|
|
355
|
+
]
|
|
356
|
+
});
|
|
219
357
|
}
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
358
|
+
|
|
359
|
+
// src/ui/viewport.ts
|
|
360
|
+
import { useWindowSize as useWindowSize2 } from "ink";
|
|
361
|
+
var MIN_ROWS = 3;
|
|
362
|
+
var MAX_ROWS = 20;
|
|
363
|
+
function useViewport({
|
|
364
|
+
cursor,
|
|
365
|
+
total,
|
|
366
|
+
chrome
|
|
367
|
+
}) {
|
|
368
|
+
const { rows } = useWindowSize2();
|
|
369
|
+
const size = Math.max(MIN_ROWS, Math.min(MAX_ROWS, rows - chrome));
|
|
370
|
+
const first = Math.max(0, Math.min(cursor - Math.floor(size / 2), total - size));
|
|
371
|
+
return { first, size };
|
|
225
372
|
}
|
|
226
|
-
function
|
|
227
|
-
|
|
228
|
-
return false;
|
|
229
|
-
if (a.includes(b) || b.includes(a))
|
|
230
|
-
return true;
|
|
231
|
-
if (Math.abs(a.length - b.length) > 1)
|
|
232
|
-
return false;
|
|
233
|
-
const [short, long] = a.length <= b.length ? [a, b] : [b, a];
|
|
234
|
-
let i = 0;
|
|
235
|
-
let j = 0;
|
|
236
|
-
let slack = 1;
|
|
237
|
-
while (i < short.length && j < long.length) {
|
|
238
|
-
if (short[i] === long[j]) {
|
|
239
|
-
i++;
|
|
240
|
-
j++;
|
|
241
|
-
continue;
|
|
242
|
-
}
|
|
243
|
-
if (slack-- === 0)
|
|
244
|
-
return false;
|
|
245
|
-
if (short.length === long.length)
|
|
246
|
-
i++;
|
|
247
|
-
j++;
|
|
248
|
-
}
|
|
249
|
-
return true;
|
|
373
|
+
function position(cursor, total) {
|
|
374
|
+
return `${total > 0 ? cursor + 1 : 0}/${total}`;
|
|
250
375
|
}
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
case "autoUpdate":
|
|
263
|
-
return String(config.autoUpdate !== false);
|
|
264
|
-
}
|
|
376
|
+
|
|
377
|
+
// src/ui/command.tsx
|
|
378
|
+
import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
379
|
+
function filterCommands(groups, query) {
|
|
380
|
+
if (query.length === 0)
|
|
381
|
+
return groups;
|
|
382
|
+
const q = query.toLowerCase();
|
|
383
|
+
return groups.map((group) => ({
|
|
384
|
+
group: group.group,
|
|
385
|
+
items: group.items.filter((item) => item.label.toLowerCase().includes(q) || (item.hint ?? "").toLowerCase().includes(q))
|
|
386
|
+
})).filter((group) => group.items.length > 0);
|
|
265
387
|
}
|
|
266
|
-
function
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
}
|
|
281
|
-
return {
|
|
282
|
-
ok: true,
|
|
283
|
-
config: { ...config, defaultScope: value },
|
|
284
|
-
shown: value
|
|
285
|
-
};
|
|
286
|
-
}
|
|
287
|
-
case "agents": {
|
|
288
|
-
const agents = value.split(",").map((a) => a.trim()).filter((a) => a.length > 0);
|
|
289
|
-
if (agents.length === 0) {
|
|
290
|
-
return {
|
|
291
|
-
ok: false,
|
|
292
|
-
message: "agents must be a comma separated list, for example `claude-code,codex`."
|
|
293
|
-
};
|
|
294
|
-
}
|
|
295
|
-
return {
|
|
296
|
-
ok: true,
|
|
297
|
-
config: { ...config, agents },
|
|
298
|
-
shown: agents.join(",")
|
|
299
|
-
};
|
|
300
|
-
}
|
|
301
|
-
case "server": {
|
|
302
|
-
let url;
|
|
303
|
-
try {
|
|
304
|
-
url = new URL(value);
|
|
305
|
-
} catch {
|
|
306
|
-
return {
|
|
307
|
-
ok: false,
|
|
308
|
-
message: `server must be a URL, for example \`https://skills.acme.dev\`. Got: ${value}`
|
|
309
|
-
};
|
|
310
|
-
}
|
|
311
|
-
if (url.protocol !== "http:" && url.protocol !== "https:") {
|
|
312
|
-
return { ok: false, message: "server must be an http or https URL." };
|
|
313
|
-
}
|
|
314
|
-
const serverUrl = url.toString().replace(/\/$/, "");
|
|
315
|
-
return { ok: true, config: { ...config, serverUrl }, shown: serverUrl };
|
|
316
|
-
}
|
|
317
|
-
case "track": {
|
|
318
|
-
if (value !== "true" && value !== "false") {
|
|
319
|
-
return { ok: false, message: "track must be `true` or `false`." };
|
|
320
|
-
}
|
|
321
|
-
const track = value === "true";
|
|
322
|
-
return { ok: true, config: { ...config, track }, shown: String(track) };
|
|
323
|
-
}
|
|
324
|
-
case "autoUpdate": {
|
|
325
|
-
if (value !== "true" && value !== "false") {
|
|
326
|
-
return { ok: false, message: "autoUpdate must be `true` or `false`." };
|
|
327
|
-
}
|
|
328
|
-
const autoUpdate = value === "true";
|
|
329
|
-
return {
|
|
330
|
-
ok: true,
|
|
331
|
-
config: { ...config, autoUpdate },
|
|
332
|
-
shown: String(autoUpdate)
|
|
333
|
-
};
|
|
334
|
-
}
|
|
388
|
+
function flattenCommands(groups) {
|
|
389
|
+
return groups.flatMap((group) => group.items);
|
|
390
|
+
}
|
|
391
|
+
function windowGroups(groups, first, count) {
|
|
392
|
+
const out = [];
|
|
393
|
+
let index = 0;
|
|
394
|
+
for (const group of groups) {
|
|
395
|
+
const items = group.items.filter((_, i) => {
|
|
396
|
+
const row = index + i;
|
|
397
|
+
return row >= first && row < first + count;
|
|
398
|
+
});
|
|
399
|
+
index += group.items.length;
|
|
400
|
+
if (items.length > 0)
|
|
401
|
+
out.push({ group: group.group, items });
|
|
335
402
|
}
|
|
403
|
+
return out;
|
|
336
404
|
}
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
405
|
+
var POINTER = 2;
|
|
406
|
+
var GAP2 = 2;
|
|
407
|
+
var LABEL_CAP = 22;
|
|
408
|
+
function valueColumn(items) {
|
|
409
|
+
return Math.min(items.reduce((wide, item) => Math.max(wide, item.label.length), 0), LABEL_CAP);
|
|
410
|
+
}
|
|
411
|
+
function CommandRow({
|
|
412
|
+
item,
|
|
413
|
+
width,
|
|
414
|
+
column,
|
|
415
|
+
active
|
|
416
|
+
}) {
|
|
417
|
+
const hint = item.hint ?? "";
|
|
418
|
+
const room = width - POINTER - (hint ? hint.length + GAP2 : 0);
|
|
419
|
+
const label = clip(item.label, Math.max(room, 0));
|
|
420
|
+
const shows = item.shows ?? "";
|
|
421
|
+
const pad = Math.max(column - label.length, 0) + GAP2;
|
|
422
|
+
const value = shows && label.length + pad + shows.length <= room ? shows : "";
|
|
423
|
+
const left = label.length + (value ? pad + value.length : 0);
|
|
424
|
+
const gap = Math.max(width - POINTER - left - hint.length, hint ? 1 : 0);
|
|
425
|
+
return /* @__PURE__ */ jsxs5(Text4, {
|
|
426
|
+
children: [
|
|
427
|
+
/* @__PURE__ */ jsx5(Text4, {
|
|
428
|
+
color: tone.primary,
|
|
429
|
+
children: `${active ? mark.cursor : " "} `
|
|
430
|
+
}),
|
|
431
|
+
/* @__PURE__ */ jsx5(Text4, {
|
|
432
|
+
bold: active,
|
|
433
|
+
color: active ? tone.primary : undefined,
|
|
434
|
+
children: label
|
|
435
|
+
}),
|
|
436
|
+
value ? /* @__PURE__ */ jsx5(Text4, {
|
|
437
|
+
children: `${" ".repeat(pad)}${value}`
|
|
438
|
+
}) : null,
|
|
439
|
+
/* @__PURE__ */ jsx5(Text4, {
|
|
440
|
+
dimColor: true,
|
|
441
|
+
children: `${" ".repeat(gap)}${hint}`
|
|
442
|
+
})
|
|
443
|
+
]
|
|
346
444
|
});
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
445
|
+
}
|
|
446
|
+
function CommandPalette({
|
|
447
|
+
groups,
|
|
448
|
+
query,
|
|
449
|
+
cursor,
|
|
450
|
+
width,
|
|
451
|
+
chrome = 11,
|
|
452
|
+
placeholder = "type a command or search",
|
|
453
|
+
emptyMessage = "No matching commands."
|
|
454
|
+
}) {
|
|
455
|
+
const visible = filterCommands(groups, query);
|
|
456
|
+
const rows = flattenCommands(visible);
|
|
457
|
+
const current = rows[cursor];
|
|
458
|
+
const interior = width - FRAME_CHROME;
|
|
459
|
+
const column = valueColumn(rows);
|
|
460
|
+
const { first, size } = useViewport({
|
|
461
|
+
cursor,
|
|
462
|
+
total: rows.length,
|
|
463
|
+
chrome: chrome + Math.max(0, visible.length * 2 - 1)
|
|
464
|
+
});
|
|
465
|
+
const paged = windowGroups(visible, first, size);
|
|
466
|
+
return /* @__PURE__ */ jsxs5(Box3, {
|
|
467
|
+
flexDirection: "column",
|
|
468
|
+
children: [
|
|
469
|
+
/* @__PURE__ */ jsxs5(Box3, {
|
|
470
|
+
children: [
|
|
471
|
+
/* @__PURE__ */ jsx5(Text4, {
|
|
472
|
+
color: tone.primary,
|
|
473
|
+
children: "> "
|
|
474
|
+
}),
|
|
475
|
+
/* @__PURE__ */ jsx5(SearchInput, {
|
|
476
|
+
value: query,
|
|
477
|
+
placeholder
|
|
478
|
+
})
|
|
479
|
+
]
|
|
480
|
+
}),
|
|
481
|
+
/* @__PURE__ */ jsx5(Divider, {
|
|
482
|
+
width
|
|
483
|
+
}),
|
|
484
|
+
rows.length === 0 ? /* @__PURE__ */ jsx5(Text4, {
|
|
485
|
+
dimColor: true,
|
|
486
|
+
children: emptyMessage
|
|
487
|
+
}) : paged.map((group, i) => /* @__PURE__ */ jsxs5(Box3, {
|
|
488
|
+
flexDirection: "column",
|
|
489
|
+
children: [
|
|
490
|
+
i > 0 ? /* @__PURE__ */ jsx5(Divider, {
|
|
491
|
+
width
|
|
492
|
+
}) : null,
|
|
493
|
+
/* @__PURE__ */ jsx5(Text4, {
|
|
494
|
+
dimColor: true,
|
|
495
|
+
children: group.group.toUpperCase()
|
|
496
|
+
}),
|
|
497
|
+
group.items.map((item) => /* @__PURE__ */ jsx5(CommandRow, {
|
|
498
|
+
item,
|
|
499
|
+
width: interior,
|
|
500
|
+
column,
|
|
501
|
+
active: item.value === current?.value
|
|
502
|
+
}, item.value))
|
|
503
|
+
]
|
|
504
|
+
}, group.group))
|
|
505
|
+
]
|
|
350
506
|
});
|
|
351
|
-
return lines;
|
|
352
507
|
}
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
}
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
508
|
+
// src/ui/confirm.tsx
|
|
509
|
+
import { Box as Box4, Text as Text5, useInput as useInput2 } from "ink";
|
|
510
|
+
import { useState } from "react";
|
|
511
|
+
import { jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
512
|
+
function ConfirmScreen({
|
|
513
|
+
title,
|
|
514
|
+
headline,
|
|
515
|
+
lines,
|
|
516
|
+
notes = [],
|
|
517
|
+
yes,
|
|
518
|
+
no,
|
|
519
|
+
argv,
|
|
520
|
+
onYes,
|
|
521
|
+
onNo
|
|
522
|
+
}) {
|
|
523
|
+
const width = useFrameWidth();
|
|
524
|
+
const [choice, setChoice] = useState("yes");
|
|
525
|
+
useInput2((input, key) => {
|
|
526
|
+
if (key.upArrow || key.downArrow) {
|
|
527
|
+
setChoice((c) => c === "yes" ? "no" : "yes");
|
|
528
|
+
} else if (key.return) {
|
|
529
|
+
if (choice === "yes")
|
|
530
|
+
onYes();
|
|
531
|
+
else
|
|
532
|
+
onNo();
|
|
533
|
+
} else if (key.escape || input === "x") {
|
|
534
|
+
onNo();
|
|
535
|
+
}
|
|
536
|
+
});
|
|
537
|
+
const interior = width - FRAME_CHROME;
|
|
538
|
+
const row = (label, active, hint) => {
|
|
539
|
+
const room = interior - 2 - (hint ? hint.length + 2 : 0);
|
|
540
|
+
const text = clip(label, Math.max(room, 0));
|
|
541
|
+
const gap = Math.max(interior - 2 - text.length - (hint?.length ?? 0), 1);
|
|
542
|
+
return /* @__PURE__ */ jsxs6(Text5, {
|
|
543
|
+
children: [
|
|
544
|
+
/* @__PURE__ */ jsx6(Text5, {
|
|
545
|
+
color: tone.primary,
|
|
546
|
+
children: `${active ? mark.cursor : " "} `
|
|
547
|
+
}),
|
|
548
|
+
/* @__PURE__ */ jsx6(Text5, {
|
|
549
|
+
bold: active,
|
|
550
|
+
color: active ? tone.primary : undefined,
|
|
551
|
+
children: text
|
|
552
|
+
}),
|
|
553
|
+
hint ? /* @__PURE__ */ jsx6(Text5, {
|
|
554
|
+
dimColor: true,
|
|
555
|
+
children: `${" ".repeat(gap)}${hint}`
|
|
556
|
+
}) : null
|
|
557
|
+
]
|
|
558
|
+
});
|
|
385
559
|
};
|
|
560
|
+
return /* @__PURE__ */ jsxs6(Frame, {
|
|
561
|
+
title,
|
|
562
|
+
width,
|
|
563
|
+
children: [
|
|
564
|
+
/* @__PURE__ */ jsxs6(Box4, {
|
|
565
|
+
flexDirection: "column",
|
|
566
|
+
children: [
|
|
567
|
+
/* @__PURE__ */ jsx6(Text5, {
|
|
568
|
+
color: tone.notice,
|
|
569
|
+
children: clip(headline, interior)
|
|
570
|
+
}),
|
|
571
|
+
/* @__PURE__ */ jsx6(Text5, {
|
|
572
|
+
children: " "
|
|
573
|
+
}),
|
|
574
|
+
lines.map((line) => /* @__PURE__ */ jsxs6(Text5, {
|
|
575
|
+
children: [
|
|
576
|
+
/* @__PURE__ */ jsx6(Text5, {
|
|
577
|
+
color: line.mark === "fail" ? tone.danger : undefined,
|
|
578
|
+
children: `${mark[line.mark] || " "} `
|
|
579
|
+
}),
|
|
580
|
+
/* @__PURE__ */ jsx6(Text5, {
|
|
581
|
+
children: clip(line.text, interior - 2)
|
|
582
|
+
}),
|
|
583
|
+
line.note ? /* @__PURE__ */ jsx6(Text5, {
|
|
584
|
+
dimColor: true,
|
|
585
|
+
children: clip(` ${line.note}`, Math.max(interior - 2 - line.text.length, 0))
|
|
586
|
+
}) : null
|
|
587
|
+
]
|
|
588
|
+
}, `${line.mark}:${line.text}`)),
|
|
589
|
+
notes.length > 0 ? /* @__PURE__ */ jsx6(Text5, {
|
|
590
|
+
children: " "
|
|
591
|
+
}) : null,
|
|
592
|
+
notes.map((note) => /* @__PURE__ */ jsx6(Text5, {
|
|
593
|
+
dimColor: true,
|
|
594
|
+
children: clip(note, interior)
|
|
595
|
+
}, note)),
|
|
596
|
+
/* @__PURE__ */ jsx6(Text5, {
|
|
597
|
+
children: " "
|
|
598
|
+
}),
|
|
599
|
+
row(yes, choice === "yes", argv),
|
|
600
|
+
row(no, choice === "no")
|
|
601
|
+
]
|
|
602
|
+
}),
|
|
603
|
+
/* @__PURE__ */ jsx6(Divider, {
|
|
604
|
+
width
|
|
605
|
+
}),
|
|
606
|
+
/* @__PURE__ */ jsx6(Hints, {
|
|
607
|
+
hints: [
|
|
608
|
+
["^v", "choose"],
|
|
609
|
+
["enter", choice === "yes" ? yes : no],
|
|
610
|
+
["esc", no]
|
|
611
|
+
]
|
|
612
|
+
})
|
|
613
|
+
]
|
|
614
|
+
});
|
|
386
615
|
}
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
616
|
+
// src/ui/spinner.tsx
|
|
617
|
+
import { Text as Text6 } from "ink";
|
|
618
|
+
import { useEffect, useState as useState2 } from "react";
|
|
619
|
+
import { jsx as jsx7 } from "react/jsx-runtime";
|
|
620
|
+
var FRAMES = ["-", "\\", "|", "/"];
|
|
621
|
+
var INTERVAL_MS = 90;
|
|
622
|
+
function Spinner() {
|
|
623
|
+
const [frame, setFrame] = useState2(0);
|
|
624
|
+
useEffect(() => {
|
|
625
|
+
const timer = setInterval(() => setFrame((f) => (f + 1) % FRAMES.length), INTERVAL_MS);
|
|
626
|
+
return () => clearInterval(timer);
|
|
627
|
+
}, []);
|
|
628
|
+
return /* @__PURE__ */ jsx7(Text6, {
|
|
629
|
+
color: tone.primary,
|
|
630
|
+
children: FRAMES[frame]
|
|
631
|
+
});
|
|
391
632
|
}
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
633
|
+
// src/auth/session.ts
|
|
634
|
+
import { setTimeout as sleep } from "node:timers/promises";
|
|
635
|
+
|
|
636
|
+
// src/settings/agents.ts
|
|
637
|
+
import { readFileSync } from "node:fs";
|
|
638
|
+
import { homedir as homedir2 } from "node:os";
|
|
639
|
+
import { join as join2 } from "node:path";
|
|
640
|
+
|
|
641
|
+
// src/settings/answer.ts
|
|
642
|
+
import { dirname } from "node:path";
|
|
643
|
+
|
|
644
|
+
// src/tracking/answer.ts
|
|
645
|
+
function trackingFrom(layer, configPath) {
|
|
646
|
+
switch (layer) {
|
|
647
|
+
case "env":
|
|
648
|
+
return "YOURSKILLS_NO_TRACK in the environment";
|
|
649
|
+
case "config":
|
|
650
|
+
return `track in ${configPath}`;
|
|
651
|
+
case "organization":
|
|
652
|
+
return "usage tracking in your organization's settings";
|
|
653
|
+
case "default":
|
|
654
|
+
return "built-in default: turn off with `config set track false`";
|
|
409
655
|
}
|
|
410
|
-
await writeConfig(result.config);
|
|
411
|
-
return {
|
|
412
|
-
title: "config",
|
|
413
|
-
lines: [{ mark: "ok", text: `${key} = ${result.shown}`, note: path }],
|
|
414
|
-
json: { path, key, value: result.shown }
|
|
415
|
-
};
|
|
416
656
|
}
|
|
417
|
-
function
|
|
418
|
-
const
|
|
419
|
-
|
|
420
|
-
return;
|
|
421
|
-
const named = unknown.join(", ");
|
|
422
|
-
return {
|
|
423
|
-
title: "config",
|
|
424
|
-
lines: [
|
|
425
|
-
{
|
|
426
|
-
mark: "fail",
|
|
427
|
-
text: `No agent is called ${named}.`,
|
|
428
|
-
note: suggest.length > 0 ? `did you mean ${suggest.join(", ")}?` : ""
|
|
429
|
-
},
|
|
430
|
-
{
|
|
431
|
-
mark: "none",
|
|
432
|
-
text: "The stock `skills` CLI keeps the list of agent names, and it does not have this one, so every install would stop at it."
|
|
433
|
-
},
|
|
434
|
-
{
|
|
435
|
-
mark: "none",
|
|
436
|
-
text: "Run `yourskills doctor` to see the names this machine accepts."
|
|
437
|
-
}
|
|
438
|
-
],
|
|
439
|
-
json: { path, error: `unknown agents: ${named}`, suggest },
|
|
440
|
-
failed: true
|
|
441
|
-
};
|
|
442
|
-
}
|
|
443
|
-
// src/settings/scope.ts
|
|
444
|
-
import { existsSync } from "node:fs";
|
|
445
|
-
import { readFile as readFile2 } from "node:fs/promises";
|
|
446
|
-
import { dirname, join as join3, resolve } from "node:path";
|
|
447
|
-
var PROJECT_MANIFEST = "yourskills.json";
|
|
448
|
-
function findProjectManifest(from) {
|
|
449
|
-
let dir = resolve(from ?? process.cwd());
|
|
450
|
-
for (;; ) {
|
|
451
|
-
const candidate = join3(dir, PROJECT_MANIFEST);
|
|
452
|
-
if (existsSync(candidate))
|
|
453
|
-
return candidate;
|
|
454
|
-
const parent = dirname(dir);
|
|
455
|
-
if (parent === dir)
|
|
456
|
-
return null;
|
|
457
|
-
dir = parent;
|
|
458
|
-
}
|
|
657
|
+
function trackingDisabledByEnv() {
|
|
658
|
+
const value = process.env.YOURSKILLS_NO_TRACK;
|
|
659
|
+
return value !== undefined && value !== "" && value !== "0";
|
|
459
660
|
}
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
const parsed = JSON.parse(await readFile2(path, "utf8"));
|
|
463
|
-
return parsed.scope === "global" || parsed.scope === "project" ? parsed.scope : null;
|
|
464
|
-
} catch {
|
|
465
|
-
return null;
|
|
466
|
-
}
|
|
661
|
+
function trackingOn(config) {
|
|
662
|
+
return !trackingDisabledByEnv() && config.track !== false;
|
|
467
663
|
}
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
664
|
+
var ON_NOTE = "on; turn off with `yourskills config set track false`";
|
|
665
|
+
function trackingAnswer(question) {
|
|
666
|
+
const { track, configPath, organization } = question;
|
|
667
|
+
if (trackingDisabledByEnv()) {
|
|
668
|
+
return {
|
|
669
|
+
state: "off-here",
|
|
670
|
+
sending: false,
|
|
671
|
+
note: "off (YOURSKILLS_NO_TRACK is set)",
|
|
672
|
+
value: "off",
|
|
673
|
+
layer: "env",
|
|
674
|
+
from: trackingFrom("env", configPath)
|
|
675
|
+
};
|
|
476
676
|
}
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
const res = await fetch(url, {
|
|
489
|
-
method: "POST",
|
|
490
|
-
headers: { "content-type": "application/json" },
|
|
491
|
-
body: JSON.stringify(body)
|
|
492
|
-
});
|
|
493
|
-
const text = await res.text();
|
|
494
|
-
let parsed;
|
|
495
|
-
try {
|
|
496
|
-
parsed = JSON.parse(text);
|
|
497
|
-
} catch {
|
|
677
|
+
if (track === false) {
|
|
678
|
+
return {
|
|
679
|
+
state: "off-here",
|
|
680
|
+
sending: false,
|
|
681
|
+
note: "off; turn on with `yourskills config set track true`",
|
|
682
|
+
value: "off",
|
|
683
|
+
layer: "config",
|
|
684
|
+
from: trackingFrom("config", configPath)
|
|
685
|
+
};
|
|
686
|
+
}
|
|
687
|
+
if (organization?.enabled === false) {
|
|
498
688
|
return {
|
|
499
|
-
|
|
500
|
-
|
|
689
|
+
state: "off-for-organization",
|
|
690
|
+
sending: true,
|
|
691
|
+
note: "off; your organization turned usage tracking off for everyone",
|
|
692
|
+
value: "off",
|
|
693
|
+
layer: "organization",
|
|
694
|
+
from: trackingFrom("organization", configPath)
|
|
501
695
|
};
|
|
502
696
|
}
|
|
503
|
-
|
|
504
|
-
return { ok: true, data: parsed };
|
|
505
|
-
const err = parsed;
|
|
697
|
+
const layer = track === true ? "config" : "default";
|
|
506
698
|
return {
|
|
507
|
-
|
|
508
|
-
|
|
699
|
+
state: "on",
|
|
700
|
+
sending: true,
|
|
701
|
+
note: ON_NOTE,
|
|
702
|
+
value: "on",
|
|
703
|
+
layer,
|
|
704
|
+
from: trackingFrom(layer, configPath)
|
|
509
705
|
};
|
|
510
706
|
}
|
|
511
|
-
async function
|
|
512
|
-
const
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
client_id: CLIENT_ID
|
|
707
|
+
async function trackingNow(question) {
|
|
708
|
+
const here = trackingAnswer({
|
|
709
|
+
track: question.track,
|
|
710
|
+
configPath: question.configPath
|
|
516
711
|
});
|
|
517
|
-
if (!
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
kind: "prompt",
|
|
524
|
-
userCode: device.user_code,
|
|
525
|
-
verificationUri: device.verification_uri_complete ?? device.verification_uri,
|
|
526
|
-
expiresAt: deadline
|
|
527
|
-
};
|
|
528
|
-
let intervalMs = Math.max(device.interval, 1) * 1000;
|
|
529
|
-
while (Date.now() < deadline) {
|
|
530
|
-
await sleep(intervalMs);
|
|
531
|
-
const poll = await post(`${authBase(base)}/device/token`, {
|
|
532
|
-
grant_type: GRANT_TYPE,
|
|
533
|
-
device_code: device.device_code,
|
|
534
|
-
client_id: CLIENT_ID
|
|
535
|
-
});
|
|
536
|
-
if ("ok" in poll) {
|
|
537
|
-
const token = poll.data.access_token;
|
|
538
|
-
const who = await fetchSession(base, token);
|
|
539
|
-
await writeConfig({
|
|
540
|
-
...config,
|
|
541
|
-
token,
|
|
542
|
-
serverUrl: base,
|
|
543
|
-
userEmail: who?.user?.email
|
|
544
|
-
});
|
|
545
|
-
yield { kind: "done", email: who?.user?.email };
|
|
546
|
-
return;
|
|
547
|
-
}
|
|
548
|
-
switch (poll.error) {
|
|
549
|
-
case "authorization_pending":
|
|
550
|
-
yield { kind: "waiting" };
|
|
551
|
-
break;
|
|
552
|
-
case "slow_down":
|
|
553
|
-
intervalMs += SLOW_DOWN_STEP_MS;
|
|
554
|
-
yield { kind: "waiting" };
|
|
555
|
-
break;
|
|
556
|
-
case "access_denied":
|
|
557
|
-
throw new Error("Login was denied.");
|
|
558
|
-
case "expired_token":
|
|
559
|
-
throw new Error("The device code expired. Run `yourskills login` again.");
|
|
560
|
-
default:
|
|
561
|
-
throw new Error(`Login failed: ${poll.error_description ?? poll.error}`);
|
|
562
|
-
}
|
|
563
|
-
}
|
|
564
|
-
throw new Error("The device code expired. Run `yourskills login` again.");
|
|
565
|
-
}
|
|
566
|
-
async function logout() {
|
|
567
|
-
const config = await readConfig();
|
|
568
|
-
if (config.token) {
|
|
569
|
-
await fetch(`${authBase(serverUrl(config))}/sign-out`, {
|
|
570
|
-
method: "POST",
|
|
571
|
-
headers: { authorization: `Bearer ${config.token}` }
|
|
572
|
-
}).catch(() => {
|
|
573
|
-
return;
|
|
574
|
-
});
|
|
575
|
-
}
|
|
576
|
-
clearConfig();
|
|
577
|
-
}
|
|
578
|
-
async function fetchSession(base, token) {
|
|
579
|
-
const res = await fetch(`${authBase(base)}/get-session`, {
|
|
580
|
-
headers: { authorization: `Bearer ${token}` }
|
|
712
|
+
if (!here.sending)
|
|
713
|
+
return here;
|
|
714
|
+
return trackingAnswer({
|
|
715
|
+
track: question.track,
|
|
716
|
+
configPath: question.configPath,
|
|
717
|
+
organization: await question.askOrganization()
|
|
581
718
|
});
|
|
582
|
-
if (!res.ok)
|
|
583
|
-
return null;
|
|
584
|
-
return await res.json();
|
|
585
719
|
}
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
org: session.session?.activeOrganizationId ?? null,
|
|
597
|
-
server: base
|
|
598
|
-
};
|
|
720
|
+
// src/settings/keys.ts
|
|
721
|
+
var CONFIG_KEYS = [
|
|
722
|
+
"scope",
|
|
723
|
+
"agents",
|
|
724
|
+
"server",
|
|
725
|
+
"track",
|
|
726
|
+
"autoUpdate"
|
|
727
|
+
];
|
|
728
|
+
function isConfigKey(key) {
|
|
729
|
+
return CONFIG_KEYS.includes(key);
|
|
599
730
|
}
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
return {
|
|
731
|
+
var REDACTED = "<redacted>";
|
|
732
|
+
function unknownAgents(asked, valid) {
|
|
733
|
+
const unknown = asked.filter((name) => !valid.includes(name));
|
|
734
|
+
const suggest = valid.filter((name) => unknown.some((miss) => near(miss, name)));
|
|
735
|
+
return { unknown, suggest };
|
|
605
736
|
}
|
|
606
|
-
|
|
607
|
-
|
|
737
|
+
function near(a, b) {
|
|
738
|
+
if (a === b)
|
|
739
|
+
return false;
|
|
740
|
+
if (a.includes(b) || b.includes(a))
|
|
741
|
+
return true;
|
|
742
|
+
if (Math.abs(a.length - b.length) > 1)
|
|
743
|
+
return false;
|
|
744
|
+
const [short, long] = a.length <= b.length ? [a, b] : [b, a];
|
|
745
|
+
let i = 0;
|
|
746
|
+
let j = 0;
|
|
747
|
+
let slack = 1;
|
|
748
|
+
while (i < short.length && j < long.length) {
|
|
749
|
+
if (short[i] === long[j]) {
|
|
750
|
+
i++;
|
|
751
|
+
j++;
|
|
752
|
+
continue;
|
|
753
|
+
}
|
|
754
|
+
if (slack-- === 0)
|
|
755
|
+
return false;
|
|
756
|
+
if (short.length === long.length)
|
|
757
|
+
i++;
|
|
758
|
+
j++;
|
|
759
|
+
}
|
|
760
|
+
return true;
|
|
608
761
|
}
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
}
|
|
762
|
+
var UNSET = "(unset)";
|
|
763
|
+
function setConfigValue(config, key, value) {
|
|
764
|
+
if (!isConfigKey(key)) {
|
|
765
|
+
return {
|
|
766
|
+
ok: false,
|
|
767
|
+
message: `Unknown config key: ${key}. Known keys: ${CONFIG_KEYS.join(", ")}.`
|
|
768
|
+
};
|
|
769
|
+
}
|
|
770
|
+
switch (key) {
|
|
771
|
+
case "scope": {
|
|
772
|
+
if (value !== "global" && value !== "project") {
|
|
773
|
+
return {
|
|
774
|
+
ok: false,
|
|
775
|
+
message: "scope must be `global` or `project`."
|
|
776
|
+
};
|
|
777
|
+
}
|
|
778
|
+
return {
|
|
779
|
+
ok: true,
|
|
780
|
+
config: { ...config, defaultScope: value },
|
|
781
|
+
shown: value
|
|
782
|
+
};
|
|
783
|
+
}
|
|
784
|
+
case "agents": {
|
|
785
|
+
const agents = value.split(",").map((a) => a.trim()).filter((a) => a.length > 0);
|
|
786
|
+
if (agents.length === 0) {
|
|
787
|
+
return {
|
|
788
|
+
ok: false,
|
|
789
|
+
message: "agents must be a comma separated list, for example `claude-code,codex`."
|
|
790
|
+
};
|
|
791
|
+
}
|
|
792
|
+
return {
|
|
793
|
+
ok: true,
|
|
794
|
+
config: { ...config, agents },
|
|
795
|
+
shown: agents.join(",")
|
|
796
|
+
};
|
|
797
|
+
}
|
|
798
|
+
case "server": {
|
|
799
|
+
let url;
|
|
800
|
+
try {
|
|
801
|
+
url = new URL(value);
|
|
802
|
+
} catch {
|
|
803
|
+
return {
|
|
804
|
+
ok: false,
|
|
805
|
+
message: `server must be a URL, for example \`https://skills.acme.dev\`. Got: ${value}`
|
|
806
|
+
};
|
|
807
|
+
}
|
|
808
|
+
if (url.protocol !== "http:" && url.protocol !== "https:") {
|
|
809
|
+
return { ok: false, message: "server must be an http or https URL." };
|
|
810
|
+
}
|
|
811
|
+
const serverUrl = url.toString().replace(/\/$/, "");
|
|
812
|
+
return { ok: true, config: { ...config, serverUrl }, shown: serverUrl };
|
|
813
|
+
}
|
|
814
|
+
case "track": {
|
|
815
|
+
if (value !== "true" && value !== "false") {
|
|
816
|
+
return { ok: false, message: "track must be `true` or `false`." };
|
|
817
|
+
}
|
|
818
|
+
const track = value === "true";
|
|
819
|
+
return { ok: true, config: { ...config, track }, shown: String(track) };
|
|
820
|
+
}
|
|
821
|
+
case "autoUpdate": {
|
|
822
|
+
if (value !== "true" && value !== "false") {
|
|
823
|
+
return { ok: false, message: "autoUpdate must be `true` or `false`." };
|
|
824
|
+
}
|
|
825
|
+
const autoUpdate = value === "true";
|
|
826
|
+
return {
|
|
827
|
+
ok: true,
|
|
828
|
+
config: { ...config, autoUpdate },
|
|
829
|
+
shown: String(autoUpdate)
|
|
830
|
+
};
|
|
831
|
+
}
|
|
832
|
+
}
|
|
617
833
|
}
|
|
618
|
-
// src/auth/screens/gate.tsx
|
|
619
|
-
import { Box as Box6, Text as Text8, useApp as useApp2, useInput as useInput3 } from "ink";
|
|
620
|
-
import {
|
|
621
|
-
useCallback,
|
|
622
|
-
useEffect as useEffect3,
|
|
623
|
-
useMemo,
|
|
624
|
-
useState as useState4
|
|
625
|
-
} from "react";
|
|
626
|
-
|
|
627
|
-
// src/ui/back.tsx
|
|
628
|
-
import { useInput } from "ink";
|
|
629
834
|
|
|
630
|
-
//
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
}
|
|
639
|
-
function
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
835
|
+
// src/settings/store.ts
|
|
836
|
+
import { chmodSync, mkdirSync, rmSync } from "node:fs";
|
|
837
|
+
import { readFile, writeFile } from "node:fs/promises";
|
|
838
|
+
import { homedir } from "node:os";
|
|
839
|
+
import { join } from "node:path";
|
|
840
|
+
function configDir() {
|
|
841
|
+
const xdg = process.env.XDG_CONFIG_HOME;
|
|
842
|
+
return join(xdg && xdg.length > 0 ? xdg : join(homedir(), ".config"), "yourskills");
|
|
843
|
+
}
|
|
844
|
+
function configPath() {
|
|
845
|
+
return join(configDir(), "config.json");
|
|
846
|
+
}
|
|
847
|
+
async function readConfig() {
|
|
848
|
+
try {
|
|
849
|
+
return JSON.parse(await readFile(configPath(), "utf8"));
|
|
850
|
+
} catch {
|
|
851
|
+
return {};
|
|
852
|
+
}
|
|
853
|
+
}
|
|
854
|
+
async function updateConfig(change) {
|
|
855
|
+
let current = {};
|
|
856
|
+
try {
|
|
857
|
+
current = JSON.parse(await readFile(configPath(), "utf8"));
|
|
858
|
+
} catch (error) {
|
|
859
|
+
if (error?.code !== "ENOENT")
|
|
860
|
+
return null;
|
|
643
861
|
}
|
|
644
|
-
const
|
|
645
|
-
|
|
862
|
+
const next = change(current);
|
|
863
|
+
await writeConfig(next);
|
|
864
|
+
return next;
|
|
646
865
|
}
|
|
647
|
-
function
|
|
648
|
-
|
|
866
|
+
async function writeConfig(config) {
|
|
867
|
+
mkdirSync(configDir(), { recursive: true, mode: 448 });
|
|
868
|
+
await writeFile(configPath(), `${JSON.stringify(config, null, 2)}
|
|
869
|
+
`);
|
|
870
|
+
chmodSync(configPath(), 384);
|
|
649
871
|
}
|
|
650
|
-
function
|
|
651
|
-
|
|
652
|
-
if (!title) {
|
|
653
|
-
return `${j}${fill(width - 2, chars.top)}${j}`;
|
|
654
|
-
}
|
|
655
|
-
const label = ` ${title} `;
|
|
656
|
-
const left = 2;
|
|
657
|
-
const right = Math.max(width - 2 - left - label.length, 1);
|
|
658
|
-
return `${j}${fill(left, chars.top)}${label}${fill(right, chars.top)}${j}`;
|
|
872
|
+
function clearConfig() {
|
|
873
|
+
rmSync(configPath(), { force: true });
|
|
659
874
|
}
|
|
660
875
|
|
|
661
|
-
// src/
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
selected: "*",
|
|
682
|
-
unselected: " "
|
|
683
|
-
};
|
|
684
|
-
|
|
685
|
-
// src/ui/frame.tsx
|
|
686
|
-
import { jsx, jsxs } from "react/jsx-runtime";
|
|
687
|
-
var MAX_WIDTH = 96;
|
|
688
|
-
var MIN_WIDTH = 44;
|
|
689
|
-
var FRAME_CHROME = 4;
|
|
690
|
-
function useFrameWidth() {
|
|
691
|
-
const { columns } = useWindowSize();
|
|
692
|
-
return Math.max(MIN_WIDTH, Math.min(MAX_WIDTH, columns - 2));
|
|
876
|
+
// src/settings/answer.ts
|
|
877
|
+
function decide(consulted, last) {
|
|
878
|
+
const layers = [...consulted, last];
|
|
879
|
+
const spoke = consulted.find((layer) => layer.value !== null);
|
|
880
|
+
if (spoke && spoke.value !== null) {
|
|
881
|
+
return {
|
|
882
|
+
value: spoke.value,
|
|
883
|
+
layer: spoke.layer,
|
|
884
|
+
from: spoke.from,
|
|
885
|
+
layers,
|
|
886
|
+
decided: true
|
|
887
|
+
};
|
|
888
|
+
}
|
|
889
|
+
return {
|
|
890
|
+
value: last.value,
|
|
891
|
+
layer: last.layer,
|
|
892
|
+
from: last.from,
|
|
893
|
+
layers,
|
|
894
|
+
decided: false
|
|
895
|
+
};
|
|
693
896
|
}
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
}),
|
|
718
|
-
/* @__PURE__ */ jsx(Box, {
|
|
719
|
-
flexDirection: "column",
|
|
720
|
-
width,
|
|
721
|
-
paddingLeft: 1,
|
|
722
|
-
paddingRight: 1,
|
|
723
|
-
borderStyle: BORDER,
|
|
724
|
-
borderColor: tone.primary,
|
|
725
|
-
borderTop: false,
|
|
726
|
-
children
|
|
727
|
-
})
|
|
728
|
-
]
|
|
729
|
-
});
|
|
897
|
+
function scopeAnswer(held) {
|
|
898
|
+
const { project } = held;
|
|
899
|
+
return decide([
|
|
900
|
+
{
|
|
901
|
+
layer: "flag",
|
|
902
|
+
value: held.flags?.scope ?? null,
|
|
903
|
+
from: "-g or -p on the command line"
|
|
904
|
+
},
|
|
905
|
+
project ? {
|
|
906
|
+
layer: "project",
|
|
907
|
+
value: project.scope,
|
|
908
|
+
from: `yourskills.json at ${dirname(project.path)}${project.scope ? "" : " (declares no scope)"}`
|
|
909
|
+
} : {
|
|
910
|
+
layer: "project",
|
|
911
|
+
value: null,
|
|
912
|
+
from: "no yourskills.json above here"
|
|
913
|
+
},
|
|
914
|
+
{
|
|
915
|
+
layer: "config",
|
|
916
|
+
value: held.config.defaultScope ?? null,
|
|
917
|
+
from: `defaultScope in ${held.configPath}`
|
|
918
|
+
}
|
|
919
|
+
], { layer: "default", value: "global", from: "built-in default" });
|
|
730
920
|
}
|
|
731
|
-
function
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
}
|
|
921
|
+
function agentsAnswer(held) {
|
|
922
|
+
const some = (agents) => agents && agents.length > 0 ? agents : null;
|
|
923
|
+
return decide([
|
|
924
|
+
{
|
|
925
|
+
layer: "flag",
|
|
926
|
+
value: some(held.flags?.agents),
|
|
927
|
+
from: "-a on the command line"
|
|
928
|
+
},
|
|
929
|
+
{
|
|
930
|
+
layer: "config",
|
|
931
|
+
value: some(held.config.agents),
|
|
932
|
+
from: `agents in ${held.configPath}`
|
|
933
|
+
},
|
|
934
|
+
{
|
|
935
|
+
layer: "stock",
|
|
936
|
+
value: some(held.stock?.agents),
|
|
937
|
+
from: held.stock ? `lastSelectedAgents in ${held.stock.path}` : "what the stock `skills` CLI remembers (not read)"
|
|
938
|
+
}
|
|
939
|
+
], {
|
|
940
|
+
layer: "default",
|
|
941
|
+
value: [],
|
|
942
|
+
from: "nothing pinned; the stock `skills` CLI looks at what is installed"
|
|
739
943
|
});
|
|
740
944
|
}
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
945
|
+
var DEFAULT_URL = "https://api.yourskills.store";
|
|
946
|
+
function serverAnswer(held) {
|
|
947
|
+
return decide([
|
|
948
|
+
{
|
|
949
|
+
layer: "flag",
|
|
950
|
+
value: held.flags?.server || null,
|
|
951
|
+
from: "--server on the command line"
|
|
952
|
+
},
|
|
953
|
+
{
|
|
954
|
+
layer: "env",
|
|
955
|
+
value: process.env.YOURSKILLS_URL || null,
|
|
956
|
+
from: "YOURSKILLS_URL in the environment"
|
|
957
|
+
},
|
|
958
|
+
{
|
|
959
|
+
layer: "config",
|
|
960
|
+
value: held.config.serverUrl || null,
|
|
961
|
+
from: `server in ${held.configPath}`
|
|
962
|
+
}
|
|
963
|
+
], { layer: "default", value: DEFAULT_URL, from: "built-in default" });
|
|
745
964
|
}
|
|
746
|
-
function
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
965
|
+
function serverUrl(config = {}, override) {
|
|
966
|
+
return serverAnswer({
|
|
967
|
+
config,
|
|
968
|
+
configPath: configPath(),
|
|
969
|
+
flags: { server: override }
|
|
970
|
+
}).value;
|
|
752
971
|
}
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
word: "experimental",
|
|
760
|
-
glyph: "?",
|
|
761
|
-
tone: "state-experimental"
|
|
762
|
-
},
|
|
763
|
-
{
|
|
764
|
-
status: "deprecated",
|
|
765
|
-
word: "deprecated",
|
|
766
|
-
glyph: "x",
|
|
767
|
-
tone: "state-deprecated"
|
|
768
|
-
}
|
|
769
|
-
];
|
|
770
|
-
var LIFECYCLE_ORDER = LIFECYCLES.map((lifecycle) => lifecycle.status);
|
|
771
|
-
var BY_STATUS = new Map(LIFECYCLES.map((lifecycle) => [lifecycle.status, lifecycle]));
|
|
772
|
-
function lifecycleOf(status) {
|
|
773
|
-
const lifecycle = BY_STATUS.get(status);
|
|
774
|
-
if (!lifecycle) {
|
|
775
|
-
throw new Error(`No lifecycle vocabulary for "${status}". Add it to LIFECYCLES.`);
|
|
776
|
-
}
|
|
777
|
-
return lifecycle;
|
|
972
|
+
function trackingOf(held) {
|
|
973
|
+
return trackingAnswer({
|
|
974
|
+
track: held.config.track,
|
|
975
|
+
configPath: held.configPath,
|
|
976
|
+
organization: held.organization
|
|
977
|
+
});
|
|
778
978
|
}
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
979
|
+
function trackAnswer(held) {
|
|
980
|
+
const tracking = trackingOf(held);
|
|
981
|
+
const { configPath, organization } = held;
|
|
982
|
+
const layers = [
|
|
983
|
+
{
|
|
984
|
+
layer: "env",
|
|
985
|
+
value: trackingDisabledByEnv() ? false : null,
|
|
986
|
+
from: trackingFrom("env", configPath)
|
|
987
|
+
},
|
|
988
|
+
{
|
|
989
|
+
layer: "config",
|
|
990
|
+
value: held.config.track ?? null,
|
|
991
|
+
from: trackingFrom("config", configPath)
|
|
992
|
+
},
|
|
993
|
+
{
|
|
994
|
+
layer: "organization",
|
|
995
|
+
value: organization ? organization.enabled : null,
|
|
996
|
+
from: trackingFrom("organization", configPath)
|
|
997
|
+
},
|
|
998
|
+
{
|
|
999
|
+
layer: "default",
|
|
1000
|
+
value: true,
|
|
1001
|
+
from: trackingFrom("default", configPath)
|
|
1002
|
+
}
|
|
1003
|
+
];
|
|
1004
|
+
return {
|
|
1005
|
+
value: tracking.value === "on",
|
|
1006
|
+
layer: tracking.layer,
|
|
1007
|
+
from: tracking.from,
|
|
1008
|
+
layers,
|
|
1009
|
+
decided: tracking.layer !== "default"
|
|
1010
|
+
};
|
|
786
1011
|
}
|
|
787
|
-
function
|
|
788
|
-
return
|
|
1012
|
+
function autoUpdateAnswer(held) {
|
|
1013
|
+
return decide([
|
|
1014
|
+
{
|
|
1015
|
+
layer: "config",
|
|
1016
|
+
value: held.config.autoUpdate ?? null,
|
|
1017
|
+
from: `autoUpdate in ${held.configPath}`
|
|
1018
|
+
}
|
|
1019
|
+
], { layer: "default", value: true, from: "built-in default" });
|
|
789
1020
|
}
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
1021
|
+
function spell(key, answer, as) {
|
|
1022
|
+
return {
|
|
1023
|
+
key,
|
|
1024
|
+
json: answer.value,
|
|
1025
|
+
value: as(answer.value),
|
|
1026
|
+
layer: answer.layer,
|
|
1027
|
+
from: answer.from,
|
|
1028
|
+
decided: answer.decided,
|
|
1029
|
+
layers: answer.layers.map((layer) => ({
|
|
1030
|
+
layer: layer.layer,
|
|
1031
|
+
from: layer.from,
|
|
1032
|
+
value: layer.value === null ? null : as(layer.value)
|
|
1033
|
+
}))
|
|
1034
|
+
};
|
|
794
1035
|
}
|
|
795
|
-
function
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
children: [
|
|
809
|
-
selectable ? /* @__PURE__ */ jsx2(Text2, {
|
|
810
|
-
color: cursor ? "cyan" : undefined,
|
|
811
|
-
children: `${cursor ? mark.cursor : " "} ${selected ? mark.selected : mark.unselected} `
|
|
812
|
-
}) : null,
|
|
813
|
-
/* @__PURE__ */ jsx2(Text2, {
|
|
814
|
-
bold: cursor,
|
|
815
|
-
color: lifecycleColor(row.status),
|
|
816
|
-
children: clip(row.name, names).padEnd(names)
|
|
817
|
-
}),
|
|
818
|
-
/* @__PURE__ */ jsx2(Text2, {
|
|
819
|
-
children: " ".repeat(GAP)
|
|
820
|
-
}),
|
|
821
|
-
/* @__PURE__ */ jsx2(Text2, {
|
|
822
|
-
dimColor: true,
|
|
823
|
-
children: clip(row.description ?? "", Math.max(rest, 0))
|
|
824
|
-
}),
|
|
825
|
-
meta ? /* @__PURE__ */ jsxs2(Fragment, {
|
|
826
|
-
children: [
|
|
827
|
-
/* @__PURE__ */ jsx2(Text2, {
|
|
828
|
-
children: " ".repeat(GAP)
|
|
829
|
-
}),
|
|
830
|
-
/* @__PURE__ */ jsx2(Text2, {
|
|
831
|
-
color: lifecycleColor(row.status),
|
|
832
|
-
dimColor: !tag,
|
|
833
|
-
children: meta
|
|
834
|
-
})
|
|
835
|
-
]
|
|
836
|
-
}) : null
|
|
837
|
-
]
|
|
838
|
-
});
|
|
1036
|
+
function shown(key, held) {
|
|
1037
|
+
switch (key) {
|
|
1038
|
+
case "scope":
|
|
1039
|
+
return spell(key, scopeAnswer(held), (scope) => scope);
|
|
1040
|
+
case "agents":
|
|
1041
|
+
return spell(key, agentsAnswer(held), (agents) => agents.length > 0 ? agents.join(",") : UNSET);
|
|
1042
|
+
case "server":
|
|
1043
|
+
return spell(key, serverAnswer(held), (server) => server);
|
|
1044
|
+
case "track":
|
|
1045
|
+
return spell(key, trackAnswer(held), String);
|
|
1046
|
+
case "autoUpdate":
|
|
1047
|
+
return spell(key, autoUpdateAnswer(held), String);
|
|
1048
|
+
}
|
|
839
1049
|
}
|
|
840
|
-
function
|
|
841
|
-
return
|
|
842
|
-
children: hints.map(([key, label], i) => /* @__PURE__ */ jsxs2(Text2, {
|
|
843
|
-
children: [
|
|
844
|
-
i > 0 ? /* @__PURE__ */ jsx2(Text2, {
|
|
845
|
-
dimColor: true,
|
|
846
|
-
children: " "
|
|
847
|
-
}) : null,
|
|
848
|
-
/* @__PURE__ */ jsx2(Text2, {
|
|
849
|
-
color: "cyan",
|
|
850
|
-
children: key
|
|
851
|
-
}),
|
|
852
|
-
/* @__PURE__ */ jsx2(Text2, {
|
|
853
|
-
dimColor: true,
|
|
854
|
-
children: ` ${label}`
|
|
855
|
-
})
|
|
856
|
-
]
|
|
857
|
-
}, key))
|
|
858
|
-
});
|
|
1050
|
+
function settingsShown(held) {
|
|
1051
|
+
return CONFIG_KEYS.map((key) => shown(key, held));
|
|
859
1052
|
}
|
|
860
1053
|
|
|
861
|
-
// src/
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
1054
|
+
// src/settings/agents.ts
|
|
1055
|
+
function stockLockPath() {
|
|
1056
|
+
const state = process.env.XDG_STATE_HOME;
|
|
1057
|
+
if (state && state.length > 0)
|
|
1058
|
+
return join2(state, "skills", ".skill-lock.json");
|
|
1059
|
+
const home = process.env.HOME;
|
|
1060
|
+
return join2(home && home.length > 0 ? home : homedir2(), ".agents", ".skill-lock.json");
|
|
1061
|
+
}
|
|
1062
|
+
var LOCK_VERSION = 3;
|
|
1063
|
+
function rememberedAgents() {
|
|
1064
|
+
let lock;
|
|
1065
|
+
try {
|
|
1066
|
+
lock = JSON.parse(readFileSync(stockLockPath(), "utf8"));
|
|
1067
|
+
} catch {
|
|
1068
|
+
return [];
|
|
1069
|
+
}
|
|
1070
|
+
if (!lock || typeof lock !== "object")
|
|
1071
|
+
return [];
|
|
1072
|
+
const raw = lock;
|
|
1073
|
+
if (typeof raw.version !== "number" || raw.version < LOCK_VERSION)
|
|
1074
|
+
return [];
|
|
1075
|
+
if (!raw.skills)
|
|
1076
|
+
return [];
|
|
1077
|
+
return Array.isArray(raw.lastSelectedAgents) ? raw.lastSelectedAgents.filter((a) => typeof a === "string") : [];
|
|
1078
|
+
}
|
|
1079
|
+
async function resolveAgents(flagAgents) {
|
|
1080
|
+
const config = await readConfig();
|
|
1081
|
+
return agentsAnswer({
|
|
1082
|
+
config,
|
|
1083
|
+
configPath: configPath(),
|
|
1084
|
+
flags: { agents: flagAgents },
|
|
1085
|
+
stock: { path: stockLockPath(), agents: rememberedAgents() }
|
|
1086
|
+
}).value;
|
|
1087
|
+
}
|
|
1088
|
+
// src/settings/scope.ts
|
|
1089
|
+
import { existsSync } from "node:fs";
|
|
1090
|
+
import { readFile as readFile2 } from "node:fs/promises";
|
|
1091
|
+
import { dirname as dirname2, join as join3, resolve } from "node:path";
|
|
1092
|
+
var PROJECT_MANIFEST = "yourskills.json";
|
|
1093
|
+
function findProjectManifest(from) {
|
|
1094
|
+
let dir = resolve(from ?? process.cwd());
|
|
1095
|
+
for (;; ) {
|
|
1096
|
+
const candidate = join3(dir, PROJECT_MANIFEST);
|
|
1097
|
+
if (existsSync(candidate))
|
|
1098
|
+
return candidate;
|
|
1099
|
+
const parent = dirname2(dir);
|
|
1100
|
+
if (parent === dir)
|
|
1101
|
+
return null;
|
|
1102
|
+
dir = parent;
|
|
1103
|
+
}
|
|
1104
|
+
}
|
|
1105
|
+
async function scopeInManifest(path) {
|
|
1106
|
+
try {
|
|
1107
|
+
const parsed = JSON.parse(await readFile2(path, "utf8"));
|
|
1108
|
+
return parsed.scope === "global" || parsed.scope === "project" ? parsed.scope : null;
|
|
1109
|
+
} catch {
|
|
873
1110
|
return null;
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
1111
|
+
}
|
|
1112
|
+
}
|
|
1113
|
+
async function nearestProject(cwd) {
|
|
1114
|
+
const path = findProjectManifest(cwd);
|
|
1115
|
+
return path ? { path, scope: await scopeInManifest(path) } : null;
|
|
1116
|
+
}
|
|
1117
|
+
async function resolveScope(opts = {}) {
|
|
1118
|
+
const [config, project] = await Promise.all([
|
|
1119
|
+
readConfig(),
|
|
1120
|
+
nearestProject(opts.cwd)
|
|
1121
|
+
]);
|
|
1122
|
+
const answer = scopeAnswer({
|
|
1123
|
+
config,
|
|
1124
|
+
configPath: configPath(),
|
|
1125
|
+
flags: { scope: opts.flag },
|
|
1126
|
+
project
|
|
883
1127
|
});
|
|
1128
|
+
return {
|
|
1129
|
+
scope: answer.value,
|
|
1130
|
+
source: answer.layer,
|
|
1131
|
+
manifestPath: project?.path ?? null
|
|
1132
|
+
};
|
|
884
1133
|
}
|
|
885
|
-
// src/ui/command.tsx
|
|
886
|
-
import { Box as Box3, Text as Text4 } from "ink";
|
|
887
1134
|
|
|
888
|
-
// src/
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
}),
|
|
904
|
-
focus ? /* @__PURE__ */ jsx4(Text3, {
|
|
905
|
-
color: tone.primary,
|
|
906
|
-
children: "_"
|
|
907
|
-
}) : null
|
|
908
|
-
]
|
|
909
|
-
});
|
|
1135
|
+
// src/settings/held.ts
|
|
1136
|
+
async function holdSettings(ask = {}) {
|
|
1137
|
+
const [config, project] = await Promise.all([
|
|
1138
|
+
readConfig(),
|
|
1139
|
+
nearestProject(ask.cwd)
|
|
1140
|
+
]);
|
|
1141
|
+
const organization = ask.askOrganization && trackingOn(config) ? await ask.askOrganization() : undefined;
|
|
1142
|
+
return {
|
|
1143
|
+
config,
|
|
1144
|
+
configPath: configPath(),
|
|
1145
|
+
flags: ask.flags,
|
|
1146
|
+
project,
|
|
1147
|
+
stock: { path: stockLockPath(), agents: rememberedAgents() },
|
|
1148
|
+
organization
|
|
1149
|
+
};
|
|
910
1150
|
}
|
|
911
1151
|
|
|
912
|
-
// src/
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
var MAX_ROWS = 20;
|
|
916
|
-
function useViewport({
|
|
917
|
-
cursor,
|
|
918
|
-
total,
|
|
919
|
-
chrome
|
|
920
|
-
}) {
|
|
921
|
-
const { rows } = useWindowSize2();
|
|
922
|
-
const size = Math.max(MIN_ROWS, Math.min(MAX_ROWS, rows - chrome));
|
|
923
|
-
const first = Math.max(0, Math.min(cursor - Math.floor(size / 2), total - size));
|
|
924
|
-
return { first, size };
|
|
1152
|
+
// src/settings/command.ts
|
|
1153
|
+
async function configGet(key) {
|
|
1154
|
+
return configGetReport(await holdSettings(), key);
|
|
925
1155
|
}
|
|
926
|
-
function
|
|
927
|
-
|
|
1156
|
+
function configLines(held) {
|
|
1157
|
+
const width = Math.max(...CONFIG_KEYS.map((k) => k.length), "user".length);
|
|
1158
|
+
const lines = settingsShown(held).map((setting) => ({
|
|
1159
|
+
mark: "none",
|
|
1160
|
+
text: `${setting.key.padEnd(width)} ${setting.value}`
|
|
1161
|
+
}));
|
|
1162
|
+
lines.push({
|
|
1163
|
+
mark: "none",
|
|
1164
|
+
text: `${"user".padEnd(width)} ${held.config.userEmail ?? UNSET}`
|
|
1165
|
+
});
|
|
1166
|
+
lines.push({
|
|
1167
|
+
mark: "none",
|
|
1168
|
+
text: `${"token".padEnd(width)} ${held.config.token ? REDACTED : UNSET}`
|
|
1169
|
+
});
|
|
1170
|
+
return lines;
|
|
928
1171
|
}
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
1172
|
+
function configGetReport(held, key) {
|
|
1173
|
+
const path = held.configPath;
|
|
1174
|
+
if (key === undefined) {
|
|
1175
|
+
return {
|
|
1176
|
+
title: "config",
|
|
1177
|
+
lines: configLines(held),
|
|
1178
|
+
json: {
|
|
1179
|
+
path,
|
|
1180
|
+
...Object.fromEntries(settingsShown(held).map((setting) => [setting.key, setting.json])),
|
|
1181
|
+
user: held.config.userEmail ?? null,
|
|
1182
|
+
token: held.config.token ? REDACTED : null
|
|
1183
|
+
}
|
|
1184
|
+
};
|
|
1185
|
+
}
|
|
1186
|
+
if (!isConfigKey(key)) {
|
|
1187
|
+
return {
|
|
1188
|
+
title: "config",
|
|
1189
|
+
lines: [
|
|
1190
|
+
{
|
|
1191
|
+
mark: "fail",
|
|
1192
|
+
text: `Unknown config key: ${key}`,
|
|
1193
|
+
note: `Known keys: ${CONFIG_KEYS.join(", ")}`
|
|
1194
|
+
}
|
|
1195
|
+
],
|
|
1196
|
+
json: { path, error: `unknown key: ${key}`, keys: CONFIG_KEYS },
|
|
1197
|
+
failed: true
|
|
1198
|
+
};
|
|
1199
|
+
}
|
|
1200
|
+
const setting = shown(key, held);
|
|
1201
|
+
return {
|
|
1202
|
+
title: "config",
|
|
1203
|
+
lines: [{ mark: "none", text: setting.value }],
|
|
1204
|
+
json: { path, key, value: setting.json }
|
|
1205
|
+
};
|
|
940
1206
|
}
|
|
941
|
-
function
|
|
942
|
-
|
|
1207
|
+
async function configSet(key, value, known) {
|
|
1208
|
+
const path = configPath();
|
|
1209
|
+
const config = await readConfig();
|
|
1210
|
+
const result = setConfigValue(config, key, value);
|
|
1211
|
+
if (!result.ok) {
|
|
1212
|
+
return {
|
|
1213
|
+
title: "config",
|
|
1214
|
+
lines: [{ mark: "fail", text: result.message }],
|
|
1215
|
+
json: { path, error: result.message },
|
|
1216
|
+
failed: true
|
|
1217
|
+
};
|
|
1218
|
+
}
|
|
1219
|
+
if (key === "agents") {
|
|
1220
|
+
const valid = await known();
|
|
1221
|
+
const refusal = valid && refuse(result.config.agents ?? [], valid, path);
|
|
1222
|
+
if (refusal)
|
|
1223
|
+
return refusal;
|
|
1224
|
+
}
|
|
1225
|
+
await writeConfig(result.config);
|
|
1226
|
+
return {
|
|
1227
|
+
title: "config",
|
|
1228
|
+
lines: [{ mark: "ok", text: `${key} = ${result.shown}`, note: path }],
|
|
1229
|
+
json: { path, key, value: result.shown }
|
|
1230
|
+
};
|
|
943
1231
|
}
|
|
944
|
-
function
|
|
945
|
-
const
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
1232
|
+
function refuse(asked, valid, path) {
|
|
1233
|
+
const { unknown, suggest } = unknownAgents(asked, valid);
|
|
1234
|
+
if (unknown.length === 0)
|
|
1235
|
+
return;
|
|
1236
|
+
const named = unknown.join(", ");
|
|
1237
|
+
return {
|
|
1238
|
+
title: "config",
|
|
1239
|
+
lines: [
|
|
1240
|
+
{
|
|
1241
|
+
mark: "fail",
|
|
1242
|
+
text: `No agent is called ${named}.`,
|
|
1243
|
+
note: suggest.length > 0 ? `did you mean ${suggest.join(", ")}?` : ""
|
|
1244
|
+
},
|
|
1245
|
+
{
|
|
1246
|
+
mark: "none",
|
|
1247
|
+
text: "The stock `skills` CLI keeps the list of agent names, and it does not have this one, so every install would stop at it."
|
|
1248
|
+
},
|
|
1249
|
+
{
|
|
1250
|
+
mark: "none",
|
|
1251
|
+
text: "Run `yourskills doctor` to see the names this machine accepts."
|
|
1252
|
+
}
|
|
1253
|
+
],
|
|
1254
|
+
json: { path, error: `unknown agents: ${named}`, suggest },
|
|
1255
|
+
failed: true
|
|
1256
|
+
};
|
|
1257
|
+
}
|
|
1258
|
+
// src/auth/session.ts
|
|
1259
|
+
var CLIENT_ID = "yourskills-cli";
|
|
1260
|
+
var GRANT_TYPE = "urn:ietf:params:oauth:grant-type:device_code";
|
|
1261
|
+
var SLOW_DOWN_STEP_MS = 5000;
|
|
1262
|
+
var LOGIN = "yourskills login";
|
|
1263
|
+
var EXPIRED = `The device code expired. Run \`${LOGIN}\` again.`;
|
|
1264
|
+
var authBase = (base) => `${base.replace(/\/$/, "")}/api/auth`;
|
|
1265
|
+
var bearer = (token) => ({ authorization: `Bearer ${token}` });
|
|
1266
|
+
var REFUSED = {
|
|
1267
|
+
"no-token": { said: "Not logged in.", fact: "no token on this machine" },
|
|
1268
|
+
stale: {
|
|
1269
|
+
said: "Session is no longer valid.",
|
|
1270
|
+
fact: "the registry no longer accepts the token on this machine"
|
|
1271
|
+
}
|
|
1272
|
+
};
|
|
1273
|
+
|
|
1274
|
+
class SessionRefusal extends Error {
|
|
1275
|
+
reason;
|
|
1276
|
+
fact;
|
|
1277
|
+
next = LOGIN;
|
|
1278
|
+
constructor(reason) {
|
|
1279
|
+
super(`${REFUSED[reason].said} Run \`${LOGIN}\`.`);
|
|
1280
|
+
this.name = "SessionRefusal";
|
|
1281
|
+
this.reason = reason;
|
|
1282
|
+
this.fact = REFUSED[reason].fact;
|
|
955
1283
|
}
|
|
956
|
-
return out;
|
|
957
1284
|
}
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
var LABEL_CAP = 22;
|
|
961
|
-
function valueColumn(items) {
|
|
962
|
-
return Math.min(items.reduce((wide, item) => Math.max(wide, item.label.length), 0), LABEL_CAP);
|
|
1285
|
+
function isSessionRefusal(error) {
|
|
1286
|
+
return error instanceof Error && error.name === "SessionRefusal";
|
|
963
1287
|
}
|
|
964
|
-
function
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
}) {
|
|
970
|
-
const hint = item.hint ?? "";
|
|
971
|
-
const room = width - POINTER - (hint ? hint.length + GAP2 : 0);
|
|
972
|
-
const label = clip(item.label, Math.max(room, 0));
|
|
973
|
-
const shows = item.shows ?? "";
|
|
974
|
-
const pad = Math.max(column - label.length, 0) + GAP2;
|
|
975
|
-
const value = shows && label.length + pad + shows.length <= room ? shows : "";
|
|
976
|
-
const left = label.length + (value ? pad + value.length : 0);
|
|
977
|
-
const gap = Math.max(width - POINTER - left - hint.length, hint ? 1 : 0);
|
|
978
|
-
return /* @__PURE__ */ jsxs5(Text4, {
|
|
979
|
-
children: [
|
|
980
|
-
/* @__PURE__ */ jsx5(Text4, {
|
|
981
|
-
color: tone.primary,
|
|
982
|
-
children: `${active ? mark.cursor : " "} `
|
|
983
|
-
}),
|
|
984
|
-
/* @__PURE__ */ jsx5(Text4, {
|
|
985
|
-
bold: active,
|
|
986
|
-
color: active ? tone.primary : undefined,
|
|
987
|
-
children: label
|
|
988
|
-
}),
|
|
989
|
-
value ? /* @__PURE__ */ jsx5(Text4, {
|
|
990
|
-
children: `${" ".repeat(pad)}${value}`
|
|
991
|
-
}) : null,
|
|
992
|
-
/* @__PURE__ */ jsx5(Text4, {
|
|
993
|
-
dimColor: true,
|
|
994
|
-
children: `${" ".repeat(gap)}${hint}`
|
|
995
|
-
})
|
|
996
|
-
]
|
|
997
|
-
});
|
|
1288
|
+
async function requireToken(server) {
|
|
1289
|
+
const config = await readConfig();
|
|
1290
|
+
if (!config.token)
|
|
1291
|
+
throw new SessionRefusal("no-token");
|
|
1292
|
+
return { token: config.token, base: serverUrl(config, server) };
|
|
998
1293
|
}
|
|
999
|
-
function
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
const
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
chrome: chrome + Math.max(0, visible.length * 2 - 1)
|
|
1294
|
+
async function hasSession() {
|
|
1295
|
+
return Boolean((await readConfig()).token);
|
|
1296
|
+
}
|
|
1297
|
+
async function localAccount(server) {
|
|
1298
|
+
const config = await readConfig();
|
|
1299
|
+
return {
|
|
1300
|
+
signedIn: Boolean(config.token),
|
|
1301
|
+
email: config.userEmail ?? null,
|
|
1302
|
+
organization: config.orgName ?? null,
|
|
1303
|
+
server: serverUrl(config, server)
|
|
1304
|
+
};
|
|
1305
|
+
}
|
|
1306
|
+
async function post(talk, url, body) {
|
|
1307
|
+
const res = await talk(url, {
|
|
1308
|
+
method: "POST",
|
|
1309
|
+
headers: { "content-type": "application/json" },
|
|
1310
|
+
body: JSON.stringify(body)
|
|
1017
1311
|
});
|
|
1018
|
-
const
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
children: emptyMessage
|
|
1040
|
-
}) : paged.map((group, i) => /* @__PURE__ */ jsxs5(Box3, {
|
|
1041
|
-
flexDirection: "column",
|
|
1042
|
-
children: [
|
|
1043
|
-
i > 0 ? /* @__PURE__ */ jsx5(Divider, {
|
|
1044
|
-
width
|
|
1045
|
-
}) : null,
|
|
1046
|
-
/* @__PURE__ */ jsx5(Text4, {
|
|
1047
|
-
dimColor: true,
|
|
1048
|
-
children: group.group.toUpperCase()
|
|
1049
|
-
}),
|
|
1050
|
-
group.items.map((item) => /* @__PURE__ */ jsx5(CommandRow, {
|
|
1051
|
-
item,
|
|
1052
|
-
width: interior,
|
|
1053
|
-
column,
|
|
1054
|
-
active: item.value === current?.value
|
|
1055
|
-
}, item.value))
|
|
1056
|
-
]
|
|
1057
|
-
}, group.group))
|
|
1058
|
-
]
|
|
1312
|
+
const text = await res.text();
|
|
1313
|
+
let parsed;
|
|
1314
|
+
try {
|
|
1315
|
+
parsed = JSON.parse(text);
|
|
1316
|
+
} catch {
|
|
1317
|
+
return {
|
|
1318
|
+
error: "invalid_response",
|
|
1319
|
+
error_description: text.slice(0, 200) || res.statusText
|
|
1320
|
+
};
|
|
1321
|
+
}
|
|
1322
|
+
if (res.ok)
|
|
1323
|
+
return { ok: true, data: parsed };
|
|
1324
|
+
const err = parsed;
|
|
1325
|
+
return {
|
|
1326
|
+
error: err.error ?? `http_${res.status}`,
|
|
1327
|
+
error_description: err.error_description
|
|
1328
|
+
};
|
|
1329
|
+
}
|
|
1330
|
+
async function getSession(talk, base, token) {
|
|
1331
|
+
const res = await talk(`${authBase(base)}/get-session`, {
|
|
1332
|
+
headers: bearer(token)
|
|
1059
1333
|
});
|
|
1334
|
+
if (!res.ok)
|
|
1335
|
+
return null;
|
|
1336
|
+
return await res.json();
|
|
1060
1337
|
}
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1338
|
+
async function sessionOf(talk, base, token) {
|
|
1339
|
+
const answer = await getSession(talk, base, token);
|
|
1340
|
+
if (!answer?.user)
|
|
1341
|
+
throw new SessionRefusal("stale");
|
|
1342
|
+
return {
|
|
1343
|
+
user: answer.user.email ?? answer.user.id,
|
|
1344
|
+
organizationId: answer.session?.activeOrganizationId ?? null,
|
|
1345
|
+
server: base
|
|
1346
|
+
};
|
|
1347
|
+
}
|
|
1348
|
+
async function listOrganizations(talk, base, token) {
|
|
1349
|
+
let res;
|
|
1350
|
+
try {
|
|
1351
|
+
res = await talk(`${authBase(base)}/organization/list`, {
|
|
1352
|
+
headers: bearer(token)
|
|
1353
|
+
});
|
|
1354
|
+
} catch (error) {
|
|
1355
|
+
throw new Error(`Could not reach ${base}: ${error instanceof Error ? error.message : String(error)}`);
|
|
1356
|
+
}
|
|
1357
|
+
if (!res.ok) {
|
|
1358
|
+
throw new Error(`Could not list organizations: ${res.status} ${res.statusText}`);
|
|
1359
|
+
}
|
|
1360
|
+
return await res.json();
|
|
1361
|
+
}
|
|
1362
|
+
async function* login(reach = {}) {
|
|
1363
|
+
const talk = reach.talk ?? fetch;
|
|
1364
|
+
const wait = reach.wait ?? ((ms) => sleep(ms));
|
|
1365
|
+
const config = await readConfig();
|
|
1366
|
+
const base = serverUrl(config, reach.server);
|
|
1367
|
+
const start = await post(talk, `${authBase(base)}/device/code`, { client_id: CLIENT_ID });
|
|
1368
|
+
if (!("ok" in start)) {
|
|
1369
|
+
throw new Error(`Could not start device login: ${start.error_description ?? start.error}`);
|
|
1370
|
+
}
|
|
1371
|
+
const device = start.data;
|
|
1372
|
+
const deadline = Date.now() + device.expires_in * 1000;
|
|
1373
|
+
yield {
|
|
1374
|
+
kind: "prompt",
|
|
1375
|
+
userCode: device.user_code,
|
|
1376
|
+
verificationUri: device.verification_uri_complete ?? device.verification_uri,
|
|
1377
|
+
expiresAt: deadline
|
|
1378
|
+
};
|
|
1379
|
+
let intervalMs = Math.max(device.interval, 1) * 1000;
|
|
1380
|
+
while (Date.now() < deadline) {
|
|
1381
|
+
await wait(intervalMs);
|
|
1382
|
+
const poll = await post(talk, `${authBase(base)}/device/token`, {
|
|
1383
|
+
grant_type: GRANT_TYPE,
|
|
1384
|
+
device_code: device.device_code,
|
|
1385
|
+
client_id: CLIENT_ID
|
|
1386
|
+
});
|
|
1387
|
+
if ("ok" in poll) {
|
|
1388
|
+
const token = poll.data.access_token;
|
|
1389
|
+
const who = await getSession(talk, base, token);
|
|
1390
|
+
await writeConfig({
|
|
1391
|
+
...config,
|
|
1392
|
+
token,
|
|
1393
|
+
serverUrl: base,
|
|
1394
|
+
userEmail: who?.user?.email
|
|
1395
|
+
});
|
|
1396
|
+
yield { kind: "done", email: who?.user?.email };
|
|
1397
|
+
return;
|
|
1088
1398
|
}
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1399
|
+
switch (poll.error) {
|
|
1400
|
+
case "authorization_pending":
|
|
1401
|
+
yield { kind: "waiting" };
|
|
1402
|
+
break;
|
|
1403
|
+
case "slow_down":
|
|
1404
|
+
intervalMs += SLOW_DOWN_STEP_MS;
|
|
1405
|
+
yield { kind: "waiting" };
|
|
1406
|
+
break;
|
|
1407
|
+
case "access_denied":
|
|
1408
|
+
throw new Error("Login was denied.");
|
|
1409
|
+
case "expired_token":
|
|
1410
|
+
throw new Error(EXPIRED);
|
|
1411
|
+
default:
|
|
1412
|
+
throw new Error(`Login failed: ${poll.error_description ?? poll.error}`);
|
|
1413
|
+
}
|
|
1414
|
+
}
|
|
1415
|
+
throw new Error(EXPIRED);
|
|
1416
|
+
}
|
|
1417
|
+
async function logout(reach = {}) {
|
|
1418
|
+
const config = await readConfig();
|
|
1419
|
+
if (config.token) {
|
|
1420
|
+
await (reach.talk ?? fetch)(`${authBase(serverUrl(config, reach.server))}/sign-out`, { method: "POST", headers: bearer(config.token) }).catch(() => {
|
|
1421
|
+
return;
|
|
1111
1422
|
});
|
|
1112
|
-
}
|
|
1113
|
-
|
|
1114
|
-
title,
|
|
1115
|
-
width,
|
|
1116
|
-
children: [
|
|
1117
|
-
/* @__PURE__ */ jsxs6(Box4, {
|
|
1118
|
-
flexDirection: "column",
|
|
1119
|
-
children: [
|
|
1120
|
-
/* @__PURE__ */ jsx6(Text5, {
|
|
1121
|
-
color: tone.notice,
|
|
1122
|
-
children: clip(headline, interior)
|
|
1123
|
-
}),
|
|
1124
|
-
/* @__PURE__ */ jsx6(Text5, {
|
|
1125
|
-
children: " "
|
|
1126
|
-
}),
|
|
1127
|
-
lines.map((line) => /* @__PURE__ */ jsxs6(Text5, {
|
|
1128
|
-
children: [
|
|
1129
|
-
/* @__PURE__ */ jsx6(Text5, {
|
|
1130
|
-
color: line.mark === "fail" ? tone.danger : undefined,
|
|
1131
|
-
children: `${mark[line.mark] || " "} `
|
|
1132
|
-
}),
|
|
1133
|
-
/* @__PURE__ */ jsx6(Text5, {
|
|
1134
|
-
children: clip(line.text, interior - 2)
|
|
1135
|
-
}),
|
|
1136
|
-
line.note ? /* @__PURE__ */ jsx6(Text5, {
|
|
1137
|
-
dimColor: true,
|
|
1138
|
-
children: clip(` ${line.note}`, Math.max(interior - 2 - line.text.length, 0))
|
|
1139
|
-
}) : null
|
|
1140
|
-
]
|
|
1141
|
-
}, `${line.mark}:${line.text}`)),
|
|
1142
|
-
notes.length > 0 ? /* @__PURE__ */ jsx6(Text5, {
|
|
1143
|
-
children: " "
|
|
1144
|
-
}) : null,
|
|
1145
|
-
notes.map((note) => /* @__PURE__ */ jsx6(Text5, {
|
|
1146
|
-
dimColor: true,
|
|
1147
|
-
children: clip(note, interior)
|
|
1148
|
-
}, note)),
|
|
1149
|
-
/* @__PURE__ */ jsx6(Text5, {
|
|
1150
|
-
children: " "
|
|
1151
|
-
}),
|
|
1152
|
-
row(yes, choice === "yes", argv),
|
|
1153
|
-
row(no, choice === "no")
|
|
1154
|
-
]
|
|
1155
|
-
}),
|
|
1156
|
-
/* @__PURE__ */ jsx6(Divider, {
|
|
1157
|
-
width
|
|
1158
|
-
}),
|
|
1159
|
-
/* @__PURE__ */ jsx6(Hints, {
|
|
1160
|
-
hints: [
|
|
1161
|
-
["^v", "choose"],
|
|
1162
|
-
["enter", choice === "yes" ? yes : no],
|
|
1163
|
-
["esc", no]
|
|
1164
|
-
]
|
|
1165
|
-
})
|
|
1166
|
-
]
|
|
1167
|
-
});
|
|
1423
|
+
}
|
|
1424
|
+
clearConfig();
|
|
1168
1425
|
}
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
var
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1426
|
+
async function readSession(reach = {}) {
|
|
1427
|
+
const { token, base } = await requireToken(reach.server);
|
|
1428
|
+
return sessionOf(reach.talk ?? fetch, base, token);
|
|
1429
|
+
}
|
|
1430
|
+
var REACH_TIMEOUT_MS = 5000;
|
|
1431
|
+
async function reachable(reach = {}, timeoutMs = REACH_TIMEOUT_MS) {
|
|
1432
|
+
const base = serverUrl(await readConfig(), reach.server);
|
|
1433
|
+
try {
|
|
1434
|
+
const res = await (reach.talk ?? fetch)(`${authBase(base)}/ok`, {
|
|
1435
|
+
signal: AbortSignal.timeout(timeoutMs)
|
|
1436
|
+
});
|
|
1437
|
+
return { base, status: res.status };
|
|
1438
|
+
} catch (error) {
|
|
1439
|
+
return {
|
|
1440
|
+
base,
|
|
1441
|
+
status: null,
|
|
1442
|
+
said: error instanceof Error ? error.message : String(error)
|
|
1443
|
+
};
|
|
1444
|
+
}
|
|
1445
|
+
}
|
|
1446
|
+
async function rememberOrganization(name) {
|
|
1447
|
+
try {
|
|
1448
|
+
const config = await readConfig();
|
|
1449
|
+
if ((config.orgName ?? null) === name)
|
|
1450
|
+
return;
|
|
1451
|
+
await writeConfig(name === null ? { ...config, orgName: undefined } : { ...config, orgName: name });
|
|
1452
|
+
} catch {}
|
|
1453
|
+
}
|
|
1454
|
+
async function organizations(reach = {}) {
|
|
1455
|
+
const { token, base } = await requireToken(reach.server);
|
|
1456
|
+
const talk = reach.talk ?? fetch;
|
|
1457
|
+
const [who, list] = await Promise.allSettled([
|
|
1458
|
+
sessionOf(talk, base, token),
|
|
1459
|
+
listOrganizations(talk, base, token)
|
|
1460
|
+
]);
|
|
1461
|
+
if (who.status === "rejected")
|
|
1462
|
+
throw who.reason;
|
|
1463
|
+
if (list.status === "rejected")
|
|
1464
|
+
throw list.reason;
|
|
1465
|
+
const active = list.value.find((o) => o.id === who.value.organizationId) ?? null;
|
|
1466
|
+
await rememberOrganization(active?.name ?? null);
|
|
1467
|
+
return { all: list.value, active };
|
|
1468
|
+
}
|
|
1469
|
+
async function switchOrganization(name, reach = {}) {
|
|
1470
|
+
const { token, base } = await requireToken(reach.server);
|
|
1471
|
+
const talk = reach.talk ?? fetch;
|
|
1472
|
+
const all = await listOrganizations(talk, base, token);
|
|
1473
|
+
const wanted = name.toLowerCase();
|
|
1474
|
+
const match = all.find((o) => o.name.toLowerCase() === wanted || o.slug?.toLowerCase() === wanted);
|
|
1475
|
+
if (!match)
|
|
1476
|
+
return { kind: "unknown", offered: all };
|
|
1477
|
+
const res = await talk(`${authBase(base)}/organization/set-active`, {
|
|
1478
|
+
method: "POST",
|
|
1479
|
+
headers: { "content-type": "application/json", ...bearer(token) },
|
|
1480
|
+
body: JSON.stringify({ organizationId: match.id })
|
|
1184
1481
|
});
|
|
1482
|
+
if (!res.ok) {
|
|
1483
|
+
const detail = (await res.text()).slice(0, 200);
|
|
1484
|
+
return {
|
|
1485
|
+
kind: "refused",
|
|
1486
|
+
organization: match,
|
|
1487
|
+
detail: detail || `${res.status} ${res.statusText}`
|
|
1488
|
+
};
|
|
1489
|
+
}
|
|
1490
|
+
await rememberOrganization(match.name);
|
|
1491
|
+
return { kind: "switched", organization: match };
|
|
1185
1492
|
}
|
|
1493
|
+
|
|
1186
1494
|
// src/auth/screens/login.tsx
|
|
1187
1495
|
import { Box as Box5, Text as Text7, useApp } from "ink";
|
|
1188
1496
|
import { useEffect as useEffect2, useState as useState3 } from "react";
|
|
@@ -1533,7 +1841,7 @@ import { useCallback as useCallback2, useEffect as useEffect6, useMemo as useMem
|
|
|
1533
1841
|
|
|
1534
1842
|
// src/manifest/manifest.ts
|
|
1535
1843
|
import { mkdirSync as mkdirSync2, readFileSync as readFileSync2, writeFileSync } from "node:fs";
|
|
1536
|
-
import { dirname as
|
|
1844
|
+
import { dirname as dirname3, join as join4, resolve as resolve2 } from "node:path";
|
|
1537
1845
|
function emptyManifest(scope) {
|
|
1538
1846
|
return { version: 1, scope, agents: [], bundles: {}, skills: {} };
|
|
1539
1847
|
}
|
|
@@ -1561,7 +1869,7 @@ function readManifest(path) {
|
|
|
1561
1869
|
};
|
|
1562
1870
|
}
|
|
1563
1871
|
function writeManifest(path, m) {
|
|
1564
|
-
mkdirSync2(
|
|
1872
|
+
mkdirSync2(dirname3(path), { recursive: true });
|
|
1565
1873
|
writeFileSync(path, `${JSON.stringify(m, null, 2)}
|
|
1566
1874
|
`);
|
|
1567
1875
|
}
|
|
@@ -1637,7 +1945,7 @@ async function nearestManifest(flag, cwd) {
|
|
|
1637
1945
|
// src/install/install.ts
|
|
1638
1946
|
import { spawn } from "node:child_process";
|
|
1639
1947
|
import { createRequire } from "node:module";
|
|
1640
|
-
import { dirname as
|
|
1948
|
+
import { dirname as dirname4, join as join5 } from "node:path";
|
|
1641
1949
|
function skillsAddArgv(location, opts = {}) {
|
|
1642
1950
|
const path = location.path.replace(/^\/+|\/+$/g, "");
|
|
1643
1951
|
const at = location.ref ? `#${location.ref}` : "";
|
|
@@ -1671,7 +1979,7 @@ function skillsBin() {
|
|
|
1671
1979
|
if (!bin) {
|
|
1672
1980
|
throw new Error("The bundled `skills` package declares no `skills` binary. Re-install yourskills.");
|
|
1673
1981
|
}
|
|
1674
|
-
return join5(
|
|
1982
|
+
return join5(dirname4(pkgPath), bin);
|
|
1675
1983
|
}
|
|
1676
1984
|
function runSkills(argv) {
|
|
1677
1985
|
const bin = skillsBin();
|
|
@@ -1703,9 +2011,9 @@ function runSkills(argv) {
|
|
|
1703
2011
|
}
|
|
1704
2012
|
function pendingMessage(pending) {
|
|
1705
2013
|
const where = `${pending.repo}/${pending.path}`;
|
|
1706
|
-
return
|
|
2014
|
+
return `${pending.skillName} not in your catalog yet — opened a pull request against ${where}
|
|
1707
2015
|
` + ` ${pending.prUrl}
|
|
1708
|
-
` + " Merge it, then run this command again."
|
|
2016
|
+
` + " Merge it, then run this command again.";
|
|
1709
2017
|
}
|
|
1710
2018
|
function licenceQuestionMessage(name, licence) {
|
|
1711
2019
|
const repos = [...new Set(licence.upstreams.map((one) => one.repo))];
|
|
@@ -2185,7 +2493,7 @@ function InstallLine({ line, width }) {
|
|
|
2185
2493
|
})
|
|
2186
2494
|
]
|
|
2187
2495
|
}),
|
|
2188
|
-
|
|
2496
|
+
/* @__PURE__ */ jsxs9(Text9, {
|
|
2189
2497
|
children: [
|
|
2190
2498
|
/* @__PURE__ */ jsx10(Text9, {
|
|
2191
2499
|
children: " "
|
|
@@ -2195,7 +2503,7 @@ function InstallLine({ line, width }) {
|
|
|
2195
2503
|
children: line.vendor.prUrl
|
|
2196
2504
|
})
|
|
2197
2505
|
]
|
|
2198
|
-
})
|
|
2506
|
+
})
|
|
2199
2507
|
]
|
|
2200
2508
|
});
|
|
2201
2509
|
case "question":
|
|
@@ -2420,6 +2728,7 @@ function NoticeScreen({
|
|
|
2420
2728
|
}
|
|
2421
2729
|
var COLOURS = {
|
|
2422
2730
|
ok: "green",
|
|
2731
|
+
warn: tone.notice,
|
|
2423
2732
|
fail: tone.danger,
|
|
2424
2733
|
pending: tone.notice,
|
|
2425
2734
|
none: undefined
|
|
@@ -3298,7 +3607,7 @@ function shellFromEnv(shell) {
|
|
|
3298
3607
|
// src/doctor/doctor.ts
|
|
3299
3608
|
import { existsSync as existsSync5, readFileSync as readFileSync3 } from "node:fs";
|
|
3300
3609
|
import { homedir as homedir4 } from "node:os";
|
|
3301
|
-
import { dirname as
|
|
3610
|
+
import { dirname as dirname6, join as join8 } from "node:path";
|
|
3302
3611
|
|
|
3303
3612
|
// ../../node_modules/.bun/@trpc+client@11.18.0+acc6907a906fa511/node_modules/@trpc/client/dist/objectSpread2-BvkFp-_Y.mjs
|
|
3304
3613
|
var __create = Object.create;
|
|
@@ -4728,7 +5037,7 @@ var EXIT_CODES = [
|
|
|
4728
5037
|
[EXIT.usage, "the command line was wrong; the last line says the spelling"],
|
|
4729
5038
|
[EXIT.session, "needs a session: run `yourskills login`"]
|
|
4730
5039
|
];
|
|
4731
|
-
var
|
|
5040
|
+
var LOGIN2 = "yourskills login";
|
|
4732
5041
|
function needsTerminal(kind) {
|
|
4733
5042
|
if (kind === "login")
|
|
4734
5043
|
return new CliError("login needs a terminal", {
|
|
@@ -4752,7 +5061,7 @@ function describeError(error) {
|
|
|
4752
5061
|
const message = error instanceof Error ? error.message : String(error);
|
|
4753
5062
|
if (isUnauthorized(error)) {
|
|
4754
5063
|
return {
|
|
4755
|
-
lines: shape("you are not signed in", message.startsWith("Session") ? "the registry no longer accepts the token on this machine" : "the catalog needs a session, and there is no token on this machine",
|
|
5064
|
+
lines: shape("you are not signed in", message.startsWith("Session") ? "the registry no longer accepts the token on this machine" : "the catalog needs a session, and there is no token on this machine", LOGIN2),
|
|
4756
5065
|
code: EXIT.session
|
|
4757
5066
|
};
|
|
4758
5067
|
}
|
|
@@ -4871,7 +5180,7 @@ import { existsSync as existsSync2 } from "node:fs";
|
|
|
4871
5180
|
// src/hooks/settings.ts
|
|
4872
5181
|
import { mkdirSync as mkdirSync3 } from "node:fs";
|
|
4873
5182
|
import { readFile as readFile3, writeFile as writeFile2 } from "node:fs/promises";
|
|
4874
|
-
import { dirname as
|
|
5183
|
+
import { dirname as dirname5 } from "node:path";
|
|
4875
5184
|
function binaryName(command) {
|
|
4876
5185
|
const parts = command.split(/[\\/]/);
|
|
4877
5186
|
return (parts[parts.length - 1] ?? "").replace(/\.(js|mjs|cjs|exe)$/i, "");
|
|
@@ -4993,7 +5302,7 @@ function serialiseSettings(settings, indent) {
|
|
|
4993
5302
|
`;
|
|
4994
5303
|
}
|
|
4995
5304
|
async function writeSettingsFile(path, settings, indent) {
|
|
4996
|
-
mkdirSync3(
|
|
5305
|
+
mkdirSync3(dirname5(path), { recursive: true });
|
|
4997
5306
|
await writeFile2(path, serialiseSettings(settings, indent));
|
|
4998
5307
|
}
|
|
4999
5308
|
async function applyTo(path, change) {
|
|
@@ -5778,7 +6087,7 @@ async function hooksStatus() {
|
|
|
5778
6087
|
{ mark: "none", text: "tracking", note: tracking.note }
|
|
5779
6088
|
];
|
|
5780
6089
|
const agents = [];
|
|
5781
|
-
for (const agent of agentsToReport(
|
|
6090
|
+
for (const agent of agentsToReport(await resolveAgents())) {
|
|
5782
6091
|
const adapter = adapterFor(agent);
|
|
5783
6092
|
if (!adapter) {
|
|
5784
6093
|
lines.push({
|
|
@@ -5843,7 +6152,7 @@ import { realpathSync } from "node:fs";
|
|
|
5843
6152
|
// package.json
|
|
5844
6153
|
var package_default = {
|
|
5845
6154
|
name: "yourskills",
|
|
5846
|
-
version: "0.
|
|
6155
|
+
version: "0.5.0",
|
|
5847
6156
|
description: "Browse, install and keep up to date the agent skills and bundles your team publishes on yourskills.",
|
|
5848
6157
|
license: "UNLICENSED",
|
|
5849
6158
|
homepage: "https://yourskills.store",
|
|
@@ -6288,40 +6597,6 @@ async function runSessionStart() {
|
|
|
6288
6597
|
const outcome = await sessionStart();
|
|
6289
6598
|
emit(outcome, await upgradeCheck());
|
|
6290
6599
|
}
|
|
6291
|
-
// src/org/wire.ts
|
|
6292
|
-
var authBase2 = (base) => `${base.replace(/\/$/, "")}/api/auth`;
|
|
6293
|
-
async function activeOrganizationId(base, token) {
|
|
6294
|
-
const res = await fetch(`${authBase2(base)}/get-session`, {
|
|
6295
|
-
headers: { authorization: `Bearer ${token}` }
|
|
6296
|
-
});
|
|
6297
|
-
if (!res.ok)
|
|
6298
|
-
return null;
|
|
6299
|
-
const body = await res.json();
|
|
6300
|
-
return body?.session?.activeOrganizationId ?? null;
|
|
6301
|
-
}
|
|
6302
|
-
async function listOrganizations(base, token) {
|
|
6303
|
-
let res;
|
|
6304
|
-
try {
|
|
6305
|
-
res = await fetch(`${authBase2(base)}/organization/list`, {
|
|
6306
|
-
headers: { authorization: `Bearer ${token}` }
|
|
6307
|
-
});
|
|
6308
|
-
} catch (error) {
|
|
6309
|
-
throw new Error(`Could not reach ${base}: ${error instanceof Error ? error.message : String(error)}`);
|
|
6310
|
-
}
|
|
6311
|
-
if (!res.ok) {
|
|
6312
|
-
throw new Error(`Could not list organizations: ${res.status} ${res.statusText}`);
|
|
6313
|
-
}
|
|
6314
|
-
return await res.json();
|
|
6315
|
-
}
|
|
6316
|
-
async function rememberOrganization(name) {
|
|
6317
|
-
try {
|
|
6318
|
-
const config = await readConfig();
|
|
6319
|
-
if ((config.orgName ?? null) === name)
|
|
6320
|
-
return;
|
|
6321
|
-
await writeConfig(name === null ? { ...config, orgName: undefined } : { ...config, orgName: name });
|
|
6322
|
-
} catch {}
|
|
6323
|
-
}
|
|
6324
|
-
|
|
6325
6600
|
// src/org/org.ts
|
|
6326
6601
|
var NO_ACTIVE_ORGANIZATION = "none: choose one with `yourskills org <name>`, or in the web app";
|
|
6327
6602
|
async function namedOrganization(server) {
|
|
@@ -6330,8 +6605,8 @@ async function namedOrganization(server) {
|
|
|
6330
6605
|
return view.organization;
|
|
6331
6606
|
}
|
|
6332
6607
|
async function whoamiReport(server) {
|
|
6333
|
-
const who = await
|
|
6334
|
-
const organization = who.
|
|
6608
|
+
const who = await readSession({ server });
|
|
6609
|
+
const organization = who.organizationId ? await namedOrganization(server) : null;
|
|
6335
6610
|
await rememberOrganization(organization?.name ?? null);
|
|
6336
6611
|
return drawWhoami({ user: who.user, server: who.server, organization });
|
|
6337
6612
|
}
|
|
@@ -6369,152 +6644,167 @@ function rows(orgs, active) {
|
|
|
6369
6644
|
}
|
|
6370
6645
|
async function learnActiveOrganization(server) {
|
|
6371
6646
|
try {
|
|
6372
|
-
|
|
6373
|
-
const [orgs, active] = await Promise.all([
|
|
6374
|
-
listOrganizations(base, token),
|
|
6375
|
-
activeOrganizationId(base, token)
|
|
6376
|
-
]);
|
|
6377
|
-
const name = orgs.find((o) => o.id === active)?.name ?? null;
|
|
6378
|
-
await rememberOrganization(name);
|
|
6379
|
-
return name;
|
|
6647
|
+
return (await organizations({ server })).active?.name ?? null;
|
|
6380
6648
|
} catch {
|
|
6381
6649
|
return null;
|
|
6382
6650
|
}
|
|
6383
6651
|
}
|
|
6384
6652
|
async function organizationChoices(server) {
|
|
6385
|
-
const {
|
|
6386
|
-
|
|
6387
|
-
listOrganizations(base, token),
|
|
6388
|
-
activeOrganizationId(base, token)
|
|
6389
|
-
]);
|
|
6390
|
-
await rememberOrganization(orgs.find((o) => o.id === active)?.name ?? null);
|
|
6391
|
-
return orgs.map((org) => ({
|
|
6653
|
+
const { all, active } = await organizations({ server });
|
|
6654
|
+
return all.map((org) => ({
|
|
6392
6655
|
name: org.name,
|
|
6393
6656
|
slug: org.slug ?? null,
|
|
6394
|
-
active: org.id === active
|
|
6657
|
+
active: org.id === active?.id
|
|
6395
6658
|
}));
|
|
6396
6659
|
}
|
|
6397
6660
|
async function orgListReport(server) {
|
|
6398
|
-
const {
|
|
6399
|
-
const [orgs, active] = await Promise.all([
|
|
6400
|
-
listOrganizations(base, token),
|
|
6401
|
-
activeOrganizationId(base, token)
|
|
6402
|
-
]);
|
|
6403
|
-
await rememberOrganization(orgs.find((o) => o.id === active)?.name ?? null);
|
|
6661
|
+
const { all, active } = await organizations({ server });
|
|
6404
6662
|
return {
|
|
6405
6663
|
title: "org",
|
|
6406
|
-
lines: rows(
|
|
6664
|
+
lines: rows(all, active?.id ?? null),
|
|
6407
6665
|
json: {
|
|
6408
|
-
active,
|
|
6409
|
-
organizations:
|
|
6666
|
+
active: active?.id ?? null,
|
|
6667
|
+
organizations: all.map((o) => ({
|
|
6410
6668
|
id: o.id,
|
|
6411
6669
|
name: o.name,
|
|
6412
6670
|
slug: o.slug ?? null,
|
|
6413
|
-
active: o.id === active
|
|
6671
|
+
active: o.id === active?.id
|
|
6414
6672
|
}))
|
|
6415
6673
|
}
|
|
6416
6674
|
};
|
|
6417
6675
|
}
|
|
6418
|
-
|
|
6419
|
-
|
|
6420
|
-
|
|
6421
|
-
|
|
6422
|
-
|
|
6423
|
-
|
|
6424
|
-
|
|
6425
|
-
|
|
6426
|
-
|
|
6427
|
-
|
|
6428
|
-
|
|
6429
|
-
|
|
6430
|
-
|
|
6676
|
+
function drawSwitch(name, outcome) {
|
|
6677
|
+
switch (outcome.kind) {
|
|
6678
|
+
case "unknown":
|
|
6679
|
+
return {
|
|
6680
|
+
title: "org",
|
|
6681
|
+
lines: [
|
|
6682
|
+
{
|
|
6683
|
+
mark: "fail",
|
|
6684
|
+
text: `No organization called ${name}.`,
|
|
6685
|
+
note: outcome.offered.map((o) => o.name).join(", ") || "you are in none"
|
|
6686
|
+
}
|
|
6687
|
+
],
|
|
6688
|
+
json: { error: `unknown organization: ${name}` },
|
|
6689
|
+
failed: true
|
|
6690
|
+
};
|
|
6691
|
+
case "refused":
|
|
6692
|
+
return {
|
|
6693
|
+
title: "org",
|
|
6694
|
+
lines: [
|
|
6695
|
+
{
|
|
6696
|
+
mark: "fail",
|
|
6697
|
+
text: `Could not switch to ${outcome.organization.name}.`,
|
|
6698
|
+
note: outcome.detail
|
|
6699
|
+
}
|
|
6700
|
+
],
|
|
6701
|
+
json: { error: outcome.detail },
|
|
6702
|
+
failed: true
|
|
6703
|
+
};
|
|
6704
|
+
case "switched": {
|
|
6705
|
+
const { organization } = outcome;
|
|
6706
|
+
return {
|
|
6707
|
+
title: "org",
|
|
6708
|
+
lines: [
|
|
6709
|
+
{ mark: "ok", text: `Active organization: ${organization.name}` }
|
|
6710
|
+
],
|
|
6711
|
+
json: {
|
|
6712
|
+
active: organization.id,
|
|
6713
|
+
name: organization.name,
|
|
6714
|
+
slug: organization.slug ?? null
|
|
6431
6715
|
}
|
|
6432
|
-
|
|
6433
|
-
|
|
6434
|
-
failed: true
|
|
6435
|
-
};
|
|
6716
|
+
};
|
|
6717
|
+
}
|
|
6436
6718
|
}
|
|
6437
|
-
|
|
6438
|
-
|
|
6439
|
-
|
|
6440
|
-
|
|
6441
|
-
|
|
6442
|
-
|
|
6443
|
-
|
|
6444
|
-
|
|
6445
|
-
|
|
6446
|
-
|
|
6719
|
+
}
|
|
6720
|
+
async function orgSwitchReport(name, server) {
|
|
6721
|
+
return drawSwitch(name, await switchOrganization(name, { server }));
|
|
6722
|
+
}
|
|
6723
|
+
// src/doctor/check.ts
|
|
6724
|
+
function fixable(checks) {
|
|
6725
|
+
return checks.filter((check) => check.state === "fail" && check.fix?.apply !== undefined);
|
|
6726
|
+
}
|
|
6727
|
+
var MARK = {
|
|
6728
|
+
pass: "ok",
|
|
6729
|
+
warn: "warn",
|
|
6730
|
+
fail: "fail"
|
|
6731
|
+
};
|
|
6732
|
+
function markOf(state) {
|
|
6733
|
+
return MARK[state];
|
|
6734
|
+
}
|
|
6735
|
+
function blocks(checks) {
|
|
6736
|
+
return checks.some((check) => check.state === "fail");
|
|
6737
|
+
}
|
|
6738
|
+
function nextFor2(checks, fixing) {
|
|
6739
|
+
const fixes = fixable(checks);
|
|
6740
|
+
if (!fixing && fixes.length > 0)
|
|
6447
6741
|
return {
|
|
6448
|
-
|
|
6449
|
-
|
|
6450
|
-
{
|
|
6451
|
-
mark: "fail",
|
|
6452
|
-
text: `Could not switch to ${match.name}.`,
|
|
6453
|
-
note: detail || `${res.status} ${res.statusText}`
|
|
6454
|
-
}
|
|
6455
|
-
],
|
|
6456
|
-
json: { error: detail || res.statusText },
|
|
6457
|
-
failed: true
|
|
6742
|
+
command: "yourskills doctor --fix",
|
|
6743
|
+
why: `repair ${fixes.length === 1 ? "it" : "them"}`
|
|
6458
6744
|
};
|
|
6459
|
-
|
|
6460
|
-
|
|
6461
|
-
|
|
6462
|
-
|
|
6463
|
-
|
|
6464
|
-
|
|
6465
|
-
};
|
|
6745
|
+
const names = (name) => checks.some((check) => check.fix?.name === name);
|
|
6746
|
+
if (names("login"))
|
|
6747
|
+
return { command: "yourskills login", why: "start a session" };
|
|
6748
|
+
if (names("upgrade"))
|
|
6749
|
+
return { command: "yourskills upgrade", why: "get the newer yourskills" };
|
|
6750
|
+
return;
|
|
6466
6751
|
}
|
|
6752
|
+
|
|
6467
6753
|
// src/doctor/doctor.ts
|
|
6468
|
-
var REACH_TIMEOUT_MS = 5000;
|
|
6469
6754
|
var liveDiagnosis = async (server) => (await createClient(server)).downstream.diagnosis.query();
|
|
6470
|
-
function withLogin(message) {
|
|
6471
|
-
return message.includes("yourskills login") ? message : `${message} — run \`yourskills login\``;
|
|
6472
|
-
}
|
|
6473
6755
|
function home() {
|
|
6474
6756
|
const env = process.env.HOME;
|
|
6475
6757
|
return env && env.length > 0 ? env : homedir4();
|
|
6476
6758
|
}
|
|
6477
|
-
|
|
6478
|
-
|
|
6479
|
-
|
|
6480
|
-
|
|
6481
|
-
|
|
6482
|
-
|
|
6483
|
-
|
|
6484
|
-
|
|
6485
|
-
|
|
6486
|
-
|
|
6487
|
-
|
|
6488
|
-
|
|
6489
|
-
|
|
6490
|
-
|
|
6491
|
-
|
|
6492
|
-
|
|
6493
|
-
};
|
|
6494
|
-
}
|
|
6759
|
+
function said(error) {
|
|
6760
|
+
return error instanceof Error ? error.message : String(error);
|
|
6761
|
+
}
|
|
6762
|
+
async function reached(server) {
|
|
6763
|
+
const answer = await reachable({ server });
|
|
6764
|
+
return answer.status === null ? {
|
|
6765
|
+
name: "server reachable",
|
|
6766
|
+
state: "fail",
|
|
6767
|
+
summary: `${answer.base}: ${answer.said}`,
|
|
6768
|
+
fix: null
|
|
6769
|
+
} : {
|
|
6770
|
+
name: "server reachable",
|
|
6771
|
+
state: "pass",
|
|
6772
|
+
summary: `${answer.base} (${answer.status})`,
|
|
6773
|
+
fix: null
|
|
6774
|
+
};
|
|
6495
6775
|
}
|
|
6496
6776
|
async function session(ask, server) {
|
|
6497
6777
|
try {
|
|
6498
|
-
const who = await
|
|
6778
|
+
const who = await readSession({ server });
|
|
6779
|
+
const id = who.organizationId;
|
|
6499
6780
|
return [
|
|
6500
|
-
{ name: "session valid",
|
|
6781
|
+
{ name: "session valid", state: "pass", summary: who.user, fix: null },
|
|
6501
6782
|
{
|
|
6502
6783
|
name: "active organization",
|
|
6503
|
-
|
|
6504
|
-
|
|
6784
|
+
state: id ? "pass" : "fail",
|
|
6785
|
+
summary: id ? (await ask(server).catch(() => null))?.name ?? id : NO_ACTIVE_ORGANIZATION,
|
|
6786
|
+
fix: null
|
|
6505
6787
|
}
|
|
6506
6788
|
];
|
|
6507
6789
|
} catch (error) {
|
|
6790
|
+
const refused = isSessionRefusal(error);
|
|
6508
6791
|
return [
|
|
6509
|
-
{
|
|
6792
|
+
refused ? {
|
|
6793
|
+
name: "session valid",
|
|
6794
|
+
state: "fail",
|
|
6795
|
+
summary: error.fact,
|
|
6796
|
+
fix: { name: "login", sentence: `run \`${error.next}\`` }
|
|
6797
|
+
} : {
|
|
6510
6798
|
name: "session valid",
|
|
6511
|
-
|
|
6512
|
-
|
|
6799
|
+
state: "fail",
|
|
6800
|
+
summary: said(error),
|
|
6801
|
+
fix: null
|
|
6513
6802
|
},
|
|
6514
6803
|
{
|
|
6515
6804
|
name: "active organization",
|
|
6516
|
-
|
|
6517
|
-
|
|
6805
|
+
state: "fail",
|
|
6806
|
+
summary: refused ? "unknown until you are logged in" : "not asked: the session could not be read",
|
|
6807
|
+
fix: null
|
|
6518
6808
|
}
|
|
6519
6809
|
];
|
|
6520
6810
|
}
|
|
@@ -6522,27 +6812,42 @@ async function session(ask, server) {
|
|
|
6522
6812
|
function configParses() {
|
|
6523
6813
|
const path = configPath();
|
|
6524
6814
|
if (!existsSync5(path))
|
|
6525
|
-
return {
|
|
6815
|
+
return {
|
|
6816
|
+
name: "config",
|
|
6817
|
+
state: "pass",
|
|
6818
|
+
summary: `${path} (not created yet)`,
|
|
6819
|
+
fix: null
|
|
6820
|
+
};
|
|
6526
6821
|
try {
|
|
6527
6822
|
JSON.parse(readFileSync3(path, "utf8"));
|
|
6528
|
-
return { name: "config",
|
|
6823
|
+
return { name: "config", state: "pass", summary: path, fix: null };
|
|
6529
6824
|
} catch (error) {
|
|
6530
6825
|
return {
|
|
6531
6826
|
name: "config",
|
|
6532
|
-
|
|
6533
|
-
|
|
6827
|
+
state: "fail",
|
|
6828
|
+
summary: `${path}: ${said(error)}`,
|
|
6829
|
+
fix: {
|
|
6830
|
+
name: "by-hand",
|
|
6831
|
+
sentence: "fix it by hand, or move it aside and log in again"
|
|
6832
|
+
}
|
|
6534
6833
|
};
|
|
6535
6834
|
}
|
|
6536
6835
|
}
|
|
6537
6836
|
function skillsResolvable() {
|
|
6538
6837
|
try {
|
|
6539
6838
|
const bin = skillsBin();
|
|
6540
|
-
return existsSync5(bin) ? { name: "skills dependency",
|
|
6839
|
+
return existsSync5(bin) ? { name: "skills dependency", state: "pass", summary: bin, fix: null } : {
|
|
6840
|
+
name: "skills dependency",
|
|
6841
|
+
state: "fail",
|
|
6842
|
+
summary: `missing: ${bin}`,
|
|
6843
|
+
fix: null
|
|
6844
|
+
};
|
|
6541
6845
|
} catch (error) {
|
|
6542
6846
|
return {
|
|
6543
6847
|
name: "skills dependency",
|
|
6544
|
-
|
|
6545
|
-
|
|
6848
|
+
state: "fail",
|
|
6849
|
+
summary: said(error),
|
|
6850
|
+
fix: null
|
|
6546
6851
|
};
|
|
6547
6852
|
}
|
|
6548
6853
|
}
|
|
@@ -6551,7 +6856,7 @@ async function agents(nearest, local) {
|
|
|
6551
6856
|
const found = local ? [] : [
|
|
6552
6857
|
...new Set((await listInstalled(nearest.scope)).flatMap((row) => row.agents))
|
|
6553
6858
|
];
|
|
6554
|
-
const
|
|
6859
|
+
const sentences = [
|
|
6555
6860
|
pinned.length > 0 ? `pinned: ${pinned.join(", ")}` : "",
|
|
6556
6861
|
found.length > 0 ? `found: ${found.join(", ")} (the stock CLI's labels)` : ""
|
|
6557
6862
|
].filter(Boolean);
|
|
@@ -6562,15 +6867,25 @@ async function agents(nearest, local) {
|
|
|
6562
6867
|
const hint = suggest.length > 0 ? `did you mean ${suggest.join(", ")}?` : "run `yourskills config set agents <name>` with a name the stock CLI knows";
|
|
6563
6868
|
return {
|
|
6564
6869
|
name: "agents",
|
|
6565
|
-
|
|
6566
|
-
|
|
6870
|
+
state: "fail",
|
|
6871
|
+
summary: `no agent is called ${unknown.join(", ")}: every install would stop at it`,
|
|
6872
|
+
fix: { name: "pin-agent", sentence: hint }
|
|
6567
6873
|
};
|
|
6568
6874
|
}
|
|
6569
6875
|
}
|
|
6570
|
-
return {
|
|
6876
|
+
return sentences.length > 0 ? {
|
|
6877
|
+
name: "agents",
|
|
6878
|
+
state: "pass",
|
|
6879
|
+
summary: sentences.join(" "),
|
|
6880
|
+
fix: null
|
|
6881
|
+
} : {
|
|
6571
6882
|
name: "agents",
|
|
6572
|
-
|
|
6573
|
-
|
|
6883
|
+
state: "fail",
|
|
6884
|
+
summary: "none",
|
|
6885
|
+
fix: {
|
|
6886
|
+
name: "pin-agent",
|
|
6887
|
+
sentence: "pin one with `yourskills config set agents claude-code`"
|
|
6888
|
+
}
|
|
6574
6889
|
};
|
|
6575
6890
|
}
|
|
6576
6891
|
function hookCheck(gap) {
|
|
@@ -6578,22 +6893,28 @@ function hookCheck(gap) {
|
|
|
6578
6893
|
if (gap.error)
|
|
6579
6894
|
return {
|
|
6580
6895
|
name,
|
|
6581
|
-
|
|
6582
|
-
|
|
6896
|
+
state: "fail",
|
|
6897
|
+
summary: `${gap.path}: ${gap.error}`,
|
|
6898
|
+
fix: {
|
|
6899
|
+
name: "by-hand",
|
|
6900
|
+
sentence: "left untouched; fix the JSON by hand"
|
|
6901
|
+
}
|
|
6583
6902
|
};
|
|
6584
6903
|
if (gap.missing.length === 0)
|
|
6585
6904
|
return {
|
|
6586
6905
|
name,
|
|
6587
|
-
|
|
6588
|
-
|
|
6906
|
+
state: "pass",
|
|
6907
|
+
summary: `${gap.registered.join(" + ")} ${gap.path}`,
|
|
6908
|
+
fix: null
|
|
6589
6909
|
};
|
|
6590
6910
|
const kinds = gap.missing.join(" + ");
|
|
6591
6911
|
return {
|
|
6592
6912
|
name,
|
|
6593
|
-
|
|
6594
|
-
|
|
6913
|
+
state: "fail",
|
|
6914
|
+
summary: `${kinds} not registered`,
|
|
6595
6915
|
fix: {
|
|
6596
|
-
|
|
6916
|
+
name: "register-hooks",
|
|
6917
|
+
sentence: `register ${kinds} in ${gap.path}`,
|
|
6597
6918
|
apply: () => registerMissing(gap)
|
|
6598
6919
|
}
|
|
6599
6920
|
};
|
|
@@ -6602,26 +6923,30 @@ function manifestParses(nearest) {
|
|
|
6602
6923
|
if (!existsSync5(nearest.path)) {
|
|
6603
6924
|
return {
|
|
6604
6925
|
name: "manifest",
|
|
6605
|
-
|
|
6606
|
-
|
|
6926
|
+
state: "pass",
|
|
6927
|
+
summary: `${nearest.path} (not created yet)`,
|
|
6928
|
+
fix: null
|
|
6607
6929
|
};
|
|
6608
6930
|
}
|
|
6609
6931
|
try {
|
|
6610
6932
|
const raw = JSON.parse(readFileSync3(nearest.path, "utf8"));
|
|
6611
6933
|
return raw && typeof raw === "object" ? {
|
|
6612
6934
|
name: "manifest",
|
|
6613
|
-
|
|
6614
|
-
|
|
6935
|
+
state: "pass",
|
|
6936
|
+
summary: `${nearest.path} (${nearest.scope})`,
|
|
6937
|
+
fix: null
|
|
6615
6938
|
} : {
|
|
6616
6939
|
name: "manifest",
|
|
6617
|
-
|
|
6618
|
-
|
|
6940
|
+
state: "fail",
|
|
6941
|
+
summary: `${nearest.path}: not an object`,
|
|
6942
|
+
fix: null
|
|
6619
6943
|
};
|
|
6620
6944
|
} catch (error) {
|
|
6621
6945
|
return {
|
|
6622
6946
|
name: "manifest",
|
|
6623
|
-
|
|
6624
|
-
|
|
6947
|
+
state: "fail",
|
|
6948
|
+
summary: `${nearest.path}: ${said(error)}`,
|
|
6949
|
+
fix: null
|
|
6625
6950
|
};
|
|
6626
6951
|
}
|
|
6627
6952
|
}
|
|
@@ -6629,7 +6954,7 @@ function onDisk(nearest, name) {
|
|
|
6629
6954
|
const entry = nearest.manifest.skills[name];
|
|
6630
6955
|
if (entry?.unpinned)
|
|
6631
6956
|
return existsSync5(entry.path);
|
|
6632
|
-
const root = nearest.scope === "project" ?
|
|
6957
|
+
const root = nearest.scope === "project" ? dirname6(nearest.path) : home();
|
|
6633
6958
|
return existsSync5(join8(root, ".agents", "skills", name));
|
|
6634
6959
|
}
|
|
6635
6960
|
function manifestMatchesDisk(nearest) {
|
|
@@ -6638,16 +6963,18 @@ function manifestMatchesDisk(nearest) {
|
|
|
6638
6963
|
if (missing.length === 0)
|
|
6639
6964
|
return {
|
|
6640
6965
|
name: "manifest matches disk",
|
|
6641
|
-
|
|
6642
|
-
|
|
6966
|
+
state: "pass",
|
|
6967
|
+
summary: `${names.length} skill(s) recorded`,
|
|
6968
|
+
fix: null
|
|
6643
6969
|
};
|
|
6644
6970
|
const list = missing.join(", ");
|
|
6645
6971
|
return {
|
|
6646
6972
|
name: "manifest matches disk",
|
|
6647
|
-
|
|
6648
|
-
|
|
6973
|
+
state: "fail",
|
|
6974
|
+
summary: `missing on disk: ${list}`,
|
|
6649
6975
|
fix: {
|
|
6650
|
-
|
|
6976
|
+
name: "forget",
|
|
6977
|
+
sentence: `forget ${list} in ${nearest.path}, or \`yourskills install\` to restore ${missing.length === 1 ? "it" : "them"}`,
|
|
6651
6978
|
apply: async () => {
|
|
6652
6979
|
openManifest({ path: nearest.path, scope: nearest.scope }).forget(missing);
|
|
6653
6980
|
return `forgot ${list}; \`yourskills add <name>\` brings one back`;
|
|
@@ -6661,8 +6988,9 @@ function unpinned(nearest) {
|
|
|
6661
6988
|
return null;
|
|
6662
6989
|
return {
|
|
6663
6990
|
name: "unpinned",
|
|
6664
|
-
|
|
6665
|
-
|
|
6991
|
+
state: "warn",
|
|
6992
|
+
summary: `${names.join(", ")}: on this machine, not in your catalog; nothing keeps ${names.length === 1 ? "it" : "them"} up to date`,
|
|
6993
|
+
fix: null
|
|
6666
6994
|
};
|
|
6667
6995
|
}
|
|
6668
6996
|
async function downstreamChecks(ask, server) {
|
|
@@ -6673,8 +7001,9 @@ async function downstreamChecks(ask, server) {
|
|
|
6673
7001
|
return [
|
|
6674
7002
|
{
|
|
6675
7003
|
name: "downstream",
|
|
6676
|
-
|
|
6677
|
-
|
|
7004
|
+
state: "fail",
|
|
7005
|
+
summary: `could not ask the registry: ${said(error)}`,
|
|
7006
|
+
fix: null
|
|
6678
7007
|
}
|
|
6679
7008
|
];
|
|
6680
7009
|
}
|
|
@@ -6682,21 +7011,24 @@ async function downstreamChecks(ask, server) {
|
|
|
6682
7011
|
return [
|
|
6683
7012
|
{
|
|
6684
7013
|
name: "downstream",
|
|
6685
|
-
|
|
6686
|
-
|
|
7014
|
+
state: "fail",
|
|
7015
|
+
summary: `${answer.headline}: nothing you install can be vendored until one is set`,
|
|
7016
|
+
fix: null
|
|
6687
7017
|
}
|
|
6688
7018
|
];
|
|
6689
7019
|
}
|
|
6690
7020
|
return [
|
|
6691
7021
|
{
|
|
6692
7022
|
name: "downstream",
|
|
6693
|
-
|
|
6694
|
-
|
|
7023
|
+
state: answer.tone,
|
|
7024
|
+
summary: `${answer.repo.repo}: ${answer.headline}`,
|
|
7025
|
+
fix: null
|
|
6695
7026
|
},
|
|
6696
7027
|
...answer.checks.map((check) => ({
|
|
6697
7028
|
name: `downstream: ${check.key}`,
|
|
6698
|
-
|
|
6699
|
-
|
|
7029
|
+
state: check.state,
|
|
7030
|
+
summary: check.summary,
|
|
7031
|
+
fix: check.fix
|
|
6700
7032
|
}))
|
|
6701
7033
|
];
|
|
6702
7034
|
}
|
|
@@ -6709,16 +7041,18 @@ async function unwatchedCheck(nearest, ask, server) {
|
|
|
6709
7041
|
} catch (error) {
|
|
6710
7042
|
return {
|
|
6711
7043
|
name,
|
|
6712
|
-
|
|
6713
|
-
|
|
7044
|
+
state: "fail",
|
|
7045
|
+
summary: `could not ask the registry: ${said(error)}`,
|
|
7046
|
+
fix: null
|
|
6714
7047
|
};
|
|
6715
7048
|
}
|
|
6716
7049
|
const orphans = drift.unknown.filter((row) => row.reason === "orphaned");
|
|
6717
7050
|
if (orphans.length === 0)
|
|
6718
7051
|
return {
|
|
6719
7052
|
name,
|
|
6720
|
-
|
|
6721
|
-
|
|
7053
|
+
state: "pass",
|
|
7054
|
+
summary: "every installed skill came from a source this organization still connects",
|
|
7055
|
+
fix: null
|
|
6722
7056
|
};
|
|
6723
7057
|
const names = orphans.map((row) => row.name);
|
|
6724
7058
|
const repos = [
|
|
@@ -6727,8 +7061,12 @@ async function unwatchedCheck(nearest, ask, server) {
|
|
|
6727
7061
|
const one = names.length === 1;
|
|
6728
7062
|
return {
|
|
6729
7063
|
name,
|
|
6730
|
-
|
|
6731
|
-
|
|
7064
|
+
state: "warn",
|
|
7065
|
+
summary: `${names.join(", ")}: installed from ${repos.join(", ")}, which this ` + "organization does not connect any more, so nothing is watching " + `${repos.length === 1 ? "it" : "them"} for changes. The ` + `${one ? "copy on this machine goes" : "copies on this machine go"} on ` + "working.",
|
|
7066
|
+
fix: {
|
|
7067
|
+
name: "unwatched",
|
|
7068
|
+
sentence: "A team member can reconnect the source or vendor the skill into your " + "catalog, and then `yourskills update --all` moves this machine onto it."
|
|
7069
|
+
}
|
|
6732
7070
|
};
|
|
6733
7071
|
}
|
|
6734
7072
|
function version(config) {
|
|
@@ -6736,20 +7074,23 @@ function version(config) {
|
|
|
6736
7074
|
if (latest && isNewer(latest, VERSION))
|
|
6737
7075
|
return {
|
|
6738
7076
|
name: "version",
|
|
6739
|
-
|
|
6740
|
-
|
|
7077
|
+
state: "warn",
|
|
7078
|
+
summary: VERSION,
|
|
7079
|
+
fix: {
|
|
7080
|
+
name: "upgrade",
|
|
7081
|
+
sentence: `${latest} is out; run \`yourskills upgrade\``
|
|
7082
|
+
}
|
|
6741
7083
|
};
|
|
6742
|
-
return { name: "version",
|
|
7084
|
+
return { name: "version", state: "pass", summary: VERSION, fix: null };
|
|
6743
7085
|
}
|
|
6744
7086
|
async function diagnose(nearest, options = {}) {
|
|
6745
7087
|
const config = await readConfig();
|
|
6746
|
-
const base = serverUrl(config, options.server);
|
|
6747
7088
|
const local = options.local ?? false;
|
|
6748
7089
|
const checks = [configParses()];
|
|
6749
7090
|
if (!local) {
|
|
6750
7091
|
const account = await session(options.askOrganization ?? namedOrganization, options.server);
|
|
6751
|
-
checks.push(await
|
|
6752
|
-
if (account[0].
|
|
7092
|
+
checks.push(await reached(options.server), ...account);
|
|
7093
|
+
if (account[0].state !== "fail") {
|
|
6753
7094
|
checks.push(...await downstreamChecks(options.ask ?? liveDiagnosis, options.server), await unwatchedCheck(nearest, options.askDrift ?? liveDrift, options.server));
|
|
6754
7095
|
}
|
|
6755
7096
|
}
|
|
@@ -6762,53 +7103,41 @@ async function diagnose(nearest, options = {}) {
|
|
|
6762
7103
|
checks.push(version(config));
|
|
6763
7104
|
return checks;
|
|
6764
7105
|
}
|
|
6765
|
-
function fixable(checks) {
|
|
6766
|
-
return checks.filter((check) => !check.ok && check.fix !== undefined);
|
|
6767
|
-
}
|
|
6768
7106
|
async function applyFixes(checks) {
|
|
6769
7107
|
const out = [];
|
|
6770
7108
|
for (const check of checks) {
|
|
6771
|
-
|
|
7109
|
+
const [own] = fixable([check]);
|
|
7110
|
+
if (!own) {
|
|
6772
7111
|
out.push(check);
|
|
6773
7112
|
continue;
|
|
6774
7113
|
}
|
|
6775
|
-
|
|
6776
|
-
const detail = await check.fix.apply();
|
|
6777
|
-
out.push({ ...check, ok: true, fixed: { ok: true, detail } });
|
|
6778
|
-
} catch (error) {
|
|
6779
|
-
out.push({
|
|
6780
|
-
...check,
|
|
6781
|
-
fixed: {
|
|
6782
|
-
ok: false,
|
|
6783
|
-
detail: error instanceof Error ? error.message : String(error)
|
|
6784
|
-
}
|
|
6785
|
-
});
|
|
6786
|
-
}
|
|
7114
|
+
out.push(await applied(own));
|
|
6787
7115
|
}
|
|
6788
7116
|
return out;
|
|
6789
7117
|
}
|
|
6790
|
-
function
|
|
6791
|
-
|
|
6792
|
-
|
|
6793
|
-
|
|
6794
|
-
|
|
6795
|
-
};
|
|
6796
|
-
|
|
6797
|
-
|
|
6798
|
-
|
|
6799
|
-
|
|
6800
|
-
|
|
7118
|
+
async function applied(check) {
|
|
7119
|
+
try {
|
|
7120
|
+
const detail = await check.fix.apply();
|
|
7121
|
+
return { ...check, state: "pass", fixed: { ok: true, detail } };
|
|
7122
|
+
} catch (error) {
|
|
7123
|
+
return { ...check, fixed: { ok: false, detail: said(error) } };
|
|
7124
|
+
}
|
|
7125
|
+
}
|
|
7126
|
+
function noteFor(check, fixing) {
|
|
7127
|
+
if (check.fixed)
|
|
7128
|
+
return check.fixed.ok ? `fixed: ${check.fixed.detail}` : `${check.summary} could not fix: ${check.fixed.detail}`;
|
|
7129
|
+
if (!check.fix)
|
|
7130
|
+
return check.summary;
|
|
7131
|
+
const own = check.fix.apply && !fixing ? " [fixable]" : "";
|
|
7132
|
+
return `${check.summary} ${check.fix.sentence}${own}`;
|
|
6801
7133
|
}
|
|
6802
7134
|
function doctorLines(checks, fixing = false) {
|
|
6803
|
-
const width = Math.max(...checks.map((check) => check.name.length));
|
|
6804
|
-
const lines = checks.map((check) => {
|
|
6805
|
-
|
|
6806
|
-
|
|
6807
|
-
|
|
6808
|
-
|
|
6809
|
-
note
|
|
6810
|
-
};
|
|
6811
|
-
});
|
|
7135
|
+
const width = Math.max(...checks.map((check) => check.name.length));
|
|
7136
|
+
const lines = checks.map((check) => ({
|
|
7137
|
+
mark: markOf(check.state),
|
|
7138
|
+
text: check.name.padEnd(width),
|
|
7139
|
+
note: noteFor(check, fixing)
|
|
7140
|
+
}));
|
|
6812
7141
|
const next = nextFor2(checks, fixing);
|
|
6813
7142
|
return {
|
|
6814
7143
|
title: fixing ? "doctor --fix" : "doctor",
|
|
@@ -6816,11 +7145,12 @@ function doctorLines(checks, fixing = false) {
|
|
|
6816
7145
|
json: {
|
|
6817
7146
|
checks: checks.map(({ fix, ...rest }) => ({
|
|
6818
7147
|
...rest,
|
|
6819
|
-
|
|
7148
|
+
fix: fix ? { name: fix.name, sentence: fix.sentence } : null,
|
|
7149
|
+
fixable: fix?.apply !== undefined
|
|
6820
7150
|
})),
|
|
6821
7151
|
fixed: checks.filter((check) => check.fixed?.ok).map((check) => check.fixed?.detail)
|
|
6822
7152
|
},
|
|
6823
|
-
failed: checks
|
|
7153
|
+
failed: blocks(checks),
|
|
6824
7154
|
...next ? { next } : {}
|
|
6825
7155
|
};
|
|
6826
7156
|
}
|
|
@@ -6832,38 +7162,6 @@ async function doctorReport(nearest, options = {}) {
|
|
|
6832
7162
|
return { ...report, json: { ...report.json, manifest: nearest.path } };
|
|
6833
7163
|
}
|
|
6834
7164
|
// src/doctor/explain.ts
|
|
6835
|
-
import { existsSync as existsSync6 } from "node:fs";
|
|
6836
|
-
import { dirname as dirname6 } from "node:path";
|
|
6837
|
-
function scopeLayers(resolved, manifestScope, config) {
|
|
6838
|
-
const project = resolved.manifestPath ? {
|
|
6839
|
-
layer: "project",
|
|
6840
|
-
value: manifestScope,
|
|
6841
|
-
from: `yourskills.json at ${dirname6(resolved.manifestPath)}${manifestScope ? "" : " (declares no scope)"}`
|
|
6842
|
-
} : { layer: "project", value: null, from: "no yourskills.json above here" };
|
|
6843
|
-
return [
|
|
6844
|
-
{
|
|
6845
|
-
layer: "flag",
|
|
6846
|
-
value: resolved.source === "flag" ? "given" : null,
|
|
6847
|
-
from: "-g or -p on the command line"
|
|
6848
|
-
},
|
|
6849
|
-
project,
|
|
6850
|
-
{
|
|
6851
|
-
layer: "config",
|
|
6852
|
-
value: config.defaultScope ?? null,
|
|
6853
|
-
from: `defaultScope in ${configPath()}`
|
|
6854
|
-
},
|
|
6855
|
-
{ layer: "default", value: "global", from: "built-in default" }
|
|
6856
|
-
];
|
|
6857
|
-
}
|
|
6858
|
-
function serverFrom(config, override) {
|
|
6859
|
-
if (override)
|
|
6860
|
-
return "--server on the command line";
|
|
6861
|
-
if (process.env.YOURSKILLS_URL)
|
|
6862
|
-
return "YOURSKILLS_URL in the environment";
|
|
6863
|
-
if (config.serverUrl)
|
|
6864
|
-
return `server in ${configPath()}`;
|
|
6865
|
-
return "built-in default";
|
|
6866
|
-
}
|
|
6867
7165
|
var LABEL = 12;
|
|
6868
7166
|
function row(label, value, from) {
|
|
6869
7167
|
return {
|
|
@@ -6872,36 +7170,32 @@ function row(label, value, from) {
|
|
|
6872
7170
|
note: `<- ${from}`
|
|
6873
7171
|
};
|
|
6874
7172
|
}
|
|
6875
|
-
|
|
6876
|
-
|
|
7173
|
+
var LOSER = LABEL + 22;
|
|
7174
|
+
function losers(setting) {
|
|
7175
|
+
return setting.layers.filter((layer) => layer.layer !== setting.layer).map((layer) => ({
|
|
6877
7176
|
mark: "none",
|
|
6878
|
-
text: `${"".padEnd(LABEL)}(${layer.layer}: ${layer.value ?? "unset"}`.padEnd(
|
|
7177
|
+
text: `${"".padEnd(LABEL)}(${layer.layer}: ${layer.value ?? "unset"}`.padEnd(LOSER),
|
|
6879
7178
|
note: `${layer.from})`
|
|
6880
7179
|
}));
|
|
6881
7180
|
}
|
|
7181
|
+
function settingRows(settings) {
|
|
7182
|
+
return settings.flatMap((setting) => [
|
|
7183
|
+
row(setting.key, setting.value, setting.from),
|
|
7184
|
+
...losers(setting)
|
|
7185
|
+
]);
|
|
7186
|
+
}
|
|
6882
7187
|
async function explainReport(server) {
|
|
6883
|
-
const [
|
|
6884
|
-
|
|
6885
|
-
|
|
7188
|
+
const [held, nearest, queue] = await Promise.all([
|
|
7189
|
+
holdSettings({
|
|
7190
|
+
flags: { server },
|
|
7191
|
+
askOrganization: () => organizationTracking(server)
|
|
7192
|
+
}),
|
|
6886
7193
|
nearestManifest(),
|
|
6887
7194
|
readQueue()
|
|
6888
7195
|
]);
|
|
6889
|
-
const
|
|
6890
|
-
|
|
6891
|
-
|
|
6892
|
-
askOrganization: () => organizationTracking(server)
|
|
6893
|
-
});
|
|
6894
|
-
const manifestScope = resolved.manifestPath && existsSync6(resolved.manifestPath) ? resolved.source === "project" ? resolved.scope : null : null;
|
|
6895
|
-
const layers = scopeLayers(resolved, manifestScope, config);
|
|
6896
|
-
const winner = layers.find((layer) => layer.layer === resolved.source);
|
|
6897
|
-
const lines = [
|
|
6898
|
-
row("scope", resolved.scope, winner?.from ?? resolved.source),
|
|
6899
|
-
...losers(layers, resolved.source),
|
|
6900
|
-
row("agents", config.agents?.length ? config.agents.join(", ") : "(the stock CLI detects)", config.agents?.length ? `agents in ${configPath()}` : "nothing pinned; `skills` looks at what is installed"),
|
|
6901
|
-
row("server", serverUrl(config, server), serverFrom(config, server)),
|
|
6902
|
-
row("track", track.value, track.from),
|
|
6903
|
-
row("autoUpdate", config.autoUpdate === false ? "off" : "on", config.autoUpdate === undefined ? "built-in default" : `autoUpdate in ${configPath()}`)
|
|
6904
|
-
];
|
|
7196
|
+
const settings = settingsShown(held);
|
|
7197
|
+
const tracking = trackingOf(held);
|
|
7198
|
+
const lines = settingRows(settings);
|
|
6905
7199
|
const skills = Object.keys(nearest.manifest.skills).length;
|
|
6906
7200
|
const bundles = Object.keys(nearest.manifest.bundles).length;
|
|
6907
7201
|
const loose = looseSkills(nearest.manifest).length;
|
|
@@ -6938,16 +7232,19 @@ async function explainReport(server) {
|
|
|
6938
7232
|
title: "config --explain",
|
|
6939
7233
|
lines,
|
|
6940
7234
|
json: {
|
|
6941
|
-
path: configPath
|
|
6942
|
-
|
|
6943
|
-
|
|
6944
|
-
|
|
6945
|
-
|
|
6946
|
-
|
|
6947
|
-
|
|
6948
|
-
|
|
6949
|
-
|
|
6950
|
-
|
|
7235
|
+
path: held.configPath,
|
|
7236
|
+
settings: Object.fromEntries(settings.map((setting) => [
|
|
7237
|
+
setting.key,
|
|
7238
|
+
{
|
|
7239
|
+
value: setting.json,
|
|
7240
|
+
layer: setting.layer,
|
|
7241
|
+
from: setting.from,
|
|
7242
|
+
layers: setting.layers,
|
|
7243
|
+
decided: setting.decided
|
|
7244
|
+
}
|
|
7245
|
+
])),
|
|
7246
|
+
track: tracking.value === "on",
|
|
7247
|
+
trackingState: tracking.state,
|
|
6951
7248
|
manifest: { path: nearest.path, skills, bundles, loose },
|
|
6952
7249
|
queue: { path: queuePath(), events: queue.length },
|
|
6953
7250
|
hooks: hooksJson
|
|
@@ -7225,7 +7522,25 @@ var TABLE = {
|
|
|
7225
7522
|
hint: "org <name>",
|
|
7226
7523
|
shows: (facts) => facts.feed.organizations === undefined ? "" : facts.feed.organizations <= 1 ? "only this one" : count2(facts.feed.organizations - 1, "other"),
|
|
7227
7524
|
when: (facts) => facts.signedIn,
|
|
7228
|
-
says: (facts) => facts.organization ? `switch this machine out of ${facts.organization}` : "choose which organization this machine installs from"
|
|
7525
|
+
says: (facts) => facts.organization ? `switch this machine out of ${facts.organization}` : "choose which organization this machine installs from",
|
|
7526
|
+
asks: {
|
|
7527
|
+
title: "org",
|
|
7528
|
+
verb: "switch",
|
|
7529
|
+
pick: "one",
|
|
7530
|
+
empty: "You are not a member of any organization yet.",
|
|
7531
|
+
choices: async (ctx, _facts, ask = organizationChoices) => {
|
|
7532
|
+
const orgs = await ask(ctx.server);
|
|
7533
|
+
const active = orgs.find((org) => org.active)?.name;
|
|
7534
|
+
return {
|
|
7535
|
+
rows: orgs.map((org) => ({
|
|
7536
|
+
name: org.name,
|
|
7537
|
+
description: org.slug ?? ""
|
|
7538
|
+
})),
|
|
7539
|
+
chosen: active ? [active] : []
|
|
7540
|
+
};
|
|
7541
|
+
},
|
|
7542
|
+
opens: (picked) => ({ kind: "org", name: picked[0] ?? "" })
|
|
7543
|
+
}
|
|
7229
7544
|
}
|
|
7230
7545
|
],
|
|
7231
7546
|
invitesHooks: false,
|
|
@@ -7298,6 +7613,7 @@ var TABLE = {
|
|
|
7298
7613
|
label: "search",
|
|
7299
7614
|
hint: "search <query>",
|
|
7300
7615
|
when: (facts) => facts.signedIn,
|
|
7616
|
+
opens: { kind: "search", query: "" },
|
|
7301
7617
|
says: () => "filter the same catalog by name and description"
|
|
7302
7618
|
}
|
|
7303
7619
|
],
|
|
@@ -7367,7 +7683,21 @@ var TABLE = {
|
|
|
7367
7683
|
label: "remove",
|
|
7368
7684
|
hint: "remove <name...>",
|
|
7369
7685
|
when: (facts) => facts.skills > 0,
|
|
7370
|
-
says: (facts) => `pick which of the ${count2(facts.skills, "skill")} to delete from this machine; it asks before it does
|
|
7686
|
+
says: (facts) => `pick which of the ${count2(facts.skills, "skill")} to delete from this machine; it asks before it does`,
|
|
7687
|
+
asks: {
|
|
7688
|
+
title: "remove",
|
|
7689
|
+
verb: "remove",
|
|
7690
|
+
pick: "many",
|
|
7691
|
+
empty: "Nothing installed here.",
|
|
7692
|
+
choices: async (ctx) => ({
|
|
7693
|
+
rows: installedRows(ctx.where).map((row) => ({
|
|
7694
|
+
name: row.name,
|
|
7695
|
+
description: `${row.bundle ?? "loose"} ${shortRef(row.ref)}`
|
|
7696
|
+
})),
|
|
7697
|
+
chosen: []
|
|
7698
|
+
}),
|
|
7699
|
+
opens: (picked) => ({ kind: "remove", names: [...picked] })
|
|
7700
|
+
}
|
|
7371
7701
|
}
|
|
7372
7702
|
],
|
|
7373
7703
|
invitesHooks: true,
|
|
@@ -7643,7 +7973,7 @@ var TABLE = {
|
|
|
7643
7973
|
lines: fixes.map((check) => ({
|
|
7644
7974
|
mark: "fail",
|
|
7645
7975
|
text: check.name,
|
|
7646
|
-
note: check.fix
|
|
7976
|
+
note: check.fix.sentence
|
|
7647
7977
|
})),
|
|
7648
7978
|
notes: [
|
|
7649
7979
|
"Every other check stays as it is; a session or an upgrade is yours to run."
|
|
@@ -7689,14 +8019,62 @@ var TABLE = {
|
|
|
7689
8019
|
label: "scope",
|
|
7690
8020
|
hint: "config set scope project",
|
|
7691
8021
|
shows: (facts) => facts.scope,
|
|
7692
|
-
says: (facts) => `where installs land: ${facts.scope} right now
|
|
8022
|
+
says: (facts) => `where installs land: ${facts.scope} right now`,
|
|
8023
|
+
asks: {
|
|
8024
|
+
title: "scope",
|
|
8025
|
+
verb: "set",
|
|
8026
|
+
pick: "one",
|
|
8027
|
+
empty: "",
|
|
8028
|
+
choices: async (_ctx, facts) => ({
|
|
8029
|
+
rows: [
|
|
8030
|
+
{
|
|
8031
|
+
name: "global",
|
|
8032
|
+
description: "installs land in your home directory, where every project sees them"
|
|
8033
|
+
},
|
|
8034
|
+
{
|
|
8035
|
+
name: "project",
|
|
8036
|
+
description: "installs land in this project's yourskills.json, and travel with the repo"
|
|
8037
|
+
}
|
|
8038
|
+
],
|
|
8039
|
+
chosen: [facts.scope]
|
|
8040
|
+
}),
|
|
8041
|
+
opens: (picked) => ({
|
|
8042
|
+
kind: "config",
|
|
8043
|
+
action: "set",
|
|
8044
|
+
key: "scope",
|
|
8045
|
+
value: picked[0] ?? ""
|
|
8046
|
+
})
|
|
8047
|
+
}
|
|
7693
8048
|
},
|
|
7694
8049
|
{
|
|
7695
8050
|
value: "setup.agents",
|
|
7696
8051
|
label: "agents",
|
|
7697
8052
|
hint: "config set agents <name>",
|
|
7698
8053
|
shows: (facts) => facts.agents.length > 0 ? count2(facts.agents.length, "agent") : "unset",
|
|
7699
|
-
says: (facts) => facts.agents.length > 0 ? `which agents an install writes for: ${facts.agents.join(", ")}` : "which agents an install writes for; nothing pinned, so `skills` looks"
|
|
8054
|
+
says: (facts) => facts.agents.length > 0 ? `which agents an install writes for: ${facts.agents.join(", ")}` : "which agents an install writes for; nothing pinned, so `skills` looks",
|
|
8055
|
+
asks: {
|
|
8056
|
+
title: "agents",
|
|
8057
|
+
verb: "set",
|
|
8058
|
+
pick: "many",
|
|
8059
|
+
empty: "Could not ask the `skills` CLI which agent names it accepts. Run `yourskills doctor`.",
|
|
8060
|
+
choices: async (_ctx, facts, ask = knownAgents) => {
|
|
8061
|
+
const valid = await ask() ?? [];
|
|
8062
|
+
const onDisk = new Set(facts.agentsOnDisk);
|
|
8063
|
+
return {
|
|
8064
|
+
rows: valid.map((name) => ({
|
|
8065
|
+
name,
|
|
8066
|
+
description: onDisk.has(name) ? "on this machine" : ""
|
|
8067
|
+
})),
|
|
8068
|
+
chosen: facts.agents
|
|
8069
|
+
};
|
|
8070
|
+
},
|
|
8071
|
+
opens: (picked) => ({
|
|
8072
|
+
kind: "config",
|
|
8073
|
+
action: "set",
|
|
8074
|
+
key: "agents",
|
|
8075
|
+
value: picked.join(",")
|
|
8076
|
+
})
|
|
8077
|
+
}
|
|
7700
8078
|
},
|
|
7701
8079
|
{
|
|
7702
8080
|
value: "setup.explain",
|
|
@@ -8199,14 +8577,13 @@ function paletteGroups(facts) {
|
|
|
8199
8577
|
}
|
|
8200
8578
|
return groups;
|
|
8201
8579
|
}
|
|
8202
|
-
// src/shell/
|
|
8203
|
-
import { existsSync as
|
|
8580
|
+
// src/shell/machine.ts
|
|
8581
|
+
import { existsSync as existsSync6 } from "node:fs";
|
|
8204
8582
|
async function paletteFacts(scope, server) {
|
|
8205
|
-
const [account, nearest,
|
|
8583
|
+
const [account, nearest, held] = await Promise.all([
|
|
8206
8584
|
localAccount(server),
|
|
8207
8585
|
nearestManifest(scope),
|
|
8208
|
-
|
|
8209
|
-
resolveAgents()
|
|
8586
|
+
holdSettings({ flags: { server } })
|
|
8210
8587
|
]);
|
|
8211
8588
|
const skills = Object.keys(nearest.manifest.skills).length;
|
|
8212
8589
|
return {
|
|
@@ -8214,21 +8591,252 @@ async function paletteFacts(scope, server) {
|
|
|
8214
8591
|
email: account.email,
|
|
8215
8592
|
organization: account.organization,
|
|
8216
8593
|
server: account.server,
|
|
8217
|
-
firstRun: !account.signedIn && skills === 0 && !config.welcomed,
|
|
8218
|
-
agentsOnDisk: HOOK_ADAPTERS.filter((adapter) =>
|
|
8219
|
-
agents,
|
|
8594
|
+
firstRun: !account.signedIn && skills === 0 && !held.config.welcomed,
|
|
8595
|
+
agentsOnDisk: HOOK_ADAPTERS.filter((adapter) => existsSync6(adapter.home())).map((adapter) => adapter.agent),
|
|
8596
|
+
agents: agentsAnswer(held).value,
|
|
8220
8597
|
scope: nearest.scope,
|
|
8221
8598
|
manifest: nearest.path,
|
|
8222
8599
|
skills,
|
|
8223
8600
|
bundles: Object.keys(nearest.manifest.bundles).length,
|
|
8224
8601
|
loose: looseSkills(nearest.manifest).length,
|
|
8225
|
-
settings:
|
|
8602
|
+
settings: settingsShown(held).filter((setting) => setting.decided).length,
|
|
8226
8603
|
feed: NO_FEED
|
|
8227
8604
|
};
|
|
8228
8605
|
}
|
|
8606
|
+
async function feedFacts(client, where, server, choices = organizationChoices) {
|
|
8607
|
+
const installed = Object.keys(where.manifest.skills).length > 0;
|
|
8608
|
+
const [skills, bundles, passes, drift, orgs] = await Promise.allSettled([
|
|
8609
|
+
client.skills.list.query({}),
|
|
8610
|
+
client.bundles.list.query({}),
|
|
8611
|
+
client.mirror.passes.query(),
|
|
8612
|
+
installed ? driftNow(client, where) : Promise.resolve(null),
|
|
8613
|
+
choices(server)
|
|
8614
|
+
]);
|
|
8615
|
+
return {
|
|
8616
|
+
...skills.status === "fulfilled" ? { skills: skills.value.length } : {},
|
|
8617
|
+
...bundles.status === "fulfilled" ? { bundles: bundles.value.length } : {},
|
|
8618
|
+
...passes.status === "fulfilled" ? { pending: passes.value.length } : {},
|
|
8619
|
+
...drift.status === "fulfilled" && drift.value ? { outdated: drift.value.moved.length } : {},
|
|
8620
|
+
...orgs.status === "fulfilled" ? { organizations: orgs.value.length } : {}
|
|
8621
|
+
};
|
|
8622
|
+
}
|
|
8623
|
+
var importProbe = async (where) => {
|
|
8624
|
+
const found = await listInstalled(where.scope);
|
|
8625
|
+
const recorded = new Set(Object.keys(where.manifest.skills));
|
|
8626
|
+
const news = found.filter((skill) => !recorded.has(skill.name)).length;
|
|
8627
|
+
if (found.length === 0)
|
|
8628
|
+
return "nothing on this machine to adopt";
|
|
8629
|
+
return news === 0 ? `${count2(found.length, "skill")} on this machine, all recorded here already` : `${count2(found.length, "skill")} on this machine, ${news} not recorded here`;
|
|
8630
|
+
};
|
|
8631
|
+
var doctorProbe = async (where, server) => {
|
|
8632
|
+
const checks = await diagnose(where, {
|
|
8633
|
+
local: true,
|
|
8634
|
+
...server ? { server } : {}
|
|
8635
|
+
});
|
|
8636
|
+
const bad = checks.filter((check) => check.state === "fail");
|
|
8637
|
+
return bad.length === 0 ? `${count2(checks.length, "local check")}, all passing` : `${bad.length} of ${checks.length} local checks failing: ${bad.map((check) => check.name).join(", ")}`;
|
|
8638
|
+
};
|
|
8639
|
+
var PROBES = {
|
|
8640
|
+
"setup.import": importProbe,
|
|
8641
|
+
"setup.doctor": doctorProbe
|
|
8642
|
+
};
|
|
8643
|
+
function probeFor(value) {
|
|
8644
|
+
return value ? PROBES[value] : undefined;
|
|
8645
|
+
}
|
|
8646
|
+
var REST_MS = 250;
|
|
8647
|
+
var noop3 = () => {
|
|
8648
|
+
return;
|
|
8649
|
+
};
|
|
8650
|
+
function knowMachine(from, talkers = {}) {
|
|
8651
|
+
const { server } = from;
|
|
8652
|
+
const ask = {
|
|
8653
|
+
upgrade: talkers.upgrade ?? (() => upgradeCheck()),
|
|
8654
|
+
feed: talkers.feed ?? feedFacts,
|
|
8655
|
+
probes: talkers.probes ?? probeFor,
|
|
8656
|
+
learnOrganization: talkers.learnOrganization ?? learnActiveOrganization
|
|
8657
|
+
};
|
|
8658
|
+
const restMs = talkers.restMs ?? REST_MS;
|
|
8659
|
+
let where = from.where;
|
|
8660
|
+
let local = from.facts;
|
|
8661
|
+
let feed = from.facts.feed;
|
|
8662
|
+
let upgrade;
|
|
8663
|
+
let probes = {};
|
|
8664
|
+
let probing = null;
|
|
8665
|
+
let dead = false;
|
|
8666
|
+
let epoch = 0;
|
|
8667
|
+
const listeners = new Set;
|
|
8668
|
+
const pending = new Set;
|
|
8669
|
+
function fold() {
|
|
8670
|
+
return {
|
|
8671
|
+
where,
|
|
8672
|
+
facts: {
|
|
8673
|
+
...local,
|
|
8674
|
+
feed: {
|
|
8675
|
+
...feed,
|
|
8676
|
+
...upgrade === undefined ? {} : { upgrade: upgrade ? upgrade.latest : null }
|
|
8677
|
+
}
|
|
8678
|
+
},
|
|
8679
|
+
upgrade,
|
|
8680
|
+
probes,
|
|
8681
|
+
probing
|
|
8682
|
+
};
|
|
8683
|
+
}
|
|
8684
|
+
let state = fold();
|
|
8685
|
+
function set() {
|
|
8686
|
+
state = fold();
|
|
8687
|
+
for (const listener of listeners)
|
|
8688
|
+
listener();
|
|
8689
|
+
}
|
|
8690
|
+
function track(work) {
|
|
8691
|
+
const done = work.then(noop3, noop3).then(() => {
|
|
8692
|
+
pending.delete(done);
|
|
8693
|
+
});
|
|
8694
|
+
pending.add(done);
|
|
8695
|
+
return done;
|
|
8696
|
+
}
|
|
8697
|
+
let held = null;
|
|
8698
|
+
async function client() {
|
|
8699
|
+
held ??= await from.client();
|
|
8700
|
+
return held;
|
|
8701
|
+
}
|
|
8702
|
+
function askUpgrade() {
|
|
8703
|
+
track(ask.upgrade().then((found) => {
|
|
8704
|
+
if (dead)
|
|
8705
|
+
return;
|
|
8706
|
+
upgrade = found;
|
|
8707
|
+
set();
|
|
8708
|
+
}));
|
|
8709
|
+
}
|
|
8710
|
+
function askFeed() {
|
|
8711
|
+
if (dead || !local.signedIn)
|
|
8712
|
+
return;
|
|
8713
|
+
const mine = epoch;
|
|
8714
|
+
track(client().then((live) => ask.feed(live, where, server)).then((found) => {
|
|
8715
|
+
if (dead || mine !== epoch)
|
|
8716
|
+
return;
|
|
8717
|
+
feed = found;
|
|
8718
|
+
set();
|
|
8719
|
+
}));
|
|
8720
|
+
}
|
|
8721
|
+
let resting;
|
|
8722
|
+
let timer = null;
|
|
8723
|
+
let wake = null;
|
|
8724
|
+
function unrest() {
|
|
8725
|
+
if (timer)
|
|
8726
|
+
clearTimeout(timer);
|
|
8727
|
+
timer = null;
|
|
8728
|
+
wake?.();
|
|
8729
|
+
wake = null;
|
|
8730
|
+
}
|
|
8731
|
+
function scan(value, probe) {
|
|
8732
|
+
const mine = epoch;
|
|
8733
|
+
probing = value;
|
|
8734
|
+
set();
|
|
8735
|
+
track(probe(where, server).then((text) => {
|
|
8736
|
+
if (dead || mine !== epoch)
|
|
8737
|
+
return;
|
|
8738
|
+
probes = { ...probes, [value]: text };
|
|
8739
|
+
}).catch(noop3).finally(() => {
|
|
8740
|
+
if (dead || mine !== epoch)
|
|
8741
|
+
return;
|
|
8742
|
+
if (probing === value)
|
|
8743
|
+
probing = null;
|
|
8744
|
+
set();
|
|
8745
|
+
}));
|
|
8746
|
+
}
|
|
8747
|
+
function rest(value) {
|
|
8748
|
+
resting = value;
|
|
8749
|
+
unrest();
|
|
8750
|
+
if (dead || !value)
|
|
8751
|
+
return;
|
|
8752
|
+
const probe = ask.probes(value);
|
|
8753
|
+
if (!probe || probes[value] !== undefined)
|
|
8754
|
+
return;
|
|
8755
|
+
let fired = false;
|
|
8756
|
+
track(new Promise((resolve) => {
|
|
8757
|
+
wake = resolve;
|
|
8758
|
+
timer = setTimeout(() => {
|
|
8759
|
+
fired = true;
|
|
8760
|
+
timer = null;
|
|
8761
|
+
wake = null;
|
|
8762
|
+
resolve();
|
|
8763
|
+
}, restMs);
|
|
8764
|
+
}).then(() => {
|
|
8765
|
+
if (fired && !dead)
|
|
8766
|
+
scan(value, probe);
|
|
8767
|
+
}));
|
|
8768
|
+
}
|
|
8769
|
+
async function reread() {
|
|
8770
|
+
const [nextFacts, nextWhere] = await Promise.allSettled([
|
|
8771
|
+
from.reload(),
|
|
8772
|
+
from.resite()
|
|
8773
|
+
]);
|
|
8774
|
+
if (dead)
|
|
8775
|
+
return;
|
|
8776
|
+
if (nextFacts.status === "fulfilled")
|
|
8777
|
+
local = nextFacts.value;
|
|
8778
|
+
if (nextWhere.status === "fulfilled")
|
|
8779
|
+
where = nextWhere.value;
|
|
8780
|
+
if (!local.signedIn)
|
|
8781
|
+
feed = NO_FEED;
|
|
8782
|
+
set();
|
|
8783
|
+
askFeed();
|
|
8784
|
+
rest(resting);
|
|
8785
|
+
}
|
|
8786
|
+
function forgetScans() {
|
|
8787
|
+
epoch += 1;
|
|
8788
|
+
probes = {};
|
|
8789
|
+
probing = null;
|
|
8790
|
+
set();
|
|
8791
|
+
}
|
|
8792
|
+
function back() {
|
|
8793
|
+
forgetScans();
|
|
8794
|
+
track(reread());
|
|
8795
|
+
}
|
|
8796
|
+
function loggedIn() {
|
|
8797
|
+
forgetScans();
|
|
8798
|
+
return track(ask.learnOrganization(server).catch(noop3).then(reread));
|
|
8799
|
+
}
|
|
8800
|
+
function stop() {
|
|
8801
|
+
dead = true;
|
|
8802
|
+
unrest();
|
|
8803
|
+
listeners.clear();
|
|
8804
|
+
}
|
|
8805
|
+
async function settled() {
|
|
8806
|
+
while (pending.size > 0)
|
|
8807
|
+
await Promise.all([...pending]);
|
|
8808
|
+
}
|
|
8809
|
+
askUpgrade();
|
|
8810
|
+
askFeed();
|
|
8811
|
+
return {
|
|
8812
|
+
read: () => state,
|
|
8813
|
+
subscribe: (listener) => {
|
|
8814
|
+
listeners.add(listener);
|
|
8815
|
+
return () => {
|
|
8816
|
+
listeners.delete(listener);
|
|
8817
|
+
};
|
|
8818
|
+
},
|
|
8819
|
+
back,
|
|
8820
|
+
loggedIn,
|
|
8821
|
+
rest,
|
|
8822
|
+
client,
|
|
8823
|
+
forget: () => {
|
|
8824
|
+
held = null;
|
|
8825
|
+
},
|
|
8826
|
+
resite: from.resite,
|
|
8827
|
+
stop,
|
|
8828
|
+
settled
|
|
8829
|
+
};
|
|
8830
|
+
}
|
|
8229
8831
|
// src/shell/screens/home.tsx
|
|
8230
8832
|
import { Box as Box14, Text as Text16, useApp as useApp7, useInput as useInput8 } from "ink";
|
|
8231
|
-
import {
|
|
8833
|
+
import {
|
|
8834
|
+
useCallback as useCallback3,
|
|
8835
|
+
useEffect as useEffect9,
|
|
8836
|
+
useMemo as useMemo4,
|
|
8837
|
+
useState as useState11,
|
|
8838
|
+
useSyncExternalStore
|
|
8839
|
+
} from "react";
|
|
8232
8840
|
|
|
8233
8841
|
// src/draw.tsx
|
|
8234
8842
|
import { jsx as jsx15 } from "react/jsx-runtime";
|
|
@@ -8289,50 +8897,6 @@ function drawInk(answer, {
|
|
|
8289
8897
|
}
|
|
8290
8898
|
}
|
|
8291
8899
|
|
|
8292
|
-
// src/shell/feed.ts
|
|
8293
|
-
async function feedFacts(client, where, server) {
|
|
8294
|
-
const installed = Object.keys(where.manifest.skills).length > 0;
|
|
8295
|
-
const [skills, bundles, passes, drift, orgs] = await Promise.allSettled([
|
|
8296
|
-
client.skills.list.query({}),
|
|
8297
|
-
client.bundles.list.query({}),
|
|
8298
|
-
client.mirror.passes.query(),
|
|
8299
|
-
installed ? driftNow(client, where) : Promise.resolve(null),
|
|
8300
|
-
organizationChoices(server)
|
|
8301
|
-
]);
|
|
8302
|
-
return {
|
|
8303
|
-
...skills.status === "fulfilled" ? { skills: skills.value.length } : {},
|
|
8304
|
-
...bundles.status === "fulfilled" ? { bundles: bundles.value.length } : {},
|
|
8305
|
-
...passes.status === "fulfilled" ? { pending: passes.value.length } : {},
|
|
8306
|
-
...drift.status === "fulfilled" && drift.value ? { outdated: drift.value.moved.length } : {},
|
|
8307
|
-
...orgs.status === "fulfilled" ? { organizations: orgs.value.length } : {}
|
|
8308
|
-
};
|
|
8309
|
-
}
|
|
8310
|
-
|
|
8311
|
-
// src/shell/probe.ts
|
|
8312
|
-
var importProbe = async (where) => {
|
|
8313
|
-
const found = await listInstalled(where.scope);
|
|
8314
|
-
const recorded = new Set(Object.keys(where.manifest.skills));
|
|
8315
|
-
const news = found.filter((skill) => !recorded.has(skill.name)).length;
|
|
8316
|
-
if (found.length === 0)
|
|
8317
|
-
return "nothing on this machine to adopt";
|
|
8318
|
-
return news === 0 ? `${count2(found.length, "skill")} on this machine, all recorded here already` : `${count2(found.length, "skill")} on this machine, ${news} not recorded here`;
|
|
8319
|
-
};
|
|
8320
|
-
var doctorProbe = async (where, server) => {
|
|
8321
|
-
const checks = await diagnose(where, {
|
|
8322
|
-
local: true,
|
|
8323
|
-
...server ? { server } : {}
|
|
8324
|
-
});
|
|
8325
|
-
const bad = checks.filter((check) => !check.ok);
|
|
8326
|
-
return bad.length === 0 ? `${count2(checks.length, "local check")}, all passing` : `${bad.length} of ${checks.length} local checks failing: ${bad.map((check) => check.name).join(", ")}`;
|
|
8327
|
-
};
|
|
8328
|
-
var PROBES = {
|
|
8329
|
-
"setup.import": importProbe,
|
|
8330
|
-
"setup.doctor": doctorProbe
|
|
8331
|
-
};
|
|
8332
|
-
function probeFor(value) {
|
|
8333
|
-
return value ? PROBES[value] : undefined;
|
|
8334
|
-
}
|
|
8335
|
-
|
|
8336
8900
|
// src/args.ts
|
|
8337
8901
|
function configCommand2(rest, explain) {
|
|
8338
8902
|
const [action, key, ...value] = rest;
|
|
@@ -8962,61 +9526,30 @@ function StaleLine({ stale, width }) {
|
|
|
8962
9526
|
});
|
|
8963
9527
|
}
|
|
8964
9528
|
var RUN = "run";
|
|
8965
|
-
var REST_MS = 250;
|
|
8966
|
-
var ASK_NPM = () => upgradeCheck();
|
|
8967
|
-
var ASK_REGISTRY = feedFacts;
|
|
8968
9529
|
var NO_EXIT_CODE = () => {
|
|
8969
9530
|
return;
|
|
8970
9531
|
};
|
|
8971
9532
|
function HomeScreen({
|
|
8972
|
-
|
|
8973
|
-
resite,
|
|
8974
|
-
facts: initial,
|
|
8975
|
-
reload,
|
|
9533
|
+
known,
|
|
8976
9534
|
server,
|
|
8977
|
-
openClient,
|
|
8978
9535
|
onError,
|
|
8979
9536
|
onSummary,
|
|
8980
9537
|
onWelcomed = async () => {
|
|
8981
9538
|
return;
|
|
8982
|
-
}
|
|
8983
|
-
checkUpgrade = ASK_NPM,
|
|
8984
|
-
loadFeed = ASK_REGISTRY,
|
|
8985
|
-
probes: probeSet = probeFor
|
|
9539
|
+
}
|
|
8986
9540
|
}) {
|
|
8987
9541
|
const { exit } = useApp7();
|
|
8988
9542
|
const width = useFrameWidth();
|
|
8989
|
-
|
|
8990
|
-
const
|
|
9543
|
+
useEffect9(() => () => known.stop(), [known]);
|
|
9544
|
+
const machine = useSyncExternalStore(known.subscribe, known.read, known.read);
|
|
9545
|
+
const { where, facts, upgrade: stale, probes, probing } = machine;
|
|
9546
|
+
const { resite } = known;
|
|
8991
9547
|
const [query, setQuery] = useState11("");
|
|
8992
9548
|
const [cursor, setCursor] = useState11(0);
|
|
8993
9549
|
const [route, setRoute] = useState11(null);
|
|
8994
|
-
const [welcome, setWelcome] = useState11(
|
|
9550
|
+
const [welcome, setWelcome] = useState11(() => known.read().facts.firstRun);
|
|
8995
9551
|
const [wizard, setWizard] = useState11(false);
|
|
8996
|
-
const
|
|
8997
|
-
useEffect9(() => {
|
|
8998
|
-
let live = true;
|
|
8999
|
-
checkUpgrade().then((found) => {
|
|
9000
|
-
if (live)
|
|
9001
|
-
setStale(found);
|
|
9002
|
-
}).catch(() => {
|
|
9003
|
-
return;
|
|
9004
|
-
});
|
|
9005
|
-
return () => {
|
|
9006
|
-
live = false;
|
|
9007
|
-
};
|
|
9008
|
-
}, [checkUpgrade]);
|
|
9009
|
-
const [feed, setFeed] = useState11(NO_FEED);
|
|
9010
|
-
const [probes, setProbes] = useState11({});
|
|
9011
|
-
const [probing, setProbing] = useState11(null);
|
|
9012
|
-
const shown = useMemo4(() => ({
|
|
9013
|
-
...facts,
|
|
9014
|
-
feed: {
|
|
9015
|
-
...feed,
|
|
9016
|
-
...stale === undefined ? {} : { upgrade: stale ? stale.latest : null }
|
|
9017
|
-
}
|
|
9018
|
-
}), [facts, feed, stale]);
|
|
9019
|
-
const groups = useMemo4(() => paletteGroups(shown), [shown]);
|
|
9552
|
+
const groups = useMemo4(() => paletteGroups(facts), [facts]);
|
|
9020
9553
|
const typed = useMemo4(() => typedCommand(query), [query]);
|
|
9021
9554
|
const visible = useMemo4(() => {
|
|
9022
9555
|
const filtered = filterCommands(groups, query);
|
|
@@ -9034,25 +9567,7 @@ function HomeScreen({
|
|
|
9034
9567
|
useEffect9(() => {
|
|
9035
9568
|
setCursor((c) => Math.max(0, Math.min(c, rows.length - 1)));
|
|
9036
9569
|
}, [rows.length]);
|
|
9037
|
-
const
|
|
9038
|
-
const connect = useCallback3(async () => {
|
|
9039
|
-
held.current ??= await openClient();
|
|
9040
|
-
return held.current;
|
|
9041
|
-
}, [openClient]);
|
|
9042
|
-
useEffect9(() => {
|
|
9043
|
-
if (!facts.signedIn)
|
|
9044
|
-
return;
|
|
9045
|
-
let live = true;
|
|
9046
|
-
connect().then((client) => loadFeed(client, where, server)).then((found) => {
|
|
9047
|
-
if (live)
|
|
9048
|
-
setFeed(found);
|
|
9049
|
-
}).catch(() => {
|
|
9050
|
-
return;
|
|
9051
|
-
});
|
|
9052
|
-
return () => {
|
|
9053
|
-
live = false;
|
|
9054
|
-
};
|
|
9055
|
-
}, [facts.signedIn, connect, loadFeed, where, server]);
|
|
9570
|
+
const connect = known.client;
|
|
9056
9571
|
const checkSession = useCallback3(async () => facts.signedIn, [facts.signedIn]);
|
|
9057
9572
|
const failing = useCallback3((title, error) => ({
|
|
9058
9573
|
kind: "answer",
|
|
@@ -9105,91 +9620,21 @@ function HomeScreen({
|
|
|
9105
9620
|
const asked = await row.confirm?.(command, ctx, facts);
|
|
9106
9621
|
const answer = asked ?? await row.run(command, ctx);
|
|
9107
9622
|
if (command.kind === "logout")
|
|
9108
|
-
|
|
9623
|
+
known.forget();
|
|
9109
9624
|
return routeFrom(answer, say(command, facts));
|
|
9110
|
-
}, [catalogDoor, ctx, facts, routeFrom]);
|
|
9111
|
-
const
|
|
9112
|
-
|
|
9113
|
-
|
|
9114
|
-
|
|
9115
|
-
|
|
9116
|
-
|
|
9117
|
-
|
|
9118
|
-
|
|
9119
|
-
|
|
9120
|
-
|
|
9121
|
-
|
|
9122
|
-
|
|
9123
|
-
empty: "Nothing installed here.",
|
|
9124
|
-
verb: "remove",
|
|
9125
|
-
command: (names) => ({ kind: "remove", names })
|
|
9126
|
-
};
|
|
9127
|
-
case "setup.scope":
|
|
9128
|
-
return {
|
|
9129
|
-
kind: "pick",
|
|
9130
|
-
title: "scope",
|
|
9131
|
-
rows: [
|
|
9132
|
-
{
|
|
9133
|
-
name: "global",
|
|
9134
|
-
description: "installs land in your home directory, where every project sees them"
|
|
9135
|
-
},
|
|
9136
|
-
{
|
|
9137
|
-
name: "project",
|
|
9138
|
-
description: "installs land in this project's yourskills.json, and travel with the repo"
|
|
9139
|
-
}
|
|
9140
|
-
],
|
|
9141
|
-
empty: "",
|
|
9142
|
-
verb: "set",
|
|
9143
|
-
single: true,
|
|
9144
|
-
marked: facts.scope,
|
|
9145
|
-
command: (names) => ({
|
|
9146
|
-
kind: "config",
|
|
9147
|
-
action: "set",
|
|
9148
|
-
key: "scope",
|
|
9149
|
-
value: names[0] ?? ""
|
|
9150
|
-
})
|
|
9151
|
-
};
|
|
9152
|
-
case "setup.agents": {
|
|
9153
|
-
const valid = await knownAgents() ?? [];
|
|
9154
|
-
const onDisk = new Set(facts.agentsOnDisk);
|
|
9155
|
-
return {
|
|
9156
|
-
kind: "pick",
|
|
9157
|
-
title: "agents",
|
|
9158
|
-
rows: valid.map((name) => ({
|
|
9159
|
-
name,
|
|
9160
|
-
description: onDisk.has(name) ? "on this machine" : ""
|
|
9161
|
-
})),
|
|
9162
|
-
empty: "Could not ask the `skills` CLI which agent names it accepts. Run `yourskills doctor`.",
|
|
9163
|
-
verb: "set",
|
|
9164
|
-
preset: facts.agents,
|
|
9165
|
-
command: (names) => ({
|
|
9166
|
-
kind: "config",
|
|
9167
|
-
action: "set",
|
|
9168
|
-
key: "agents",
|
|
9169
|
-
value: names.join(",")
|
|
9170
|
-
})
|
|
9171
|
-
};
|
|
9172
|
-
}
|
|
9173
|
-
case "account.org": {
|
|
9174
|
-
const orgs = await organizationChoices(server);
|
|
9175
|
-
const active = orgs.find((org) => org.active)?.name;
|
|
9176
|
-
return {
|
|
9177
|
-
kind: "pick",
|
|
9178
|
-
title: "org",
|
|
9179
|
-
rows: orgs.map((org) => ({
|
|
9180
|
-
name: org.name,
|
|
9181
|
-
description: org.slug ?? ""
|
|
9182
|
-
})),
|
|
9183
|
-
empty: "You are not a member of any organization yet.",
|
|
9184
|
-
verb: "switch",
|
|
9185
|
-
single: true,
|
|
9186
|
-
...active ? { marked: active } : {},
|
|
9187
|
-
command: (names) => ({ kind: "org", name: names[0] ?? "" })
|
|
9188
|
-
};
|
|
9189
|
-
}
|
|
9190
|
-
}
|
|
9191
|
-
}, [where, server, facts]);
|
|
9192
|
-
const routeForDoor = useCallback3(async (door) => door.opens === undefined ? ownRoute(door.value) : routeForCommand(door.opens), [ownRoute, routeForCommand]);
|
|
9625
|
+
}, [catalogDoor, ctx, facts, known, routeFrom]);
|
|
9626
|
+
const routeForDoor = useCallback3(async (door) => {
|
|
9627
|
+
if (door.opens !== undefined)
|
|
9628
|
+
return routeForCommand(door.opens);
|
|
9629
|
+
const { asks } = door;
|
|
9630
|
+
const choices = await asks.choices(ctx, facts);
|
|
9631
|
+
return {
|
|
9632
|
+
kind: "pick",
|
|
9633
|
+
ask: asks,
|
|
9634
|
+
rows: [...choices.rows],
|
|
9635
|
+
chosen: [...choices.chosen]
|
|
9636
|
+
};
|
|
9637
|
+
}, [ctx, facts, routeForCommand]);
|
|
9193
9638
|
const open = useCallback3((pending, title) => {
|
|
9194
9639
|
pending.then((next) => {
|
|
9195
9640
|
if (next)
|
|
@@ -9205,29 +9650,10 @@ function HomeScreen({
|
|
|
9205
9650
|
open(routeForCommand(parsed.command), parsed.text);
|
|
9206
9651
|
}, [open, routeForCommand]);
|
|
9207
9652
|
const current = rows[cursor];
|
|
9653
|
+
const resting = current?.value;
|
|
9208
9654
|
useEffect9(() => {
|
|
9209
|
-
|
|
9210
|
-
|
|
9211
|
-
if (!value || !probe || probes[value] !== undefined)
|
|
9212
|
-
return;
|
|
9213
|
-
let live = true;
|
|
9214
|
-
const timer = setTimeout(() => {
|
|
9215
|
-
setProbing(value);
|
|
9216
|
-
probe(where, server).then((text) => {
|
|
9217
|
-
if (live)
|
|
9218
|
-
setProbes((was) => ({ ...was, [value]: text }));
|
|
9219
|
-
}).catch(() => {
|
|
9220
|
-
return;
|
|
9221
|
-
}).finally(() => {
|
|
9222
|
-
if (live)
|
|
9223
|
-
setProbing(null);
|
|
9224
|
-
});
|
|
9225
|
-
}, REST_MS);
|
|
9226
|
-
return () => {
|
|
9227
|
-
live = false;
|
|
9228
|
-
clearTimeout(timer);
|
|
9229
|
-
};
|
|
9230
|
-
}, [current?.value, probes, probeSet, where, server]);
|
|
9655
|
+
known.rest(resting);
|
|
9656
|
+
}, [known, resting]);
|
|
9231
9657
|
const activate = useCallback3(() => {
|
|
9232
9658
|
const item = rows[cursor];
|
|
9233
9659
|
if (!item)
|
|
@@ -9250,32 +9676,24 @@ function HomeScreen({
|
|
|
9250
9676
|
setQuery(item.hint.includes("<") ? `${bare} ` : bare);
|
|
9251
9677
|
setCursor(0);
|
|
9252
9678
|
}, [rows, cursor]);
|
|
9253
|
-
const
|
|
9679
|
+
const land = useCallback3(() => {
|
|
9254
9680
|
setRoute(null);
|
|
9255
9681
|
setQuery("");
|
|
9256
9682
|
setCursor(0);
|
|
9257
|
-
|
|
9258
|
-
|
|
9259
|
-
|
|
9260
|
-
|
|
9261
|
-
|
|
9262
|
-
return;
|
|
9263
|
-
});
|
|
9264
|
-
}, [reload, resite]);
|
|
9683
|
+
}, []);
|
|
9684
|
+
const back = useCallback3(() => {
|
|
9685
|
+
land();
|
|
9686
|
+
known.back();
|
|
9687
|
+
}, [land, known]);
|
|
9265
9688
|
const afterLogin = useCallback3(() => {
|
|
9266
|
-
learnActiveOrganization(server).then(() => reload()).then(setFacts).catch(() => {
|
|
9267
|
-
return;
|
|
9268
|
-
});
|
|
9269
9689
|
if (!wizard) {
|
|
9270
|
-
|
|
9690
|
+
land();
|
|
9691
|
+
known.loggedIn();
|
|
9271
9692
|
return;
|
|
9272
9693
|
}
|
|
9273
9694
|
setWizard(false);
|
|
9274
|
-
|
|
9275
|
-
|
|
9276
|
-
setRoute({ kind: "browse" });
|
|
9277
|
-
}).catch(() => setRoute(null));
|
|
9278
|
-
}, [wizard, back, reload, server]);
|
|
9695
|
+
known.loggedIn().then(() => setRoute({ kind: "browse" }));
|
|
9696
|
+
}, [wizard, land, known]);
|
|
9279
9697
|
const leaveWelcome = useCallback3((start) => {
|
|
9280
9698
|
setWelcome(false);
|
|
9281
9699
|
onWelcomed().catch(() => {
|
|
@@ -9351,17 +9769,15 @@ function HomeScreen({
|
|
|
9351
9769
|
if (drawn)
|
|
9352
9770
|
return drawn;
|
|
9353
9771
|
if (route?.kind === "pick") {
|
|
9354
|
-
const
|
|
9772
|
+
const { ask, rows: choices, chosen } = route;
|
|
9355
9773
|
return /* @__PURE__ */ jsx19(PickScreen, {
|
|
9356
|
-
title:
|
|
9357
|
-
rows:
|
|
9358
|
-
empty:
|
|
9359
|
-
verb:
|
|
9360
|
-
...
|
|
9361
|
-
...picked.marked ? { marked: picked.marked } : {},
|
|
9362
|
-
...picked.preset ? { preset: picked.preset } : {},
|
|
9774
|
+
title: ask.title,
|
|
9775
|
+
rows: choices,
|
|
9776
|
+
empty: ask.empty,
|
|
9777
|
+
verb: ask.verb,
|
|
9778
|
+
...ask.pick === "one" ? { single: true, ...chosen[0] ? { marked: chosen[0] } : {} } : { preset: chosen },
|
|
9363
9779
|
onPick: (names) => {
|
|
9364
|
-
open(routeForCommand(
|
|
9780
|
+
open(routeForCommand(ask.opens(names)), ask.title);
|
|
9365
9781
|
},
|
|
9366
9782
|
onBack: back
|
|
9367
9783
|
});
|
|
@@ -9396,7 +9812,7 @@ function HomeScreen({
|
|
|
9396
9812
|
});
|
|
9397
9813
|
}
|
|
9398
9814
|
const highlighted = current ? doorFor(current.value) : undefined;
|
|
9399
|
-
const description = current?.value === RUN && runnable(typed) ? say(typed.command,
|
|
9815
|
+
const description = current?.value === RUN && runnable(typed) ? say(typed.command, facts) : highlighted ? doorSays(highlighted, facts) : typed ? say(typed.command, facts) : "";
|
|
9400
9816
|
const found = current ? probes[current.value] ?? (probing === current.value ? "looking..." : "") : "";
|
|
9401
9817
|
const empty = typed?.command.kind === "error" && typed.command.next ? `${mark.fail} ${typed.command.message} try: ${typed.command.next.replace(/^yourskills /, "")}` : "No matching commands.";
|
|
9402
9818
|
return /* @__PURE__ */ jsxs16(Frame, {
|
|
@@ -9404,7 +9820,7 @@ function HomeScreen({
|
|
|
9404
9820
|
width,
|
|
9405
9821
|
children: [
|
|
9406
9822
|
/* @__PURE__ */ jsx19(AccountLine, {
|
|
9407
|
-
facts
|
|
9823
|
+
facts,
|
|
9408
9824
|
width
|
|
9409
9825
|
}),
|
|
9410
9826
|
stale ? /* @__PURE__ */ jsx19(StaleLine, {
|
|
@@ -9538,13 +9954,17 @@ async function screenFor(command, ctx, resite, fail, exit) {
|
|
|
9538
9954
|
const { row, answer } = await answerFor(command, ctx);
|
|
9539
9955
|
if (answer.kind === "home") {
|
|
9540
9956
|
const reload = () => paletteFacts(ctx.where.scope, ctx.server);
|
|
9541
|
-
|
|
9957
|
+
const known = knowMachine({
|
|
9542
9958
|
where: ctx.where,
|
|
9543
|
-
resite,
|
|
9544
9959
|
facts: await reload(),
|
|
9960
|
+
...ctx.server ? { server: ctx.server } : {},
|
|
9545
9961
|
reload,
|
|
9962
|
+
resite,
|
|
9963
|
+
client: ctx.client
|
|
9964
|
+
});
|
|
9965
|
+
return /* @__PURE__ */ jsx20(HomeScreen, {
|
|
9966
|
+
known,
|
|
9546
9967
|
...ctx.server ? { server: ctx.server } : {},
|
|
9547
|
-
openClient: ctx.client,
|
|
9548
9968
|
onError: fail,
|
|
9549
9969
|
onSummary: summarise,
|
|
9550
9970
|
onWelcomed: async () => {
|