@pi-stef/catalog 0.3.5 → 0.4.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/package.json +1 -1
- package/src/catalog/packages.ts +56 -0
- package/src/commands/add.ts +84 -5
- package/src/commands/definitions.ts +2 -0
- package/src/commands/remove.ts +72 -0
- package/src/commands/reset.ts +134 -0
- package/src/commands/update.ts +85 -0
- package/src/register.ts +78 -8
- package/src/util/exec.ts +12 -0
package/package.json
CHANGED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hardcoded list of @pi-stef packages for scope-based batch operations.
|
|
3
|
+
*
|
|
4
|
+
* Used by `ct add --scope @pi-stef` and `ct remove --scope @pi-stef`
|
|
5
|
+
* to identify which packages belong to the @pi-stef ecosystem.
|
|
6
|
+
*
|
|
7
|
+
* NOTE: `@pi-stef/catalog` is intentionally excluded — it manages the
|
|
8
|
+
* other packages and should not be batch-operated on itself.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { extractNpmName } from "./source.js";
|
|
12
|
+
|
|
13
|
+
/** The catalog package itself (excluded from batch operations). */
|
|
14
|
+
export const CATALOG_PACKAGE_NAME = "@pi-stef/catalog";
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* All @pi-stef packages except the catalog itself.
|
|
18
|
+
*
|
|
19
|
+
* This list is used for `--scope @pi-stef` batch operations.
|
|
20
|
+
*/
|
|
21
|
+
export const PI_STEF_PACKAGES: readonly string[] = [
|
|
22
|
+
"@pi-stef/agent-workflows",
|
|
23
|
+
"@pi-stef/atlassian",
|
|
24
|
+
"@pi-stef/figma",
|
|
25
|
+
"@pi-stef/paths",
|
|
26
|
+
"@pi-stef/team",
|
|
27
|
+
"@pi-stef/web",
|
|
28
|
+
] as const;
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Returns true if the given package name is a @pi-stef package
|
|
32
|
+
* (excluding the catalog itself).
|
|
33
|
+
*/
|
|
34
|
+
export function isPiStefPackage(name: string): boolean {
|
|
35
|
+
return PI_STEF_PACKAGES.includes(name);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Returns true if the source string refers to a @pi-stef package.
|
|
40
|
+
*
|
|
41
|
+
* Handles both `npm:@scope/pkg@version` and bare package name formats.
|
|
42
|
+
* Explicitly excludes `@pi-stef/catalog` — it manages the others
|
|
43
|
+
* and should not be included in batch scope operations.
|
|
44
|
+
*/
|
|
45
|
+
export function isPiStefSource(source: string): boolean {
|
|
46
|
+
// npm: prefixed source — extract the package name
|
|
47
|
+
if (source.startsWith("npm:")) {
|
|
48
|
+
const pkgName = extractNpmName(source.slice(4));
|
|
49
|
+
// Never include the catalog package itself in batch operations
|
|
50
|
+
if (pkgName === CATALOG_PACKAGE_NAME) return false;
|
|
51
|
+
return PI_STEF_PACKAGES.includes(pkgName);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Non-npm source — check if it's a bare package name
|
|
55
|
+
return PI_STEF_PACKAGES.includes(source);
|
|
56
|
+
}
|
package/src/commands/add.ts
CHANGED
|
@@ -13,6 +13,8 @@
|
|
|
13
13
|
import type { RatingValue } from "../catalog/ratings.js";
|
|
14
14
|
import type { CommandArgs, CommandCtx } from "./types.js";
|
|
15
15
|
import { addPackage } from "../catalog/crud.js";
|
|
16
|
+
import { sourceToKey } from "../catalog/source.js";
|
|
17
|
+
import { PI_STEF_PACKAGES } from "../catalog/packages.js";
|
|
16
18
|
import { readCatalog, writeCatalog } from "../config/io.js";
|
|
17
19
|
import { piInstall } from "../util/exec.js";
|
|
18
20
|
|
|
@@ -75,18 +77,95 @@ function resolveType(
|
|
|
75
77
|
/**
|
|
76
78
|
* Execute the `ct add` subcommand.
|
|
77
79
|
*
|
|
80
|
+
* New syntax (preferred): `ct add <source> [--rating ...] [--type ...]`
|
|
81
|
+
* — name is auto-derived from source via `sourceToKey()`.
|
|
82
|
+
*
|
|
83
|
+
* Legacy syntax (deprecated): `ct add <name> <source> [--rating ...] [--type ...]`
|
|
84
|
+
* — still accepted but emits a deprecation warning.
|
|
85
|
+
*
|
|
78
86
|
* Reads the catalog, validates inputs, prompts for type if needed,
|
|
79
87
|
* adds the package, writes the catalog, and runs `pi install`.
|
|
80
88
|
*/
|
|
81
89
|
export async function addCommand(args: CommandArgs, ctx: AddCtx): Promise<void> {
|
|
82
90
|
const { positional, flags } = args;
|
|
83
|
-
const name = positional[0];
|
|
84
|
-
const source = positional[1];
|
|
85
91
|
|
|
86
|
-
// ---
|
|
87
|
-
if (
|
|
92
|
+
// --- Handle --scope batch mode ---------------------------------------------
|
|
93
|
+
if ("scope" in flags) {
|
|
94
|
+
const scope = flags["scope"];
|
|
95
|
+
if (scope !== "@pi-stef") {
|
|
96
|
+
ctx.ui.notify(`Unsupported scope: "${scope}". Use --scope @pi-stef.`, "error");
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const catalog = readCatalog(ctx.home);
|
|
101
|
+
const rating = resolveRating(flags);
|
|
102
|
+
let added = 0;
|
|
103
|
+
let skipped = 0;
|
|
104
|
+
let currentCatalog = catalog;
|
|
105
|
+
|
|
106
|
+
for (const pkg of PI_STEF_PACKAGES) {
|
|
107
|
+
const npmSource = `npm:${pkg}`;
|
|
108
|
+
|
|
109
|
+
// Skip if already in catalog
|
|
110
|
+
if (currentCatalog.packages[pkg]) {
|
|
111
|
+
skipped++;
|
|
112
|
+
continue;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
try {
|
|
116
|
+
currentCatalog = addPackage(currentCatalog, pkg, npmSource, rating);
|
|
117
|
+
added++;
|
|
118
|
+
} catch (err: unknown) {
|
|
119
|
+
// Unexpected validation error — warn but continue
|
|
120
|
+
ctx.ui.notify(
|
|
121
|
+
`Warning: failed to add "${pkg}": ${err instanceof Error ? err.message : String(err)}`,
|
|
122
|
+
"warning",
|
|
123
|
+
);
|
|
124
|
+
skipped++;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
if (added > 0) {
|
|
129
|
+
writeCatalog(currentCatalog, ctx.home);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// Install all added packages
|
|
133
|
+
if (added > 0) {
|
|
134
|
+
for (const pkg of PI_STEF_PACKAGES) {
|
|
135
|
+
if (currentCatalog.packages[pkg]?.source === `npm:${pkg}`) {
|
|
136
|
+
try {
|
|
137
|
+
await piInstall(`npm:${pkg}`);
|
|
138
|
+
} catch {
|
|
139
|
+
ctx.ui.notify(`Warning: install of "${pkg}" failed`, "warning");
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
ctx.ui.notify(
|
|
146
|
+
`Scope @pi-stef: added ${added}, skipped ${skipped} (already in catalog)`,
|
|
147
|
+
"info",
|
|
148
|
+
);
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// --- Handle legacy 2-arg syntax: ct add <name> <source> -------------------
|
|
153
|
+
let name: string;
|
|
154
|
+
let source: string;
|
|
155
|
+
|
|
156
|
+
if (positional.length >= 2) {
|
|
157
|
+
name = positional[0];
|
|
158
|
+
source = positional[1];
|
|
159
|
+
ctx.ui.notify(
|
|
160
|
+
`"ct add <name> <source>" is legacy. Use "ct add <source>" — name is auto-derived.`,
|
|
161
|
+
"warning",
|
|
162
|
+
);
|
|
163
|
+
} else if (positional.length === 1) {
|
|
164
|
+
source = positional[0];
|
|
165
|
+
name = sourceToKey(source);
|
|
166
|
+
} else {
|
|
88
167
|
ctx.ui.notify(
|
|
89
|
-
"Usage: ct add <
|
|
168
|
+
"Usage: ct add <source> [--rating <core|useful|debatable>] [--type <skill|pi-native>]",
|
|
90
169
|
"error",
|
|
91
170
|
);
|
|
92
171
|
return;
|
|
@@ -31,6 +31,7 @@ export const SUBCOMMAND_DEFS: readonly SubcommandDef[] = [
|
|
|
31
31
|
{ name: "add", aliases: ["a"], description: "Add a package to the catalog" },
|
|
32
32
|
{ name: "remove", aliases: ["rm"], description: "Remove a package from the catalog" },
|
|
33
33
|
{ name: "toggle", description: "Toggle a package's rating" },
|
|
34
|
+
{ name: "update", aliases: ["up"], description: "Update packages to latest versions" },
|
|
34
35
|
{ name: "disable", description: "Disable a package" },
|
|
35
36
|
{ name: "enable", description: "Enable a package" },
|
|
36
37
|
{ name: "push", description: "Push catalog to remote gist" },
|
|
@@ -41,6 +42,7 @@ export const SUBCOMMAND_DEFS: readonly SubcommandDef[] = [
|
|
|
41
42
|
{ name: "verify", description: "Verify catalog integrity" },
|
|
42
43
|
{ name: "profiles", description: "List available profiles" },
|
|
43
44
|
{ name: "profile", description: "Show or switch active profile" },
|
|
45
|
+
{ name: "reset", description: "Reset catalog: uninstall all @pi-stef packages and delete config" },
|
|
44
46
|
] as const;
|
|
45
47
|
|
|
46
48
|
// ---------------------------------------------------------------------------
|
package/src/commands/remove.ts
CHANGED
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
|
|
13
13
|
import type { CommandArgs, CommandCtx } from "./types.js";
|
|
14
14
|
import { removePackage } from "../catalog/crud.js";
|
|
15
|
+
import { isPiStefSource } from "../catalog/packages.js";
|
|
15
16
|
import { readCatalog, writeCatalog, readLock, writeLock } from "../config/io.js";
|
|
16
17
|
import { piUninstall } from "../util/exec.js";
|
|
17
18
|
|
|
@@ -41,6 +42,77 @@ export async function removeCommand(
|
|
|
41
42
|
ctx: RemoveCtx,
|
|
42
43
|
): Promise<void> {
|
|
43
44
|
const { positional, flags } = args;
|
|
45
|
+
|
|
46
|
+
// --- Handle --scope batch mode ---------------------------------------------
|
|
47
|
+
if ("scope" in flags) {
|
|
48
|
+
const scope = flags["scope"];
|
|
49
|
+
if (scope !== "@pi-stef") {
|
|
50
|
+
ctx.ui.notify(`Unsupported scope: "${scope}". Use --scope @pi-stef.`, "error");
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const catalog = readCatalog(ctx.home);
|
|
55
|
+
const lock = readLock(ctx.home);
|
|
56
|
+
|
|
57
|
+
// isPiStefSource returns false for @pi-stef/catalog by design (see packages.ts)
|
|
58
|
+
const piStefNames = Object.keys(catalog.packages).filter(
|
|
59
|
+
(name) => isPiStefSource(catalog.packages[name].source),
|
|
60
|
+
);
|
|
61
|
+
|
|
62
|
+
if (piStefNames.length === 0) {
|
|
63
|
+
ctx.ui.notify("No @pi-stef packages found in catalog", "info");
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Confirmation
|
|
68
|
+
const skipConfirm = "yes" in flags || "y" in flags;
|
|
69
|
+
if (!skipConfirm && ctx.ui.confirm) {
|
|
70
|
+
const confirmed = await ctx.ui.confirm(
|
|
71
|
+
`Remove ${piStefNames.length} @pi-stef packages from catalog?`,
|
|
72
|
+
);
|
|
73
|
+
if (!confirmed) {
|
|
74
|
+
ctx.ui.notify("Removal cancelled", "info");
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// Capture sources before removing
|
|
80
|
+
const sources: Record<string, string> = {};
|
|
81
|
+
for (const name of piStefNames) {
|
|
82
|
+
sources[name] = catalog.packages[name].source;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// Remove all from catalog and lock file
|
|
86
|
+
for (const name of piStefNames) {
|
|
87
|
+
delete catalog.packages[name];
|
|
88
|
+
if (lock.packages[name]) {
|
|
89
|
+
delete lock.packages[name];
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
writeCatalog(catalog, ctx.home);
|
|
94
|
+
writeLock(lock, ctx.home);
|
|
95
|
+
|
|
96
|
+
// Uninstall all
|
|
97
|
+
let uninstalled = 0;
|
|
98
|
+
let failed = 0;
|
|
99
|
+
for (const name of piStefNames) {
|
|
100
|
+
try {
|
|
101
|
+
await piUninstall(sources[name]);
|
|
102
|
+
uninstalled++;
|
|
103
|
+
} catch {
|
|
104
|
+
ctx.ui.notify(`Warning: uninstall of "${name}" failed`, "warning");
|
|
105
|
+
failed++;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
ctx.ui.notify(
|
|
110
|
+
`Scope @pi-stef: removed ${piStefNames.length}, uninstalled ${uninstalled}${failed > 0 ? ` (${failed} uninstall failed)` : ""}`,
|
|
111
|
+
failed > 0 ? "warning" : "info",
|
|
112
|
+
);
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
|
|
44
116
|
const name = positional[0];
|
|
45
117
|
|
|
46
118
|
// --- Validate required args -----------------------------------------------
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `ct reset` subcommand implementation.
|
|
3
|
+
*
|
|
4
|
+
* Full nuke: uninstalls all @pi-stef packages, deletes gist refs,
|
|
5
|
+
* deletes config files (cat.yaml, catalog.lock.json, .gist),
|
|
6
|
+
* and removes the empty catalog directory.
|
|
7
|
+
*
|
|
8
|
+
* Usage:
|
|
9
|
+
* - `ct reset` — prompts for confirmation
|
|
10
|
+
* - `ct reset --yes` — skips confirmation
|
|
11
|
+
*
|
|
12
|
+
* This is a destructive operation. The catalog can be re-initialized
|
|
13
|
+
* with `ct init` after resetting.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import fs from "node:fs";
|
|
17
|
+
import type { CommandArgs, CommandCtx } from "./types.js";
|
|
18
|
+
import { isPiStefSource } from "../catalog/packages.js";
|
|
19
|
+
import { catalogDir, catalogFile, lockFile } from "../config/paths.js";
|
|
20
|
+
import { piUninstall } from "../util/exec.js";
|
|
21
|
+
|
|
22
|
+
// ---------------------------------------------------------------------------
|
|
23
|
+
// Types
|
|
24
|
+
// ---------------------------------------------------------------------------
|
|
25
|
+
|
|
26
|
+
/** Context for `resetCommand`, extending the base with `confirm`. */
|
|
27
|
+
export interface ResetCtx extends CommandCtx {
|
|
28
|
+
ui: CommandCtx["ui"] & {
|
|
29
|
+
confirm?: (message: string) => Promise<boolean>;
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// ---------------------------------------------------------------------------
|
|
34
|
+
// resetCommand
|
|
35
|
+
// ---------------------------------------------------------------------------
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Execute the `ct reset` subcommand.
|
|
39
|
+
*
|
|
40
|
+
* 1. Check cat.yaml exists
|
|
41
|
+
* 2. Confirm (skippable with --yes)
|
|
42
|
+
* 3. Find @pi-stef packages (isPiStefSource excludes catalog)
|
|
43
|
+
* 4. pi uninstall each
|
|
44
|
+
* 5. Delete config files with fs.rmSync({ recursive: true, force: true })
|
|
45
|
+
* 6. Remove empty catalog directory
|
|
46
|
+
*/
|
|
47
|
+
export async function resetCommand(
|
|
48
|
+
args: CommandArgs,
|
|
49
|
+
ctx: ResetCtx,
|
|
50
|
+
): Promise<void> {
|
|
51
|
+
const { flags } = args;
|
|
52
|
+
|
|
53
|
+
// --- Check cat.yaml exists ------------------------------------------------
|
|
54
|
+
const catPath = catalogFile(ctx.home);
|
|
55
|
+
if (!fs.existsSync(catPath)) {
|
|
56
|
+
ctx.ui.notify("No catalog found. Run `ct init` first.", "error");
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// --- Confirmation ----------------------------------------------------------
|
|
61
|
+
const skipConfirm = "yes" in flags || "y" in flags;
|
|
62
|
+
if (!skipConfirm && ctx.ui.confirm) {
|
|
63
|
+
const confirmed = await ctx.ui.confirm(
|
|
64
|
+
"This will uninstall all @pi-stef packages and delete all catalog config. Continue?",
|
|
65
|
+
);
|
|
66
|
+
if (!confirmed) {
|
|
67
|
+
ctx.ui.notify("Reset cancelled", "info");
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// --- Find @pi-stef packages -----------------------------------------------
|
|
73
|
+
// Read catalog directly (raw YAML) to avoid schema validation on corrupt files
|
|
74
|
+
let packages: Record<string, { source: string }> = {};
|
|
75
|
+
try {
|
|
76
|
+
const yaml = await import("js-yaml");
|
|
77
|
+
const content = fs.readFileSync(catPath, "utf8");
|
|
78
|
+
const parsed = yaml.load(content) as { packages?: Record<string, { source: string }> };
|
|
79
|
+
packages = parsed?.packages ?? {};
|
|
80
|
+
} catch {
|
|
81
|
+
// If YAML is corrupt, we still want to delete config files
|
|
82
|
+
ctx.ui.notify("Warning: could not parse cat.yaml — skipping uninstall step", "warning");
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const piStefNames = Object.keys(packages).filter(
|
|
86
|
+
(name) => isPiStefSource(packages[name].source),
|
|
87
|
+
);
|
|
88
|
+
|
|
89
|
+
// --- Uninstall @pi-stef packages ------------------------------------------
|
|
90
|
+
let uninstalled = 0;
|
|
91
|
+
let failed = 0;
|
|
92
|
+
|
|
93
|
+
for (const name of piStefNames) {
|
|
94
|
+
try {
|
|
95
|
+
await piUninstall(packages[name].source);
|
|
96
|
+
uninstalled++;
|
|
97
|
+
} catch {
|
|
98
|
+
ctx.ui.notify(`Warning: uninstall of "${name}" failed`, "warning");
|
|
99
|
+
failed++;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// --- Delete config files --------------------------------------------------
|
|
104
|
+
const dir = catalogDir(ctx.home);
|
|
105
|
+
const gistPath = `${dir}/.gist`;
|
|
106
|
+
|
|
107
|
+
// Delete individual files first, then the directory
|
|
108
|
+
for (const filePath of [catPath, lockFile(ctx.home), gistPath]) {
|
|
109
|
+
try {
|
|
110
|
+
fs.rmSync(filePath, { force: true });
|
|
111
|
+
} catch {
|
|
112
|
+
// Ignore errors — file may not exist
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// Remove the catalog directory itself
|
|
117
|
+
try {
|
|
118
|
+
fs.rmSync(dir, { recursive: true, force: true });
|
|
119
|
+
} catch {
|
|
120
|
+
// Ignore errors — directory may not exist or be non-empty
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// --- Report ----------------------------------------------------------------
|
|
124
|
+
const parts: string[] = [];
|
|
125
|
+
if (piStefNames.length > 0) {
|
|
126
|
+
parts.push(`uninstalled ${uninstalled}/${piStefNames.length} packages`);
|
|
127
|
+
}
|
|
128
|
+
parts.push("deleted config files");
|
|
129
|
+
|
|
130
|
+
ctx.ui.notify(
|
|
131
|
+
`Reset complete: ${parts.join(", ")}${failed > 0 ? ` (${failed} uninstall failed)` : ""}`,
|
|
132
|
+
failed > 0 ? "warning" : "info",
|
|
133
|
+
);
|
|
134
|
+
}
|
|
@@ -0,0 +1,85 @@
|
|
|
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
|
+
|
|
17
|
+
// ---------------------------------------------------------------------------
|
|
18
|
+
// updateCommand
|
|
19
|
+
// ---------------------------------------------------------------------------
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Execute the `ct update` subcommand.
|
|
23
|
+
*
|
|
24
|
+
* Reads the catalog, resolves the target package(s), and runs
|
|
25
|
+
* `pi update <source>` for each.
|
|
26
|
+
*/
|
|
27
|
+
export async function updateCommand(
|
|
28
|
+
args: CommandArgs,
|
|
29
|
+
ctx: CommandCtx,
|
|
30
|
+
): Promise<void> {
|
|
31
|
+
const { positional, flags } = args;
|
|
32
|
+
const updateAll = "all" in flags;
|
|
33
|
+
const name = positional[0];
|
|
34
|
+
|
|
35
|
+
if (!name && !updateAll) {
|
|
36
|
+
ctx.ui.notify("Usage: ct update <name> | ct update --all", "error");
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const catalog = readCatalog(ctx.home);
|
|
41
|
+
const packages = catalog.packages;
|
|
42
|
+
|
|
43
|
+
// --- Single package update ------------------------------------------------
|
|
44
|
+
if (name) {
|
|
45
|
+
const entry = packages[name];
|
|
46
|
+
if (!entry) {
|
|
47
|
+
ctx.ui.notify(`Package "${name}" not found in catalog`, "error");
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
try {
|
|
52
|
+
await piUpdate(entry.source);
|
|
53
|
+
ctx.ui.notify(`Updated "${name}"`, "info");
|
|
54
|
+
} catch {
|
|
55
|
+
ctx.ui.notify(`Warning: update of "${name}" failed`, "warning");
|
|
56
|
+
}
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// --- Update all -----------------------------------------------------------
|
|
61
|
+
const names = Object.keys(packages);
|
|
62
|
+
if (names.length === 0) {
|
|
63
|
+
ctx.ui.notify("Catalog is empty — nothing to update", "info");
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
let updated = 0;
|
|
68
|
+
let failed = 0;
|
|
69
|
+
|
|
70
|
+
for (const pkgName of names) {
|
|
71
|
+
const entry = packages[pkgName];
|
|
72
|
+
try {
|
|
73
|
+
await piUpdate(entry.source);
|
|
74
|
+
updated++;
|
|
75
|
+
} catch {
|
|
76
|
+
ctx.ui.notify(`Warning: update of "${pkgName}" failed`, "warning");
|
|
77
|
+
failed++;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
ctx.ui.notify(
|
|
82
|
+
`Updated ${updated}/${names.length} packages${failed > 0 ? ` (${failed} failed)` : ""}`,
|
|
83
|
+
failed > 0 ? "warning" : "info",
|
|
84
|
+
);
|
|
85
|
+
}
|
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,27 @@ 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
|
rating: Type.Optional(Type.String({ description: "Initial rating (core, useful, debatable)" })),
|
|
228
|
+
scope: Type.Optional(Type.String({ description: "Batch scope: '@pi-stef' to add all @pi-stef packages" })),
|
|
221
229
|
}),
|
|
222
230
|
async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
|
|
223
231
|
try {
|
|
232
|
+
const flags: Record<string, true | string> = {};
|
|
233
|
+
if (params.rating) flags.rating = params.rating;
|
|
234
|
+
if (params.scope) flags.scope = params.scope;
|
|
224
235
|
const args: CommandArgs = {
|
|
225
|
-
positional:
|
|
226
|
-
flags
|
|
236
|
+
positional: params.scope ? [] : [params.source],
|
|
237
|
+
flags,
|
|
227
238
|
};
|
|
228
239
|
await addCommand(args, ctx as unknown as AddCtx);
|
|
229
|
-
return { content: [{ type: "text" as const, text: `Added ${params.
|
|
240
|
+
return { content: [{ type: "text" as const, text: `Added ${params.source}.` }], details: undefined as unknown };
|
|
230
241
|
} catch (err) {
|
|
231
242
|
return { content: [{ type: "text" as const, text: `Add failed: ${err instanceof Error ? err.message : String(err)}` }], details: undefined as unknown };
|
|
232
243
|
}
|
|
@@ -242,13 +253,20 @@ export function registerCatalog(pi: ExtensionAPI): void {
|
|
|
242
253
|
"Use ct_remove when the user asks to remove or uninstall a package from their catalog.",
|
|
243
254
|
],
|
|
244
255
|
parameters: Type.Object({
|
|
245
|
-
name: Type.String({ description: "Package name to remove" }),
|
|
256
|
+
name: Type.Optional(Type.String({ description: "Package name to remove" })),
|
|
257
|
+
scope: Type.Optional(Type.String({ description: "Batch scope: '@pi-stef' to remove all @pi-stef packages" })),
|
|
246
258
|
}),
|
|
247
259
|
async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
|
|
248
260
|
try {
|
|
249
|
-
const
|
|
261
|
+
const flags: Record<string, true | string> = {};
|
|
262
|
+
if (params.scope) flags.scope = params.scope;
|
|
263
|
+
const args: CommandArgs = {
|
|
264
|
+
positional: params.name ? [params.name] : [],
|
|
265
|
+
flags,
|
|
266
|
+
};
|
|
250
267
|
await removeCommand(args, ctx as unknown as RemoveCtx);
|
|
251
|
-
|
|
268
|
+
const label = params.scope ? `Scope ${params.scope}` : `${params.name}`;
|
|
269
|
+
return { content: [{ type: "text" as const, text: `Removed ${label}.` }], details: undefined as unknown };
|
|
252
270
|
} catch (err) {
|
|
253
271
|
return { content: [{ type: "text" as const, text: `Remove failed: ${err instanceof Error ? err.message : String(err)}` }], details: undefined as unknown };
|
|
254
272
|
}
|
|
@@ -278,6 +296,58 @@ export function registerCatalog(pi: ExtensionAPI): void {
|
|
|
278
296
|
},
|
|
279
297
|
});
|
|
280
298
|
|
|
299
|
+
pi.registerTool({
|
|
300
|
+
name: "ct_update",
|
|
301
|
+
label: "Catalog Update",
|
|
302
|
+
description:
|
|
303
|
+
"Update packages to their latest versions. Run `pi update` behind the scenes.",
|
|
304
|
+
promptSnippet: "Update catalog packages",
|
|
305
|
+
promptGuidelines: [
|
|
306
|
+
"Use ct_update when the user asks to update one or more packages in their catalog.",
|
|
307
|
+
],
|
|
308
|
+
parameters: Type.Object({
|
|
309
|
+
name: Type.Optional(Type.String({ description: "Package name to update (omit for --all)" })),
|
|
310
|
+
all: Type.Optional(Type.Boolean({ description: "Update all packages" })),
|
|
311
|
+
}),
|
|
312
|
+
async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
|
|
313
|
+
try {
|
|
314
|
+
const positional = params.name ? [params.name] : [];
|
|
315
|
+
const flags: Record<string, true | string> = {};
|
|
316
|
+
if (params.all) flags.all = true;
|
|
317
|
+
const args: CommandArgs = { positional, flags };
|
|
318
|
+
await updateCommand(args, ctx);
|
|
319
|
+
return { content: [{ type: "text" as const, text: "Update completed." }], details: undefined as unknown };
|
|
320
|
+
} catch (err) {
|
|
321
|
+
return { content: [{ type: "text" as const, text: `Update failed: ${err instanceof Error ? err.message : String(err)}` }], details: undefined as unknown };
|
|
322
|
+
}
|
|
323
|
+
},
|
|
324
|
+
});
|
|
325
|
+
|
|
326
|
+
pi.registerTool({
|
|
327
|
+
name: "ct_reset",
|
|
328
|
+
label: "Catalog Reset",
|
|
329
|
+
description:
|
|
330
|
+
"Full nuke: uninstall all @pi-stef packages, delete gist refs, delete config files.",
|
|
331
|
+
promptSnippet: "Reset catalog completely",
|
|
332
|
+
promptGuidelines: [
|
|
333
|
+
"Use ct_reset when the user wants to completely remove all @pi-stef packages and catalog config.",
|
|
334
|
+
],
|
|
335
|
+
parameters: Type.Object({
|
|
336
|
+
yes: Type.Optional(Type.Boolean({ description: "Skip confirmation prompt" })),
|
|
337
|
+
}),
|
|
338
|
+
async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
|
|
339
|
+
try {
|
|
340
|
+
const flags: Record<string, true | string> = {};
|
|
341
|
+
if (params.yes) flags.yes = true;
|
|
342
|
+
const args: CommandArgs = { positional: [], flags };
|
|
343
|
+
await resetCommand(args, ctx as unknown as ResetCtx);
|
|
344
|
+
return { content: [{ type: "text" as const, text: "Reset completed." }], details: undefined as unknown };
|
|
345
|
+
} catch (err) {
|
|
346
|
+
return { content: [{ type: "text" as const, text: `Reset failed: ${err instanceof Error ? err.message : String(err)}` }], details: undefined as unknown };
|
|
347
|
+
}
|
|
348
|
+
},
|
|
349
|
+
});
|
|
350
|
+
|
|
281
351
|
pi.registerTool({
|
|
282
352
|
name: "ct_status",
|
|
283
353
|
label: "Catalog Status",
|
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
|
+
}
|