@pi-stef/catalog 0.3.5 → 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/README.md +34 -39
- package/package.json +1 -1
- package/src/catalog/crud.ts +9 -18
- package/src/catalog/install.ts +7 -0
- package/src/catalog/migrate.ts +59 -0
- package/src/catalog/packages.ts +56 -0
- package/src/catalog/ratings.ts +0 -16
- package/src/catalog/setup.ts +169 -0
- package/src/commands/add.ts +113 -28
- package/src/commands/definitions.ts +3 -1
- package/src/commands/init.ts +3 -2
- package/src/commands/remove.ts +76 -0
- package/src/commands/reset.ts +136 -0
- package/src/commands/status.ts +71 -36
- package/src/commands/sync.ts +39 -2
- package/src/commands/toggle.ts +11 -16
- package/src/commands/types.ts +2 -0
- package/src/commands/update.ts +113 -0
- package/src/config/io.ts +3 -0
- package/src/config/schema.ts +0 -8
- package/src/register.ts +80 -12
- package/src/sync/pull.ts +2 -0
- package/src/util/exec.ts +12 -0
package/src/commands/sync.ts
CHANGED
|
@@ -120,6 +120,7 @@ export async function syncCommand(
|
|
|
120
120
|
// --- 2. Pull remote catalog (into memory only) ---------------------------
|
|
121
121
|
let remoteCatalog = false;
|
|
122
122
|
let pulledData: { catalog: CatalogYaml; lock: LockFile } | undefined;
|
|
123
|
+
ctx.ui.setWorkingMessage?.("Pulling remote catalog...");
|
|
123
124
|
try {
|
|
124
125
|
pulledData = await pullCatalog(profile, ctx.home);
|
|
125
126
|
remoteCatalog = true;
|
|
@@ -129,6 +130,7 @@ export async function syncCommand(
|
|
|
129
130
|
ctx.ui.notify(`Pull failed: ${message}`, "warning");
|
|
130
131
|
summary.errors.push(message);
|
|
131
132
|
}
|
|
133
|
+
ctx.ui.setWorkingMessage?.();
|
|
132
134
|
|
|
133
135
|
// --- 3. Reconcile --------------------------------------------------------
|
|
134
136
|
// Use pulled catalog if available, otherwise read from disk
|
|
@@ -172,6 +174,12 @@ export async function syncCommand(
|
|
|
172
174
|
}
|
|
173
175
|
}
|
|
174
176
|
|
|
177
|
+
// Track whether the rebuilt lock (from buildSyncedLock) differs from the
|
|
178
|
+
// remote lock. This catches the case where `pi update` bumped an installed
|
|
179
|
+
// version but the catalog source string hasn't changed, so the pre-pull
|
|
180
|
+
// lock comparison wouldn't detect it.
|
|
181
|
+
let rebuiltLockDiffers = false;
|
|
182
|
+
|
|
175
183
|
const installed = scanInstalled(ctx.home);
|
|
176
184
|
|
|
177
185
|
// Build catalog entries for reconcile
|
|
@@ -220,7 +228,9 @@ export async function syncCommand(
|
|
|
220
228
|
writeLock(pulledData.lock, ctx.home);
|
|
221
229
|
}
|
|
222
230
|
|
|
231
|
+
ctx.ui.setWorkingMessage?.("Executing actions...");
|
|
223
232
|
const result = await executeActions(plan, { home: ctx.home });
|
|
233
|
+
ctx.ui.setWorkingMessage?.();
|
|
224
234
|
|
|
225
235
|
for (const { error } of result.errors) {
|
|
226
236
|
ctx.ui.notify(`Action error: ${error.message}`, "warning");
|
|
@@ -234,6 +244,28 @@ export async function syncCommand(
|
|
|
234
244
|
// Always write a populated lock so "last sync" is accurate
|
|
235
245
|
const syncedLock = buildSyncedLock(catalog, installed);
|
|
236
246
|
writeLock(syncedLock, ctx.home);
|
|
247
|
+
|
|
248
|
+
// Compare rebuilt lock versions against remote to detect version drift
|
|
249
|
+
// from external `pi update` calls that bumped installed versions without
|
|
250
|
+
// changing the catalog source string.
|
|
251
|
+
if (pulledData) {
|
|
252
|
+
const remoteLock = pulledData.lock;
|
|
253
|
+
for (const [key, rebuiltEntry] of Object.entries(syncedLock.packages)) {
|
|
254
|
+
const remoteEntry = remoteLock.packages[key];
|
|
255
|
+
if (!remoteEntry || remoteEntry.version !== rebuiltEntry.version) {
|
|
256
|
+
rebuiltLockDiffers = true;
|
|
257
|
+
break;
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
if (!rebuiltLockDiffers) {
|
|
261
|
+
for (const key of Object.keys(remoteLock.packages)) {
|
|
262
|
+
if (!(key in syncedLock.packages)) {
|
|
263
|
+
rebuiltLockDiffers = true;
|
|
264
|
+
break;
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
}
|
|
237
269
|
}
|
|
238
270
|
|
|
239
271
|
// --- 5. Push if changed --------------------------------------------------
|
|
@@ -251,7 +283,8 @@ export async function syncCommand(
|
|
|
251
283
|
const hasGist = readCachedGistId(ctx.home) !== undefined;
|
|
252
284
|
const localHasPackages = Object.keys(catalog.packages).length > 0;
|
|
253
285
|
|
|
254
|
-
if (force || summary.actionCount > 0 || hasLocalOnlyPackages || hasLocalLockChanges || (!hasGist && localHasPackages)) {
|
|
286
|
+
if (force || summary.actionCount > 0 || hasLocalOnlyPackages || hasLocalLockChanges || rebuiltLockDiffers || (!hasGist && localHasPackages)) {
|
|
287
|
+
ctx.ui.setWorkingMessage?.("Pushing to gist...");
|
|
255
288
|
try {
|
|
256
289
|
const updatedCatalog = readCatalog(ctx.home);
|
|
257
290
|
const updatedLock = readLock(ctx.home);
|
|
@@ -268,6 +301,7 @@ export async function syncCommand(
|
|
|
268
301
|
ctx.ui.notify(`Push failed: ${message}`, "error");
|
|
269
302
|
summary.errors.push(message);
|
|
270
303
|
}
|
|
304
|
+
ctx.ui.setWorkingMessage?.();
|
|
271
305
|
}
|
|
272
306
|
|
|
273
307
|
// --- 6. Report summary ---------------------------------------------------
|
|
@@ -282,7 +316,7 @@ export async function syncCommand(
|
|
|
282
316
|
}
|
|
283
317
|
}
|
|
284
318
|
|
|
285
|
-
if (summary.actionCount === 0 && summary.errors.length === 0 && !force && !hasLocalOnlyPackages && !hasLocalLockChanges) {
|
|
319
|
+
if (summary.actionCount === 0 && summary.errors.length === 0 && !force && !hasLocalOnlyPackages && !hasLocalLockChanges && !rebuiltLockDiffers) {
|
|
286
320
|
ctx.ui.notify("Catalog already up to date.", "info");
|
|
287
321
|
return;
|
|
288
322
|
}
|
|
@@ -298,6 +332,9 @@ export async function syncCommand(
|
|
|
298
332
|
if (hasLocalLockChanges) {
|
|
299
333
|
parts.push("Pushed local version updates.");
|
|
300
334
|
}
|
|
335
|
+
if (rebuiltLockDiffers) {
|
|
336
|
+
parts.push("Rebuilt lock (version drift detected).");
|
|
337
|
+
}
|
|
301
338
|
if (plan.installs.length > 0) {
|
|
302
339
|
parts.push(`${plan.installs.length} install(s): ${plan.installs.map((a) => a.key).join(", ")}`);
|
|
303
340
|
}
|
package/src/commands/toggle.ts
CHANGED
|
@@ -1,12 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* `ct toggle`, `ct enable`, and `ct disable` subcommand implementations.
|
|
3
3
|
*
|
|
4
|
-
* - `ct toggle <name>`
|
|
5
|
-
*
|
|
6
|
-
* - `ct
|
|
7
|
-
* (or "core" if no previous rating stored). No-op when already enabled.
|
|
8
|
-
* - `ct disable <name>` sets rating to disabled, saves the previous rating,
|
|
9
|
-
* and runs `pi uninstall` to remove the package.
|
|
4
|
+
* - `ct toggle <name>` toggles a package's enabled state (enabled ↔ disabled)
|
|
5
|
+
* - `ct enable <name>` enables a disabled package. No-op when already enabled.
|
|
6
|
+
* - `ct disable <name>` disables a package and runs `pi uninstall`.
|
|
10
7
|
*
|
|
11
8
|
* All commands read/write `cat.yaml` via `readCatalog` / `writeCatalog`
|
|
12
9
|
* and provide user feedback through `ctx.ui.notify`.
|
|
@@ -31,7 +28,7 @@ export type ToggleCtx = CommandCtx;
|
|
|
31
28
|
/**
|
|
32
29
|
* Execute the `ct toggle` subcommand.
|
|
33
30
|
*
|
|
34
|
-
*
|
|
31
|
+
* Toggles the package's enabled state: enabled ↔ disabled.
|
|
35
32
|
*/
|
|
36
33
|
export async function toggleCommand(
|
|
37
34
|
args: CommandArgs,
|
|
@@ -49,8 +46,9 @@ export async function toggleCommand(
|
|
|
49
46
|
try {
|
|
50
47
|
const updated = togglePackage(catalog, name);
|
|
51
48
|
writeCatalog(updated, ctx.home);
|
|
49
|
+
const isEnabled = updated.packages[name].enabled !== false;
|
|
52
50
|
ctx.ui.notify(
|
|
53
|
-
`Toggled "${name}"
|
|
51
|
+
`Toggled "${name}" — now ${isEnabled ? "enabled" : "disabled"}`,
|
|
54
52
|
"info",
|
|
55
53
|
);
|
|
56
54
|
} catch (err: unknown) {
|
|
@@ -66,8 +64,7 @@ export async function toggleCommand(
|
|
|
66
64
|
/**
|
|
67
65
|
* Execute the `ct enable` subcommand.
|
|
68
66
|
*
|
|
69
|
-
*
|
|
70
|
-
* No-op when the package is already enabled.
|
|
67
|
+
* Enables a disabled package. No-op when the package is already enabled.
|
|
71
68
|
*/
|
|
72
69
|
export async function enableCommand(
|
|
73
70
|
args: CommandArgs,
|
|
@@ -92,10 +89,7 @@ export async function enableCommand(
|
|
|
92
89
|
}
|
|
93
90
|
|
|
94
91
|
writeCatalog(updated, ctx.home);
|
|
95
|
-
ctx.ui.notify(
|
|
96
|
-
`Enabled "${name}" (rating: ${updated.packages[name].rating})`,
|
|
97
|
-
"info",
|
|
98
|
-
);
|
|
92
|
+
ctx.ui.notify(`Enabled "${name}"`, "info");
|
|
99
93
|
} catch (err: unknown) {
|
|
100
94
|
const message = err instanceof Error ? err.message : String(err);
|
|
101
95
|
ctx.ui.notify(message, "error");
|
|
@@ -109,8 +103,7 @@ export async function enableCommand(
|
|
|
109
103
|
/**
|
|
110
104
|
* Execute the `ct disable` subcommand.
|
|
111
105
|
*
|
|
112
|
-
*
|
|
113
|
-
* restoration, and runs `pi uninstall` to remove the package.
|
|
106
|
+
* Disables a package and runs `pi uninstall` to remove it.
|
|
114
107
|
*/
|
|
115
108
|
export async function disableCommand(
|
|
116
109
|
args: CommandArgs,
|
|
@@ -136,6 +129,7 @@ export async function disableCommand(
|
|
|
136
129
|
}
|
|
137
130
|
|
|
138
131
|
// Run pi uninstall after disabling
|
|
132
|
+
ctx.ui.setWorkingMessage?.(`Uninstalling ${name}...`);
|
|
139
133
|
try {
|
|
140
134
|
await piUninstall(name);
|
|
141
135
|
} catch {
|
|
@@ -144,4 +138,5 @@ export async function disableCommand(
|
|
|
144
138
|
"warning",
|
|
145
139
|
);
|
|
146
140
|
}
|
|
141
|
+
ctx.ui.setWorkingMessage?.();
|
|
147
142
|
}
|
package/src/commands/types.ts
CHANGED
|
@@ -32,6 +32,8 @@ export interface CommandArgs {
|
|
|
32
32
|
export interface CommandCtx {
|
|
33
33
|
ui: {
|
|
34
34
|
notify: (msg: string, type?: "error" | "info" | "warning") => void;
|
|
35
|
+
/** Show a temporary working message (e.g. "Adding..."). Pass undefined or no arg to clear. */
|
|
36
|
+
setWorkingMessage?: (msg?: string) => void;
|
|
35
37
|
};
|
|
36
38
|
/** Home directory override (for testing). */
|
|
37
39
|
home?: string;
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `ct update` subcommand implementation.
|
|
3
|
+
*
|
|
4
|
+
* Updates packages to their latest versions by running `pi update <source>`.
|
|
5
|
+
*
|
|
6
|
+
* Usage:
|
|
7
|
+
* - `ct update <name>` — update a single package by catalog name
|
|
8
|
+
* - `ct update --all` — update all packages in the catalog
|
|
9
|
+
*
|
|
10
|
+
* After updating, a `/ct sync` should be run to persist changes to the remote gist.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import type { CommandArgs, CommandCtx } from "./types.js";
|
|
14
|
+
import { readCatalog } from "../config/io.js";
|
|
15
|
+
import { piUpdate } from "../util/exec.js";
|
|
16
|
+
import { checkSetupForSource, formatSetupStatus } from "../catalog/setup.js";
|
|
17
|
+
|
|
18
|
+
// ---------------------------------------------------------------------------
|
|
19
|
+
// updateCommand
|
|
20
|
+
// ---------------------------------------------------------------------------
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Execute the `ct update` subcommand.
|
|
24
|
+
*
|
|
25
|
+
* Reads the catalog, resolves the target package(s), and runs
|
|
26
|
+
* `pi update <source>` for each.
|
|
27
|
+
*/
|
|
28
|
+
export async function updateCommand(
|
|
29
|
+
args: CommandArgs,
|
|
30
|
+
ctx: CommandCtx,
|
|
31
|
+
): Promise<void> {
|
|
32
|
+
const { positional, flags } = args;
|
|
33
|
+
const updateAll = "all" in flags;
|
|
34
|
+
const name = positional[0];
|
|
35
|
+
|
|
36
|
+
if (!name && !updateAll) {
|
|
37
|
+
ctx.ui.notify("Usage: ct update <name> | ct update --all", "error");
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const catalog = readCatalog(ctx.home);
|
|
42
|
+
const packages = catalog.packages;
|
|
43
|
+
|
|
44
|
+
// --- Single package update ------------------------------------------------
|
|
45
|
+
if (name) {
|
|
46
|
+
const entry = packages[name];
|
|
47
|
+
if (!entry) {
|
|
48
|
+
ctx.ui.notify(`Package "${name}" not found in catalog`, "error");
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
ctx.ui.setWorkingMessage?.(`Updating ${name}...`);
|
|
53
|
+
try {
|
|
54
|
+
await piUpdate(entry.source);
|
|
55
|
+
ctx.ui.notify(`Updated "${name}"`, "info");
|
|
56
|
+
} catch {
|
|
57
|
+
ctx.ui.notify(`Warning: update of "${name}" failed`, "warning");
|
|
58
|
+
}
|
|
59
|
+
ctx.ui.setWorkingMessage?.();
|
|
60
|
+
|
|
61
|
+
// Check setup requirements after update
|
|
62
|
+
const setup = checkSetupForSource(entry.source, ctx.home);
|
|
63
|
+
if (setup && !setup.ok) {
|
|
64
|
+
ctx.ui.notify(
|
|
65
|
+
`Setup incomplete for "${name}": ${formatSetupStatus(setup)}`,
|
|
66
|
+
"warning",
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// --- Update all -----------------------------------------------------------
|
|
73
|
+
const names = Object.keys(packages);
|
|
74
|
+
if (names.length === 0) {
|
|
75
|
+
ctx.ui.notify("Catalog is empty — nothing to update", "info");
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
let updated = 0;
|
|
80
|
+
let failed = 0;
|
|
81
|
+
const setupWarnings: string[] = [];
|
|
82
|
+
|
|
83
|
+
for (const pkgName of names) {
|
|
84
|
+
const entry = packages[pkgName];
|
|
85
|
+
ctx.ui.setWorkingMessage?.(`Updating ${pkgName} (${updated + 1}/${names.length})...`);
|
|
86
|
+
try {
|
|
87
|
+
await piUpdate(entry.source);
|
|
88
|
+
updated++;
|
|
89
|
+
|
|
90
|
+
// Check setup after successful update
|
|
91
|
+
const setup = checkSetupForSource(entry.source, ctx.home);
|
|
92
|
+
if (setup && !setup.ok) {
|
|
93
|
+
setupWarnings.push(`${pkgName}: ${formatSetupStatus(setup)}`);
|
|
94
|
+
}
|
|
95
|
+
} catch {
|
|
96
|
+
ctx.ui.notify(`Warning: update of "${pkgName}" failed`, "warning");
|
|
97
|
+
failed++;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
ctx.ui.setWorkingMessage?.();
|
|
101
|
+
|
|
102
|
+
const parts: string[] = [
|
|
103
|
+
`Updated ${updated}/${names.length} packages${failed > 0 ? ` (${failed} failed)` : ""}`,
|
|
104
|
+
];
|
|
105
|
+
if (setupWarnings.length > 0) {
|
|
106
|
+
parts.push(`Setup incomplete:\n ${setupWarnings.join("\n ")}`);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
ctx.ui.notify(
|
|
110
|
+
parts.join("\n"),
|
|
111
|
+
failed > 0 || setupWarnings.length > 0 ? "warning" : "info",
|
|
112
|
+
);
|
|
113
|
+
}
|
package/src/config/io.ts
CHANGED
|
@@ -4,6 +4,7 @@ import yaml from "js-yaml";
|
|
|
4
4
|
import { catalogFile, lockFile, ensureCatalogDir } from "./paths.js";
|
|
5
5
|
import { CatalogYamlSchema, LockFileSchema } from "./schema.js";
|
|
6
6
|
import type { CatalogYaml, LockFile } from "./schema.js";
|
|
7
|
+
import { migrateRatingToEnabledRaw } from "../catalog/migrate.js";
|
|
7
8
|
|
|
8
9
|
// ---------------------------------------------------------------------------
|
|
9
10
|
// Empty defaults
|
|
@@ -37,6 +38,8 @@ export function readCatalog(home?: string): CatalogYaml {
|
|
|
37
38
|
}
|
|
38
39
|
|
|
39
40
|
const parsed = yaml.load(raw);
|
|
41
|
+
// Migrate rating → enabled before Zod validation strips the unknown field
|
|
42
|
+
migrateRatingToEnabledRaw(parsed);
|
|
40
43
|
return CatalogYamlSchema.parse(parsed);
|
|
41
44
|
}
|
|
42
45
|
|
package/src/config/schema.ts
CHANGED
|
@@ -7,23 +7,16 @@ import { z } from "zod";
|
|
|
7
7
|
/** Type discriminator for a catalog package entry. */
|
|
8
8
|
export const PackageType = z.enum(["skill", "pi-native"]);
|
|
9
9
|
|
|
10
|
-
/** Rating values for catalog packages. */
|
|
11
|
-
export const Rating = z.enum(["core", "useful", "debatable", "disabled"]);
|
|
12
|
-
|
|
13
10
|
/** A single package entry inside cat.yaml. */
|
|
14
11
|
export const CatalogPackageSchema = z.object({
|
|
15
12
|
/** Where to fetch the package from (URL, path, etc.). */
|
|
16
13
|
source: z.string().min(1),
|
|
17
|
-
/** User-assigned rating. */
|
|
18
|
-
rating: Rating,
|
|
19
14
|
/** Optional type discriminator. */
|
|
20
15
|
type: PackageType.optional(),
|
|
21
16
|
/** Optional profile name this package belongs to. */
|
|
22
17
|
profile: z.string().optional(),
|
|
23
18
|
/** Whether the package is active. Defaults to true when absent. */
|
|
24
19
|
enabled: z.boolean().optional(),
|
|
25
|
-
/** Previous rating before disable; used by enablePackage to restore. */
|
|
26
|
-
previousRating: Rating.optional(),
|
|
27
20
|
});
|
|
28
21
|
|
|
29
22
|
/** The meta section at the top of cat.yaml. */
|
|
@@ -84,4 +77,3 @@ export type CatalogPackage = z.infer<typeof CatalogPackageSchema>;
|
|
|
84
77
|
export type Profile = z.infer<typeof ProfileSchema>;
|
|
85
78
|
export type LockFile = z.infer<typeof LockFileSchema>;
|
|
86
79
|
export type LockPackage = z.infer<typeof LockPackageSchema>;
|
|
87
|
-
export type RatingValue = z.infer<typeof Rating>;
|
package/src/register.ts
CHANGED
|
@@ -15,6 +15,8 @@ import {
|
|
|
15
15
|
import { addCommand, type AddCtx } from "./commands/add.js";
|
|
16
16
|
import { initCommand, type InitContext } from "./commands/init.js";
|
|
17
17
|
import { removeCommand, type RemoveCtx } from "./commands/remove.js";
|
|
18
|
+
import { updateCommand } from "./commands/update.js";
|
|
19
|
+
import { resetCommand, type ResetCtx } from "./commands/reset.js";
|
|
18
20
|
import {
|
|
19
21
|
toggleCommand,
|
|
20
22
|
enableCommand,
|
|
@@ -109,6 +111,9 @@ async function handleSubcommand(
|
|
|
109
111
|
case "toggle":
|
|
110
112
|
await toggleCommand(parsed, ctx as ToggleCtx);
|
|
111
113
|
break;
|
|
114
|
+
case "update":
|
|
115
|
+
await updateCommand(parsed, ctx);
|
|
116
|
+
break;
|
|
112
117
|
case "enable":
|
|
113
118
|
await enableCommand(parsed, ctx as ToggleCtx);
|
|
114
119
|
break;
|
|
@@ -133,6 +138,9 @@ async function handleSubcommand(
|
|
|
133
138
|
case "profile":
|
|
134
139
|
await profileCommand(parsed, ctx as ProfilesCtx);
|
|
135
140
|
break;
|
|
141
|
+
case "reset":
|
|
142
|
+
await resetCommand(parsed, ctx as ResetCtx);
|
|
143
|
+
break;
|
|
136
144
|
default:
|
|
137
145
|
ctx.ui.notify(`ct ${canonical}: not yet implemented`, "info");
|
|
138
146
|
}
|
|
@@ -209,24 +217,25 @@ export function registerCatalog(pi: ExtensionAPI): void {
|
|
|
209
217
|
name: "ct_add",
|
|
210
218
|
label: "Catalog Add",
|
|
211
219
|
description:
|
|
212
|
-
"Add a package to the catalog by
|
|
220
|
+
"Add a package to the catalog by source. Name is auto-derived from source. Source must start with 'npm:' or 'git:'.",
|
|
213
221
|
promptSnippet: "Add a package to the catalog",
|
|
214
222
|
promptGuidelines: [
|
|
215
223
|
"Use ct_add when the user asks to add a new package or skill to their catalog.",
|
|
216
224
|
],
|
|
217
225
|
parameters: Type.Object({
|
|
218
|
-
name: Type.String({ description: "Package name" }),
|
|
219
226
|
source: Type.String({ description: "Package source (npm:… or git:…)" }),
|
|
220
|
-
|
|
227
|
+
scope: Type.Optional(Type.String({ description: "Batch scope: '@pi-stef' to add all @pi-stef packages" })),
|
|
221
228
|
}),
|
|
222
229
|
async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
|
|
223
230
|
try {
|
|
231
|
+
const flags: Record<string, true | string> = {};
|
|
232
|
+
if (params.scope) flags.scope = params.scope;
|
|
224
233
|
const args: CommandArgs = {
|
|
225
|
-
positional:
|
|
226
|
-
flags
|
|
234
|
+
positional: params.scope ? [] : [params.source],
|
|
235
|
+
flags,
|
|
227
236
|
};
|
|
228
237
|
await addCommand(args, ctx as unknown as AddCtx);
|
|
229
|
-
return { content: [{ type: "text" as const, text: `Added ${params.
|
|
238
|
+
return { content: [{ type: "text" as const, text: `Added ${params.source}.` }], details: undefined as unknown };
|
|
230
239
|
} catch (err) {
|
|
231
240
|
return { content: [{ type: "text" as const, text: `Add failed: ${err instanceof Error ? err.message : String(err)}` }], details: undefined as unknown };
|
|
232
241
|
}
|
|
@@ -242,13 +251,20 @@ export function registerCatalog(pi: ExtensionAPI): void {
|
|
|
242
251
|
"Use ct_remove when the user asks to remove or uninstall a package from their catalog.",
|
|
243
252
|
],
|
|
244
253
|
parameters: Type.Object({
|
|
245
|
-
name: Type.String({ description: "Package name to remove" }),
|
|
254
|
+
name: Type.Optional(Type.String({ description: "Package name to remove" })),
|
|
255
|
+
scope: Type.Optional(Type.String({ description: "Batch scope: '@pi-stef' to remove all @pi-stef packages" })),
|
|
246
256
|
}),
|
|
247
257
|
async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
|
|
248
258
|
try {
|
|
249
|
-
const
|
|
259
|
+
const flags: Record<string, true | string> = {};
|
|
260
|
+
if (params.scope) flags.scope = params.scope;
|
|
261
|
+
const args: CommandArgs = {
|
|
262
|
+
positional: params.name ? [params.name] : [],
|
|
263
|
+
flags,
|
|
264
|
+
};
|
|
250
265
|
await removeCommand(args, ctx as unknown as RemoveCtx);
|
|
251
|
-
|
|
266
|
+
const label = params.scope ? `Scope ${params.scope}` : `${params.name}`;
|
|
267
|
+
return { content: [{ type: "text" as const, text: `Removed ${label}.` }], details: undefined as unknown };
|
|
252
268
|
} catch (err) {
|
|
253
269
|
return { content: [{ type: "text" as const, text: `Remove failed: ${err instanceof Error ? err.message : String(err)}` }], details: undefined as unknown };
|
|
254
270
|
}
|
|
@@ -259,10 +275,10 @@ export function registerCatalog(pi: ExtensionAPI): void {
|
|
|
259
275
|
name: "ct_toggle",
|
|
260
276
|
label: "Catalog Toggle",
|
|
261
277
|
description:
|
|
262
|
-
"Toggle a package's
|
|
263
|
-
promptSnippet: "Toggle a package's
|
|
278
|
+
"Toggle a package's enabled state (enabled ↔ disabled).",
|
|
279
|
+
promptSnippet: "Toggle a package's enabled state",
|
|
264
280
|
promptGuidelines: [
|
|
265
|
-
"Use ct_toggle when the user wants to
|
|
281
|
+
"Use ct_toggle when the user wants to enable or disable a package.",
|
|
266
282
|
],
|
|
267
283
|
parameters: Type.Object({
|
|
268
284
|
name: Type.String({ description: "Package name to toggle" }),
|
|
@@ -278,6 +294,58 @@ export function registerCatalog(pi: ExtensionAPI): void {
|
|
|
278
294
|
},
|
|
279
295
|
});
|
|
280
296
|
|
|
297
|
+
pi.registerTool({
|
|
298
|
+
name: "ct_update",
|
|
299
|
+
label: "Catalog Update",
|
|
300
|
+
description:
|
|
301
|
+
"Update packages to their latest versions. Run `pi update` behind the scenes.",
|
|
302
|
+
promptSnippet: "Update catalog packages",
|
|
303
|
+
promptGuidelines: [
|
|
304
|
+
"Use ct_update when the user asks to update one or more packages in their catalog.",
|
|
305
|
+
],
|
|
306
|
+
parameters: Type.Object({
|
|
307
|
+
name: Type.Optional(Type.String({ description: "Package name to update (omit for --all)" })),
|
|
308
|
+
all: Type.Optional(Type.Boolean({ description: "Update all packages" })),
|
|
309
|
+
}),
|
|
310
|
+
async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
|
|
311
|
+
try {
|
|
312
|
+
const positional = params.name ? [params.name] : [];
|
|
313
|
+
const flags: Record<string, true | string> = {};
|
|
314
|
+
if (params.all) flags.all = true;
|
|
315
|
+
const args: CommandArgs = { positional, flags };
|
|
316
|
+
await updateCommand(args, ctx);
|
|
317
|
+
return { content: [{ type: "text" as const, text: "Update completed." }], details: undefined as unknown };
|
|
318
|
+
} catch (err) {
|
|
319
|
+
return { content: [{ type: "text" as const, text: `Update failed: ${err instanceof Error ? err.message : String(err)}` }], details: undefined as unknown };
|
|
320
|
+
}
|
|
321
|
+
},
|
|
322
|
+
});
|
|
323
|
+
|
|
324
|
+
pi.registerTool({
|
|
325
|
+
name: "ct_reset",
|
|
326
|
+
label: "Catalog Reset",
|
|
327
|
+
description:
|
|
328
|
+
"Full nuke: uninstall all @pi-stef packages, delete gist refs, delete config files.",
|
|
329
|
+
promptSnippet: "Reset catalog completely",
|
|
330
|
+
promptGuidelines: [
|
|
331
|
+
"Use ct_reset when the user wants to completely remove all @pi-stef packages and catalog config.",
|
|
332
|
+
],
|
|
333
|
+
parameters: Type.Object({
|
|
334
|
+
yes: Type.Optional(Type.Boolean({ description: "Skip confirmation prompt" })),
|
|
335
|
+
}),
|
|
336
|
+
async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
|
|
337
|
+
try {
|
|
338
|
+
const flags: Record<string, true | string> = {};
|
|
339
|
+
if (params.yes) flags.yes = true;
|
|
340
|
+
const args: CommandArgs = { positional: [], flags };
|
|
341
|
+
await resetCommand(args, ctx as unknown as ResetCtx);
|
|
342
|
+
return { content: [{ type: "text" as const, text: "Reset completed." }], details: undefined as unknown };
|
|
343
|
+
} catch (err) {
|
|
344
|
+
return { content: [{ type: "text" as const, text: `Reset failed: ${err instanceof Error ? err.message : String(err)}` }], details: undefined as unknown };
|
|
345
|
+
}
|
|
346
|
+
},
|
|
347
|
+
});
|
|
348
|
+
|
|
281
349
|
pi.registerTool({
|
|
282
350
|
name: "ct_status",
|
|
283
351
|
label: "Catalog Status",
|
package/src/sync/pull.ts
CHANGED
|
@@ -2,6 +2,7 @@ import yaml from "js-yaml";
|
|
|
2
2
|
|
|
3
3
|
import { CatalogYamlSchema, LockFileSchema } from "../config/schema.js";
|
|
4
4
|
import type { CatalogYaml, LockFile } from "../config/schema.js";
|
|
5
|
+
import { migrateRatingToEnabledRaw } from "../catalog/migrate.js";
|
|
5
6
|
import { readGist, findGistByDescription } from "./gist.js";
|
|
6
7
|
import { readCachedGistId, writeCachedGistId } from "./cache.js";
|
|
7
8
|
|
|
@@ -62,6 +63,7 @@ export async function pullCatalog(
|
|
|
62
63
|
const lockJsonContent = gist.files["catalog.lock.json"]?.content ?? "";
|
|
63
64
|
|
|
64
65
|
const parsedYaml = yaml.load(catYamlContent);
|
|
66
|
+
migrateRatingToEnabledRaw(parsedYaml);
|
|
65
67
|
const catalog: CatalogYaml = CatalogYamlSchema.parse(parsedYaml);
|
|
66
68
|
|
|
67
69
|
const parsedLock = JSON.parse(lockJsonContent);
|
package/src/util/exec.ts
CHANGED
|
@@ -158,3 +158,15 @@ export function piUninstall(
|
|
|
158
158
|
): Promise<ExecResult> {
|
|
159
159
|
return execCommand("pi", ["uninstall", packageName], options);
|
|
160
160
|
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Update a pi package from the given source.
|
|
164
|
+
*
|
|
165
|
+
* Runs `pi update <source>`.
|
|
166
|
+
*/
|
|
167
|
+
export function piUpdate(
|
|
168
|
+
source: string,
|
|
169
|
+
options?: PiExecOptions,
|
|
170
|
+
): Promise<ExecResult> {
|
|
171
|
+
return execCommand("pi", ["update", source], options);
|
|
172
|
+
}
|