@unbrained/pm-cli 2026.5.11 → 2026.5.14
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/AGENTS.md +3 -116
- package/CHANGELOG.md +18 -0
- package/PRD.md +18 -39
- package/README.md +8 -5
- package/dist/cli/commander-usage.js +27 -0
- package/dist/cli/commander-usage.js.map +1 -1
- package/dist/cli/commands/activity.js +19 -4
- package/dist/cli/commands/activity.js.map +1 -1
- package/dist/cli/commands/calendar.js +5 -2
- package/dist/cli/commands/calendar.js.map +1 -1
- package/dist/cli/commands/contracts.js +63 -19
- package/dist/cli/commands/contracts.js.map +1 -1
- package/dist/cli/commands/create.js +58 -3
- package/dist/cli/commands/create.js.map +1 -1
- package/dist/cli/commands/extension.d.ts +14 -3
- package/dist/cli/commands/extension.js +481 -95
- package/dist/cli/commands/extension.js.map +1 -1
- package/dist/cli/commands/index.d.ts +1 -8
- package/dist/cli/commands/index.js +1 -8
- package/dist/cli/commands/index.js.map +1 -1
- package/dist/cli/commands/reindex.d.ts +8 -0
- package/dist/cli/commands/reindex.js +96 -23
- package/dist/cli/commands/reindex.js.map +1 -1
- package/dist/cli/commands/search.js +51 -25
- package/dist/cli/commands/search.js.map +1 -1
- package/dist/cli/commands/test.js +14 -6
- package/dist/cli/commands/test.js.map +1 -1
- package/dist/cli/commands/upgrade.d.ts +63 -0
- package/dist/cli/commands/upgrade.js +260 -0
- package/dist/cli/commands/upgrade.js.map +1 -0
- package/dist/cli/guide-topics.js +18 -16
- package/dist/cli/guide-topics.js.map +1 -1
- package/dist/cli/help-content.js +57 -18
- package/dist/cli/help-content.js.map +1 -1
- package/dist/cli/main.js +73 -7
- package/dist/cli/main.js.map +1 -1
- package/dist/cli/register-list-query.js +24 -142
- package/dist/cli/register-list-query.js.map +1 -1
- package/dist/cli/register-mutation.js +49 -257
- package/dist/cli/register-mutation.js.map +1 -1
- package/dist/cli/register-operations.js +29 -198
- package/dist/cli/register-operations.js.map +1 -1
- package/dist/cli/register-setup.js +181 -204
- package/dist/cli/register-setup.js.map +1 -1
- package/dist/cli/registration-helpers.d.ts +2 -2
- package/dist/cli/registration-helpers.js +1 -19
- package/dist/cli/registration-helpers.js.map +1 -1
- package/dist/core/extensions/loader.js +7 -1
- package/dist/core/extensions/loader.js.map +1 -1
- package/dist/core/packages/manifest.d.ts +38 -0
- package/dist/core/packages/manifest.js +221 -0
- package/dist/core/packages/manifest.js.map +1 -0
- package/dist/core/search/embedding-batches.d.ts +13 -1
- package/dist/core/search/embedding-batches.js +19 -1
- package/dist/core/search/embedding-batches.js.map +1 -1
- package/dist/core/store/front-matter-cache.d.ts +8 -1
- package/dist/core/store/front-matter-cache.js +20 -11
- package/dist/core/store/front-matter-cache.js.map +1 -1
- package/dist/mcp/server.d.ts +8 -0
- package/dist/mcp/server.js +100 -43
- package/dist/mcp/server.js.map +1 -1
- package/dist/sdk/cli-contracts/commander-mutation-options.d.ts +7 -0
- package/dist/sdk/cli-contracts/commander-mutation-options.js +477 -0
- package/dist/sdk/cli-contracts/commander-mutation-options.js.map +1 -0
- package/dist/sdk/cli-contracts/commander-types.d.ts +21 -0
- package/dist/sdk/cli-contracts/commander-types.js +92 -0
- package/dist/sdk/cli-contracts/commander-types.js.map +1 -0
- package/dist/sdk/cli-contracts.d.ts +22 -32
- package/dist/sdk/cli-contracts.js +155 -296
- package/dist/sdk/cli-contracts.js.map +1 -1
- package/dist/sdk/index.d.ts +2 -0
- package/dist/sdk/index.js +2 -0
- package/dist/sdk/index.js.map +1 -1
- package/dist/sdk/runtime.d.ts +29 -0
- package/dist/sdk/runtime.js +28 -0
- package/dist/sdk/runtime.js.map +1 -0
- package/docs/ARCHITECTURE.md +1 -1
- package/docs/COMMANDS.md +17 -1
- package/docs/EXTENSIONS.md +169 -61
- package/docs/QUICKSTART.md +11 -2
- package/docs/README.md +4 -6
- package/docs/RELEASING.md +4 -2
- package/docs/SDK.md +79 -438
- package/package.json +6 -23
- package/packages/pm-beads/README.md +10 -0
- package/packages/pm-beads/extensions/beads/index.js +113 -0
- package/{.agents/pm/extensions/beads/index.js → packages/pm-beads/extensions/beads/index.ts} +42 -20
- package/{.agents/pm → packages/pm-beads}/extensions/beads/runtime.js +2 -17
- package/{.agents/pm → packages/pm-beads}/extensions/beads/runtime.ts +41 -18
- package/packages/pm-beads/package.json +50 -0
- package/packages/pm-calendar/README.md +13 -0
- package/packages/pm-calendar/extensions/calendar/index.js +56 -0
- package/packages/pm-calendar/extensions/calendar/index.ts +62 -0
- package/packages/pm-calendar/extensions/calendar/manifest.json +7 -0
- package/packages/pm-calendar/extensions/calendar/runtime.js +95 -0
- package/packages/pm-calendar/extensions/calendar/runtime.ts +104 -0
- package/packages/pm-calendar/package.json +51 -0
- package/packages/pm-governance-audit/README.md +23 -0
- package/packages/pm-governance-audit/extensions/governance-audit/index.js +117 -0
- package/packages/pm-governance-audit/extensions/governance-audit/index.ts +118 -0
- package/packages/pm-governance-audit/extensions/governance-audit/manifest.json +7 -0
- package/packages/pm-governance-audit/extensions/governance-audit/runtime.js +159 -0
- package/packages/pm-governance-audit/extensions/governance-audit/runtime.ts +176 -0
- package/packages/pm-governance-audit/package.json +52 -0
- package/packages/pm-guide-shell/README.md +23 -0
- package/packages/pm-guide-shell/extensions/guide-shell/index.js +76 -0
- package/packages/pm-guide-shell/extensions/guide-shell/index.ts +81 -0
- package/packages/pm-guide-shell/extensions/guide-shell/manifest.json +7 -0
- package/packages/pm-guide-shell/extensions/guide-shell/runtime.js +263 -0
- package/packages/pm-guide-shell/extensions/guide-shell/runtime.ts +327 -0
- package/packages/pm-guide-shell/package.json +52 -0
- package/packages/pm-linked-test-adapters/README.md +24 -0
- package/packages/pm-linked-test-adapters/extensions/linked-test-adapters/index.js +101 -0
- package/packages/pm-linked-test-adapters/extensions/linked-test-adapters/index.ts +102 -0
- package/packages/pm-linked-test-adapters/extensions/linked-test-adapters/manifest.json +7 -0
- package/packages/pm-linked-test-adapters/extensions/linked-test-adapters/runtime.js +142 -0
- package/packages/pm-linked-test-adapters/extensions/linked-test-adapters/runtime.ts +173 -0
- package/packages/pm-linked-test-adapters/package.json +53 -0
- package/packages/pm-search-advanced/README.md +27 -0
- package/packages/pm-search-advanced/extensions/search-advanced/index.js +93 -0
- package/packages/pm-search-advanced/extensions/search-advanced/index.ts +94 -0
- package/packages/pm-search-advanced/extensions/search-advanced/manifest.json +7 -0
- package/packages/pm-search-advanced/extensions/search-advanced/runtime.js +120 -0
- package/packages/pm-search-advanced/extensions/search-advanced/runtime.ts +144 -0
- package/packages/pm-search-advanced/package.json +54 -0
- package/packages/pm-templates/README.md +20 -0
- package/packages/pm-templates/extensions/templates/index.js +101 -0
- package/packages/pm-templates/extensions/templates/index.ts +109 -0
- package/packages/pm-templates/extensions/templates/manifest.json +7 -0
- package/packages/pm-templates/extensions/templates/runtime.js +226 -0
- package/packages/pm-templates/extensions/templates/runtime.ts +283 -0
- package/packages/pm-templates/package.json +50 -0
- package/packages/pm-todos/README.md +11 -0
- package/packages/pm-todos/extensions/todos/index.js +130 -0
- package/{.agents/pm/extensions/todos/index.js → packages/pm-todos/extensions/todos/index.ts} +47 -23
- package/{.agents/pm → packages/pm-todos}/extensions/todos/runtime.js +3 -18
- package/{.agents/pm → packages/pm-todos}/extensions/todos/runtime.ts +42 -20
- package/packages/pm-todos/package.json +51 -0
- package/plugins/pm-cli-claude/README.md +1 -2
- package/plugins/pm-cli-claude/hooks/session-start.mjs +4 -55
- package/plugins/pm-cli-claude/scripts/pm-mcp-server.mjs +4 -2
- package/plugins/pm-cli-codex/scripts/pm-mcp-server.mjs +4 -2
- package/.agents/pm/extensions/.managed-extensions.json +0 -42
- package/.agents/skills/HARNESS_COMPATIBILITY.md +0 -45
- package/.agents/skills/README.md +0 -21
- package/.agents/skills/pm-developer/SKILL.md +0 -73
- package/.agents/skills/pm-developer/references/COMMAND_PLAYBOOK.md +0 -48
- package/.agents/skills/pm-developer/references/PROMPTS.md +0 -17
- package/.agents/skills/pm-extensions/SKILL.md +0 -57
- package/.agents/skills/pm-extensions/references/LIFECYCLE.md +0 -40
- package/.agents/skills/pm-extensions/references/TROUBLESHOOTING.md +0 -25
- package/.agents/skills/pm-sdk/SKILL.md +0 -50
- package/.agents/skills/pm-sdk/references/INTEGRATION_CHECKLIST.md +0 -31
- package/.agents/skills/pm-sdk/references/PROMPTS.md +0 -13
- package/.agents/skills/pm-user/SKILL.md +0 -59
- package/.agents/skills/pm-user/references/PROMPTS.md +0 -17
- package/.agents/skills/pm-user/references/WORKFLOWS.md +0 -35
- package/.pi/README.md +0 -35
- package/.pi/agents/pm-triage-agent.md +0 -19
- package/.pi/agents/pm-verification-agent.md +0 -21
- package/.pi/chains/pm-native-delivery.chain.md +0 -11
- package/.pi/extensions/pm-cli/index.js +0 -387
- package/.pi/prompts/pm-workflow.md +0 -5
- package/.pi/skills/pm-native/SKILL.md +0 -44
- package/.pi/skills/pm-release/SKILL.md +0 -35
- package/dist/pi/native.d.ts +0 -5
- package/dist/pi/native.js +0 -236
- package/dist/pi/native.js.map +0 -1
- package/docs/PI_PACKAGE.md +0 -141
- /package/{.agents/pm → packages/pm-beads}/extensions/beads/manifest.json +0 -0
- /package/{.agents/pm → packages/pm-todos}/extensions/todos/manifest.json +0 -0
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import type { CommandDefinition, ExtensionApi, GlobalOptions } from "../../../../src/sdk/index.js";
|
|
2
|
+
import {
|
|
3
|
+
runTemplatesList as runTemplatesListPackage,
|
|
4
|
+
runTemplatesSave as runTemplatesSavePackage,
|
|
5
|
+
runTemplatesShow as runTemplatesShowPackage,
|
|
6
|
+
type TemplatesListResult,
|
|
7
|
+
type TemplatesSaveResult,
|
|
8
|
+
type TemplatesShowResult,
|
|
9
|
+
} from "./runtime.js";
|
|
10
|
+
|
|
11
|
+
export const manifest = {
|
|
12
|
+
name: "builtin-templates",
|
|
13
|
+
version: "0.1.0",
|
|
14
|
+
entry: "./index.js",
|
|
15
|
+
priority: 0,
|
|
16
|
+
capabilities: ["commands", "schema"],
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
function firstArg(args: string[], commandName: string): string {
|
|
20
|
+
const value = args[0];
|
|
21
|
+
if (typeof value === "string" && value.trim().length > 0) {
|
|
22
|
+
return value;
|
|
23
|
+
}
|
|
24
|
+
throw new Error(`${commandName} requires a template name argument.`);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
async function runTemplatesListFromRuntime(global: GlobalOptions): Promise<TemplatesListResult> {
|
|
28
|
+
return runTemplatesListPackage(global);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
async function runTemplatesSaveFromRuntime(
|
|
32
|
+
args: string[],
|
|
33
|
+
options: Record<string, unknown>,
|
|
34
|
+
global: GlobalOptions,
|
|
35
|
+
): Promise<TemplatesSaveResult> {
|
|
36
|
+
return runTemplatesSavePackage(firstArg(args, "templates save"), options, global);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
async function runTemplatesShowFromRuntime(args: string[], global: GlobalOptions): Promise<TemplatesShowResult> {
|
|
40
|
+
return runTemplatesShowPackage(firstArg(args, "templates show"), global);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const createOptionFlags = [
|
|
44
|
+
{ long: "--title", short: "-t", value_name: "value", value_type: "string", description: "Template default item title." },
|
|
45
|
+
{ long: "--description", short: "-d", value_name: "value", value_type: "string", description: "Template default item description." },
|
|
46
|
+
{ long: "--type", value_name: "value", value_type: "string", description: "Template default item type." },
|
|
47
|
+
{ long: "--status", short: "-s", value_name: "value", value_type: "string", description: "Template default item status." },
|
|
48
|
+
{ long: "--priority", short: "-p", value_name: "value", value_type: "string", description: "Template default priority 0..4." },
|
|
49
|
+
{ long: "--tags", value_name: "value", value_type: "string", description: "Template default comma-separated tags." },
|
|
50
|
+
{ long: "--body", short: "-b", value_name: "value", value_type: "string", description: "Template default item markdown body." },
|
|
51
|
+
{ long: "--deadline", value_name: "value", value_type: "string", description: "Template default deadline." },
|
|
52
|
+
{ long: "--estimate", value_name: "value", value_type: "string", description: "Template default estimated minutes." },
|
|
53
|
+
{ long: "--estimated-minutes", value_name: "value", value_type: "string", description: "Template default estimated minutes." },
|
|
54
|
+
{ long: "--acceptance-criteria", value_name: "value", value_type: "string", description: "Template default acceptance criteria." },
|
|
55
|
+
{ long: "--ac", value_name: "value", value_type: "string", description: "Alias for --acceptance-criteria." },
|
|
56
|
+
{ long: "--author", value_name: "value", value_type: "string", description: "Template default mutation author." },
|
|
57
|
+
{ long: "--message", value_name: "value", value_type: "string", description: "Template default history message." },
|
|
58
|
+
{ long: "--assignee", value_name: "value", value_type: "string", description: "Template default assignee." },
|
|
59
|
+
{ long: "--parent", value_name: "value", value_type: "string", description: "Template default parent item ID." },
|
|
60
|
+
{ long: "--reviewer", value_name: "value", value_type: "string", description: "Template default reviewer." },
|
|
61
|
+
{ long: "--risk", value_name: "value", value_type: "string", description: "Template default risk level." },
|
|
62
|
+
{ long: "--confidence", value_name: "value", value_type: "string", description: "Template default confidence." },
|
|
63
|
+
{ long: "--sprint", value_name: "value", value_type: "string", description: "Template default sprint identifier." },
|
|
64
|
+
{ long: "--release", value_name: "value", value_type: "string", description: "Template default release identifier." },
|
|
65
|
+
{ long: "--dep", value_name: "value", value_type: "string", description: "Template default dependency seed.", repeatable: true },
|
|
66
|
+
{ long: "--comment", value_name: "value", value_type: "string", description: "Template default comment seed.", repeatable: true },
|
|
67
|
+
{ long: "--note", value_name: "value", value_type: "string", description: "Template default note seed.", repeatable: true },
|
|
68
|
+
{ long: "--learning", value_name: "value", value_type: "string", description: "Template default learning seed.", repeatable: true },
|
|
69
|
+
{ long: "--file", value_name: "value", value_type: "string", description: "Template default linked file seed.", repeatable: true },
|
|
70
|
+
{ long: "--test", value_name: "value", value_type: "string", description: "Template default linked test seed.", repeatable: true },
|
|
71
|
+
{ long: "--doc", value_name: "value", value_type: "string", description: "Template default linked doc seed.", repeatable: true },
|
|
72
|
+
{ long: "--reminder", value_name: "value", value_type: "string", description: "Template default reminder seed.", repeatable: true },
|
|
73
|
+
{ long: "--event", value_name: "value", value_type: "string", description: "Template default event seed.", repeatable: true },
|
|
74
|
+
] as const;
|
|
75
|
+
|
|
76
|
+
export function activate(api: ExtensionApi): void {
|
|
77
|
+
api.registerCommand({
|
|
78
|
+
name: "templates",
|
|
79
|
+
action: "templates-list",
|
|
80
|
+
description: "List saved create templates.",
|
|
81
|
+
run: async (context) => runTemplatesListFromRuntime(context.global),
|
|
82
|
+
} satisfies CommandDefinition);
|
|
83
|
+
api.registerCommand({
|
|
84
|
+
name: "templates list",
|
|
85
|
+
action: "templates-list",
|
|
86
|
+
description: "List saved create templates.",
|
|
87
|
+
run: async (context) => runTemplatesListFromRuntime(context.global),
|
|
88
|
+
} satisfies CommandDefinition);
|
|
89
|
+
api.registerCommand({
|
|
90
|
+
name: "templates save",
|
|
91
|
+
action: "templates-save",
|
|
92
|
+
description: "Save reusable create template defaults.",
|
|
93
|
+
arguments: [{ name: "name", required: true, description: "Template name." }],
|
|
94
|
+
flags: [...createOptionFlags],
|
|
95
|
+
run: async (context) => runTemplatesSaveFromRuntime(context.args, context.options, context.global),
|
|
96
|
+
} satisfies CommandDefinition);
|
|
97
|
+
api.registerCommand({
|
|
98
|
+
name: "templates show",
|
|
99
|
+
action: "templates-show",
|
|
100
|
+
description: "Show a saved create template.",
|
|
101
|
+
arguments: [{ name: "name", required: true, description: "Template name." }],
|
|
102
|
+
run: async (context) => runTemplatesShowFromRuntime(context.args, context.global),
|
|
103
|
+
} satisfies CommandDefinition);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export default {
|
|
107
|
+
manifest,
|
|
108
|
+
activate,
|
|
109
|
+
};
|
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
import fs from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { pathToFileURL } from "node:url";
|
|
4
|
+
|
|
5
|
+
const PM_PACKAGE_ROOT_ENV = "PM_CLI_PACKAGE_ROOT";
|
|
6
|
+
const sdk = await loadTemplatesSdkModule();
|
|
7
|
+
const {
|
|
8
|
+
CREATE_COMMANDER_REPEATABLE_OPTION_CONTRACTS,
|
|
9
|
+
EXIT_CODE,
|
|
10
|
+
PmCliError,
|
|
11
|
+
getSettingsPath,
|
|
12
|
+
nowIso,
|
|
13
|
+
pathExists,
|
|
14
|
+
readFileIfExists,
|
|
15
|
+
resolvePmRoot,
|
|
16
|
+
writeFileAtomic,
|
|
17
|
+
} = sdk;
|
|
18
|
+
|
|
19
|
+
async function loadTemplatesSdkModule() {
|
|
20
|
+
const envRoot = process.env[PM_PACKAGE_ROOT_ENV];
|
|
21
|
+
if (typeof envRoot !== "string" || envRoot.trim().length === 0) {
|
|
22
|
+
throw new Error(
|
|
23
|
+
`builtin-templates requires ${PM_PACKAGE_ROOT_ENV} to locate core SDK runtime exports.`,
|
|
24
|
+
);
|
|
25
|
+
}
|
|
26
|
+
const modulePath = path.join(path.resolve(envRoot.trim()), "dist", "sdk", "index.js");
|
|
27
|
+
try {
|
|
28
|
+
const loaded = await import(pathToFileURL(modulePath).href);
|
|
29
|
+
if (
|
|
30
|
+
typeof loaded.resolvePmRoot === "function" &&
|
|
31
|
+
typeof loaded.pathExists === "function" &&
|
|
32
|
+
typeof loaded.PmCliError === "function" &&
|
|
33
|
+
Array.isArray(loaded.CREATE_COMMANDER_REPEATABLE_OPTION_CONTRACTS)
|
|
34
|
+
) {
|
|
35
|
+
return loaded;
|
|
36
|
+
}
|
|
37
|
+
} catch {
|
|
38
|
+
// Fall through to deterministic failure message below.
|
|
39
|
+
}
|
|
40
|
+
throw new Error(
|
|
41
|
+
`builtin-templates failed to load SDK runtime exports from ${modulePath}.`,
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const TEMPLATE_DIRECTORY_NAME = "templates";
|
|
46
|
+
const TEMPLATE_FILE_EXTENSION = ".json";
|
|
47
|
+
const TEMPLATE_NAME_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$/;
|
|
48
|
+
const TEMPLATE_OPTION_REPEATABLE_KEY_SET = new Set(CREATE_COMMANDER_REPEATABLE_OPTION_CONTRACTS.map((entry) => entry.target));
|
|
49
|
+
|
|
50
|
+
function normalizeTemplateName(rawName) {
|
|
51
|
+
const name = rawName.trim();
|
|
52
|
+
if (!TEMPLATE_NAME_PATTERN.test(name)) {
|
|
53
|
+
throw new PmCliError(
|
|
54
|
+
`Invalid template name "${rawName}". Expected 1-64 characters matching [A-Za-z0-9][A-Za-z0-9._-]*.`,
|
|
55
|
+
EXIT_CODE.USAGE,
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
return name;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function templatesDirectory(pmRoot) {
|
|
62
|
+
return path.join(pmRoot, TEMPLATE_DIRECTORY_NAME);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function templatePath(pmRoot, normalizedName) {
|
|
66
|
+
return path.join(templatesDirectory(pmRoot), `${normalizedName}${TEMPLATE_FILE_EXTENSION}`);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
async function ensureTrackerInitialized(pmRoot) {
|
|
70
|
+
if (!(await pathExists(getSettingsPath(pmRoot)))) {
|
|
71
|
+
throw new PmCliError(`Tracker is not initialized at ${pmRoot}. Run pm init first.`, EXIT_CODE.NOT_FOUND);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function sortTemplateOptions(options) {
|
|
76
|
+
return Object.fromEntries(Object.entries(options).sort(([left], [right]) => left.localeCompare(right)));
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function extractTemplateOptions(rawOptions) {
|
|
80
|
+
const next = {};
|
|
81
|
+
for (const [key, value] of Object.entries(rawOptions)) {
|
|
82
|
+
if (value === undefined) {
|
|
83
|
+
continue;
|
|
84
|
+
}
|
|
85
|
+
if (TEMPLATE_OPTION_REPEATABLE_KEY_SET.has(key)) {
|
|
86
|
+
if (typeof value === "string") {
|
|
87
|
+
next[key] = [value];
|
|
88
|
+
continue;
|
|
89
|
+
}
|
|
90
|
+
if (!Array.isArray(value)) {
|
|
91
|
+
continue;
|
|
92
|
+
}
|
|
93
|
+
const values = value.filter((entry) => typeof entry === "string");
|
|
94
|
+
if (values.length > 0) {
|
|
95
|
+
next[key] = values;
|
|
96
|
+
}
|
|
97
|
+
continue;
|
|
98
|
+
}
|
|
99
|
+
if (typeof value === "string") {
|
|
100
|
+
next[key] = value;
|
|
101
|
+
continue;
|
|
102
|
+
}
|
|
103
|
+
if (Array.isArray(value) && value.every((entry) => typeof entry === "string")) {
|
|
104
|
+
next[key] = [...value];
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
return sortTemplateOptions(next);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function parseStoredTemplateOptions(rawOptions, templateName) {
|
|
111
|
+
if (typeof rawOptions !== "object" || rawOptions === null || Array.isArray(rawOptions)) {
|
|
112
|
+
throw new PmCliError(`Template "${templateName}" has invalid options payload.`, EXIT_CODE.GENERIC_FAILURE);
|
|
113
|
+
}
|
|
114
|
+
const normalized = {};
|
|
115
|
+
for (const [key, value] of Object.entries(rawOptions)) {
|
|
116
|
+
const normalizedKey = key.trim();
|
|
117
|
+
if (normalizedKey.length === 0) {
|
|
118
|
+
throw new PmCliError(`Template "${templateName}" contains an empty option key.`, EXIT_CODE.GENERIC_FAILURE);
|
|
119
|
+
}
|
|
120
|
+
if (typeof value === "string") {
|
|
121
|
+
normalized[normalizedKey] = value;
|
|
122
|
+
continue;
|
|
123
|
+
}
|
|
124
|
+
if (Array.isArray(value) && value.every((entry) => typeof entry === "string")) {
|
|
125
|
+
normalized[normalizedKey] = [...value];
|
|
126
|
+
continue;
|
|
127
|
+
}
|
|
128
|
+
throw new PmCliError(`Template "${templateName}" contains invalid value for option "${normalizedKey}".`, EXIT_CODE.GENERIC_FAILURE);
|
|
129
|
+
}
|
|
130
|
+
return sortTemplateOptions(normalized);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function parseStoredTemplateDocument(raw, normalizedName) {
|
|
134
|
+
let parsed;
|
|
135
|
+
try {
|
|
136
|
+
parsed = JSON.parse(raw);
|
|
137
|
+
} catch {
|
|
138
|
+
throw new PmCliError(`Template "${normalizedName}" contains invalid JSON.`, EXIT_CODE.GENERIC_FAILURE);
|
|
139
|
+
}
|
|
140
|
+
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
|
141
|
+
throw new PmCliError(`Template "${normalizedName}" has invalid document shape.`, EXIT_CODE.GENERIC_FAILURE);
|
|
142
|
+
}
|
|
143
|
+
const options = parseStoredTemplateOptions(parsed.options, normalizedName);
|
|
144
|
+
const now = nowIso();
|
|
145
|
+
return {
|
|
146
|
+
name: typeof parsed.name === "string" && parsed.name.trim().length > 0 ? parsed.name.trim() : normalizedName,
|
|
147
|
+
created_at: typeof parsed.created_at === "string" ? parsed.created_at : now,
|
|
148
|
+
updated_at: typeof parsed.updated_at === "string" ? parsed.updated_at : now,
|
|
149
|
+
options,
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
async function readStoredTemplateDocument(pmRoot, normalizedName) {
|
|
154
|
+
const raw = await readFileIfExists(templatePath(pmRoot, normalizedName));
|
|
155
|
+
if (raw === null) {
|
|
156
|
+
throw new PmCliError(`Template "${normalizedName}" not found`, EXIT_CODE.NOT_FOUND);
|
|
157
|
+
}
|
|
158
|
+
return parseStoredTemplateDocument(raw, normalizedName);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
export async function runTemplatesSave(rawTemplateName, options, global) {
|
|
162
|
+
const pmRoot = resolvePmRoot(process.cwd(), global.path);
|
|
163
|
+
await ensureTrackerInitialized(pmRoot);
|
|
164
|
+
const normalizedName = normalizeTemplateName(rawTemplateName);
|
|
165
|
+
const nextOptions = extractTemplateOptions(options);
|
|
166
|
+
if (Object.keys(nextOptions).length === 0) {
|
|
167
|
+
throw new PmCliError("templates save requires at least one create option flag", EXIT_CODE.USAGE);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
const now = nowIso();
|
|
171
|
+
const storedPath = templatePath(pmRoot, normalizedName);
|
|
172
|
+
let createdAt = now;
|
|
173
|
+
if (await pathExists(storedPath)) {
|
|
174
|
+
const existing = await readStoredTemplateDocument(pmRoot, normalizedName);
|
|
175
|
+
createdAt = existing.created_at;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
const document = {
|
|
179
|
+
name: normalizedName,
|
|
180
|
+
created_at: createdAt,
|
|
181
|
+
updated_at: now,
|
|
182
|
+
options: nextOptions,
|
|
183
|
+
};
|
|
184
|
+
await fs.mkdir(templatesDirectory(pmRoot), { recursive: true });
|
|
185
|
+
await writeFileAtomic(storedPath, `${JSON.stringify(document, null, 2)}\n`);
|
|
186
|
+
return {
|
|
187
|
+
name: document.name,
|
|
188
|
+
created_at: document.created_at,
|
|
189
|
+
updated_at: document.updated_at,
|
|
190
|
+
path: storedPath,
|
|
191
|
+
options: document.options,
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
export async function runTemplatesList(global) {
|
|
196
|
+
const pmRoot = resolvePmRoot(process.cwd(), global.path);
|
|
197
|
+
await ensureTrackerInitialized(pmRoot);
|
|
198
|
+
const dirPath = templatesDirectory(pmRoot);
|
|
199
|
+
if (!(await pathExists(dirPath))) {
|
|
200
|
+
return { templates: [], count: 0 };
|
|
201
|
+
}
|
|
202
|
+
const entries = await fs.readdir(dirPath);
|
|
203
|
+
const templates = entries
|
|
204
|
+
.filter((entry) => entry.toLowerCase().endsWith(TEMPLATE_FILE_EXTENSION))
|
|
205
|
+
.map((entry) => entry.slice(0, -TEMPLATE_FILE_EXTENSION.length))
|
|
206
|
+
.filter((entry) => TEMPLATE_NAME_PATTERN.test(entry))
|
|
207
|
+
.sort((left, right) => left.localeCompare(right));
|
|
208
|
+
return {
|
|
209
|
+
templates,
|
|
210
|
+
count: templates.length,
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
export async function runTemplatesShow(rawTemplateName, global) {
|
|
215
|
+
const pmRoot = resolvePmRoot(process.cwd(), global.path);
|
|
216
|
+
await ensureTrackerInitialized(pmRoot);
|
|
217
|
+
const normalizedName = normalizeTemplateName(rawTemplateName);
|
|
218
|
+
const stored = await readStoredTemplateDocument(pmRoot, normalizedName);
|
|
219
|
+
return {
|
|
220
|
+
name: stored.name,
|
|
221
|
+
created_at: stored.created_at,
|
|
222
|
+
updated_at: stored.updated_at,
|
|
223
|
+
path: templatePath(pmRoot, normalizedName),
|
|
224
|
+
options: stored.options,
|
|
225
|
+
};
|
|
226
|
+
}
|
|
@@ -0,0 +1,283 @@
|
|
|
1
|
+
import fs from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { pathToFileURL } from "node:url";
|
|
4
|
+
import type { GlobalOptions } from "../../../../src/sdk/index.js";
|
|
5
|
+
|
|
6
|
+
const PM_PACKAGE_ROOT_ENV = "PM_CLI_PACKAGE_ROOT";
|
|
7
|
+
|
|
8
|
+
interface TemplatesSdkModule {
|
|
9
|
+
CREATE_COMMANDER_REPEATABLE_OPTION_CONTRACTS: Array<{ target: string }>;
|
|
10
|
+
EXIT_CODE: {
|
|
11
|
+
USAGE: number;
|
|
12
|
+
NOT_FOUND: number;
|
|
13
|
+
GENERIC_FAILURE: number;
|
|
14
|
+
};
|
|
15
|
+
PmCliError: new (message: string, exitCode?: number) => Error;
|
|
16
|
+
getSettingsPath: (pmRoot: string) => string;
|
|
17
|
+
nowIso: () => string;
|
|
18
|
+
pathExists: (targetPath: string) => Promise<boolean>;
|
|
19
|
+
readFileIfExists: (targetPath: string) => Promise<string | null>;
|
|
20
|
+
resolvePmRoot: (cwd: string, overridePath?: string) => string;
|
|
21
|
+
writeFileAtomic: (targetPath: string, contents: string) => Promise<void>;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const sdk = await loadTemplatesSdkModule();
|
|
25
|
+
const {
|
|
26
|
+
CREATE_COMMANDER_REPEATABLE_OPTION_CONTRACTS,
|
|
27
|
+
EXIT_CODE,
|
|
28
|
+
PmCliError,
|
|
29
|
+
getSettingsPath,
|
|
30
|
+
nowIso,
|
|
31
|
+
pathExists,
|
|
32
|
+
readFileIfExists,
|
|
33
|
+
resolvePmRoot,
|
|
34
|
+
writeFileAtomic,
|
|
35
|
+
} = sdk;
|
|
36
|
+
|
|
37
|
+
async function loadTemplatesSdkModule(): Promise<TemplatesSdkModule> {
|
|
38
|
+
const envRoot = process.env[PM_PACKAGE_ROOT_ENV];
|
|
39
|
+
if (typeof envRoot !== "string" || envRoot.trim().length === 0) {
|
|
40
|
+
throw new Error(
|
|
41
|
+
`builtin-templates requires ${PM_PACKAGE_ROOT_ENV} to locate core SDK runtime exports.`,
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
const modulePath = path.join(path.resolve(envRoot.trim()), "dist", "sdk", "index.js");
|
|
45
|
+
try {
|
|
46
|
+
const loaded = (await import(pathToFileURL(modulePath).href)) as Partial<TemplatesSdkModule>;
|
|
47
|
+
if (
|
|
48
|
+
typeof loaded.resolvePmRoot === "function" &&
|
|
49
|
+
typeof loaded.pathExists === "function" &&
|
|
50
|
+
typeof loaded.PmCliError === "function" &&
|
|
51
|
+
Array.isArray(loaded.CREATE_COMMANDER_REPEATABLE_OPTION_CONTRACTS)
|
|
52
|
+
) {
|
|
53
|
+
return loaded as TemplatesSdkModule;
|
|
54
|
+
}
|
|
55
|
+
} catch {
|
|
56
|
+
// Fall through to deterministic failure message below.
|
|
57
|
+
}
|
|
58
|
+
throw new Error(
|
|
59
|
+
`builtin-templates failed to load SDK runtime exports from ${modulePath}.`,
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const TEMPLATE_DIRECTORY_NAME = "templates";
|
|
64
|
+
const TEMPLATE_FILE_EXTENSION = ".json";
|
|
65
|
+
const TEMPLATE_NAME_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$/;
|
|
66
|
+
const TEMPLATE_OPTION_REPEATABLE_KEYS = CREATE_COMMANDER_REPEATABLE_OPTION_CONTRACTS.map((entry) => entry.target);
|
|
67
|
+
const TEMPLATE_OPTION_REPEATABLE_KEY_SET = new Set<string>(TEMPLATE_OPTION_REPEATABLE_KEYS);
|
|
68
|
+
|
|
69
|
+
type TemplateOptionValue = string | string[];
|
|
70
|
+
export type CreateTemplateOptions = Record<string, TemplateOptionValue>;
|
|
71
|
+
|
|
72
|
+
interface StoredCreateTemplateDocument {
|
|
73
|
+
name: string;
|
|
74
|
+
created_at: string;
|
|
75
|
+
updated_at: string;
|
|
76
|
+
options: CreateTemplateOptions;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export interface TemplatesSaveResult {
|
|
80
|
+
name: string;
|
|
81
|
+
created_at: string;
|
|
82
|
+
updated_at: string;
|
|
83
|
+
path: string;
|
|
84
|
+
options: CreateTemplateOptions;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export interface TemplatesListResult {
|
|
88
|
+
templates: string[];
|
|
89
|
+
count: number;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export interface TemplatesShowResult {
|
|
93
|
+
name: string;
|
|
94
|
+
created_at: string;
|
|
95
|
+
updated_at: string;
|
|
96
|
+
path: string;
|
|
97
|
+
options: CreateTemplateOptions;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function normalizeTemplateName(rawName: string): string {
|
|
101
|
+
const name = rawName.trim();
|
|
102
|
+
if (!TEMPLATE_NAME_PATTERN.test(name)) {
|
|
103
|
+
throw new PmCliError(
|
|
104
|
+
`Invalid template name "${rawName}". Expected 1-64 characters matching [A-Za-z0-9][A-Za-z0-9._-]*.`,
|
|
105
|
+
EXIT_CODE.USAGE,
|
|
106
|
+
);
|
|
107
|
+
}
|
|
108
|
+
return name;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function templatesDirectory(pmRoot: string): string {
|
|
112
|
+
return path.join(pmRoot, TEMPLATE_DIRECTORY_NAME);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function templatePath(pmRoot: string, normalizedName: string): string {
|
|
116
|
+
return path.join(templatesDirectory(pmRoot), `${normalizedName}${TEMPLATE_FILE_EXTENSION}`);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
async function ensureTrackerInitialized(pmRoot: string): Promise<void> {
|
|
120
|
+
if (!(await pathExists(getSettingsPath(pmRoot)))) {
|
|
121
|
+
throw new PmCliError(`Tracker is not initialized at ${pmRoot}. Run pm init first.`, EXIT_CODE.NOT_FOUND);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function sortTemplateOptions(options: CreateTemplateOptions): CreateTemplateOptions {
|
|
126
|
+
return Object.fromEntries(
|
|
127
|
+
Object.entries(options).sort(([left], [right]) => left.localeCompare(right)),
|
|
128
|
+
) as CreateTemplateOptions;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function extractTemplateOptions(rawOptions: Record<string, unknown>): CreateTemplateOptions {
|
|
132
|
+
const next: CreateTemplateOptions = {};
|
|
133
|
+
for (const [key, value] of Object.entries(rawOptions)) {
|
|
134
|
+
if (value === undefined) {
|
|
135
|
+
continue;
|
|
136
|
+
}
|
|
137
|
+
if (TEMPLATE_OPTION_REPEATABLE_KEY_SET.has(key)) {
|
|
138
|
+
if (typeof value === "string") {
|
|
139
|
+
next[key] = [value];
|
|
140
|
+
continue;
|
|
141
|
+
}
|
|
142
|
+
if (!Array.isArray(value)) {
|
|
143
|
+
continue;
|
|
144
|
+
}
|
|
145
|
+
const values = value.filter((entry): entry is string => typeof entry === "string");
|
|
146
|
+
if (values.length > 0) {
|
|
147
|
+
next[key] = values;
|
|
148
|
+
}
|
|
149
|
+
continue;
|
|
150
|
+
}
|
|
151
|
+
if (typeof value === "string") {
|
|
152
|
+
next[key] = value;
|
|
153
|
+
continue;
|
|
154
|
+
}
|
|
155
|
+
if (Array.isArray(value) && value.every((entry) => typeof entry === "string")) {
|
|
156
|
+
next[key] = [...value];
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
return sortTemplateOptions(next);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function parseStoredTemplateOptions(rawOptions: unknown, templateName: string): CreateTemplateOptions {
|
|
163
|
+
if (typeof rawOptions !== "object" || rawOptions === null || Array.isArray(rawOptions)) {
|
|
164
|
+
throw new PmCliError(`Template "${templateName}" has invalid options payload.`, EXIT_CODE.GENERIC_FAILURE);
|
|
165
|
+
}
|
|
166
|
+
const normalized: CreateTemplateOptions = {};
|
|
167
|
+
for (const [key, value] of Object.entries(rawOptions as Record<string, unknown>)) {
|
|
168
|
+
const normalizedKey = key.trim();
|
|
169
|
+
if (normalizedKey.length === 0) {
|
|
170
|
+
throw new PmCliError(`Template "${templateName}" contains an empty option key.`, EXIT_CODE.GENERIC_FAILURE);
|
|
171
|
+
}
|
|
172
|
+
if (typeof value === "string") {
|
|
173
|
+
normalized[normalizedKey] = value;
|
|
174
|
+
continue;
|
|
175
|
+
}
|
|
176
|
+
if (Array.isArray(value) && value.every((entry) => typeof entry === "string")) {
|
|
177
|
+
normalized[normalizedKey] = [...value];
|
|
178
|
+
continue;
|
|
179
|
+
}
|
|
180
|
+
throw new PmCliError(`Template "${templateName}" contains invalid value for option "${normalizedKey}".`, EXIT_CODE.GENERIC_FAILURE);
|
|
181
|
+
}
|
|
182
|
+
return sortTemplateOptions(normalized);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
function parseStoredTemplateDocument(raw: string, normalizedName: string): StoredCreateTemplateDocument {
|
|
186
|
+
let parsed: unknown;
|
|
187
|
+
try {
|
|
188
|
+
parsed = JSON.parse(raw) as unknown;
|
|
189
|
+
} catch {
|
|
190
|
+
throw new PmCliError(`Template "${normalizedName}" contains invalid JSON.`, EXIT_CODE.GENERIC_FAILURE);
|
|
191
|
+
}
|
|
192
|
+
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
|
193
|
+
throw new PmCliError(`Template "${normalizedName}" has invalid document shape.`, EXIT_CODE.GENERIC_FAILURE);
|
|
194
|
+
}
|
|
195
|
+
const record = parsed as Record<string, unknown>;
|
|
196
|
+
const options = parseStoredTemplateOptions(record.options, normalizedName);
|
|
197
|
+
const now = nowIso();
|
|
198
|
+
return {
|
|
199
|
+
name: typeof record.name === "string" && record.name.trim().length > 0 ? record.name.trim() : normalizedName,
|
|
200
|
+
created_at: typeof record.created_at === "string" ? record.created_at : now,
|
|
201
|
+
updated_at: typeof record.updated_at === "string" ? record.updated_at : now,
|
|
202
|
+
options,
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
async function readStoredTemplateDocument(pmRoot: string, normalizedName: string): Promise<StoredCreateTemplateDocument> {
|
|
207
|
+
const raw = await readFileIfExists(templatePath(pmRoot, normalizedName));
|
|
208
|
+
if (raw === null) {
|
|
209
|
+
throw new PmCliError(`Template "${normalizedName}" not found`, EXIT_CODE.NOT_FOUND);
|
|
210
|
+
}
|
|
211
|
+
return parseStoredTemplateDocument(raw, normalizedName);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
export async function runTemplatesSave(
|
|
215
|
+
rawTemplateName: string,
|
|
216
|
+
options: Record<string, unknown>,
|
|
217
|
+
global: GlobalOptions,
|
|
218
|
+
): Promise<TemplatesSaveResult> {
|
|
219
|
+
const pmRoot = resolvePmRoot(process.cwd(), global.path);
|
|
220
|
+
await ensureTrackerInitialized(pmRoot);
|
|
221
|
+
const normalizedName = normalizeTemplateName(rawTemplateName);
|
|
222
|
+
const nextOptions = extractTemplateOptions(options);
|
|
223
|
+
if (Object.keys(nextOptions).length === 0) {
|
|
224
|
+
throw new PmCliError("templates save requires at least one create option flag", EXIT_CODE.USAGE);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
const now = nowIso();
|
|
228
|
+
const storedPath = templatePath(pmRoot, normalizedName);
|
|
229
|
+
let createdAt = now;
|
|
230
|
+
if (await pathExists(storedPath)) {
|
|
231
|
+
const existing = await readStoredTemplateDocument(pmRoot, normalizedName);
|
|
232
|
+
createdAt = existing.created_at;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
const document: StoredCreateTemplateDocument = {
|
|
236
|
+
name: normalizedName,
|
|
237
|
+
created_at: createdAt,
|
|
238
|
+
updated_at: now,
|
|
239
|
+
options: nextOptions,
|
|
240
|
+
};
|
|
241
|
+
await fs.mkdir(templatesDirectory(pmRoot), { recursive: true });
|
|
242
|
+
await writeFileAtomic(storedPath, `${JSON.stringify(document, null, 2)}\n`);
|
|
243
|
+
return {
|
|
244
|
+
name: document.name,
|
|
245
|
+
created_at: document.created_at,
|
|
246
|
+
updated_at: document.updated_at,
|
|
247
|
+
path: storedPath,
|
|
248
|
+
options: document.options,
|
|
249
|
+
};
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
export async function runTemplatesList(global: GlobalOptions): Promise<TemplatesListResult> {
|
|
253
|
+
const pmRoot = resolvePmRoot(process.cwd(), global.path);
|
|
254
|
+
await ensureTrackerInitialized(pmRoot);
|
|
255
|
+
const dirPath = templatesDirectory(pmRoot);
|
|
256
|
+
if (!(await pathExists(dirPath))) {
|
|
257
|
+
return { templates: [], count: 0 };
|
|
258
|
+
}
|
|
259
|
+
const entries = await fs.readdir(dirPath);
|
|
260
|
+
const templates = entries
|
|
261
|
+
.filter((entry) => entry.toLowerCase().endsWith(TEMPLATE_FILE_EXTENSION))
|
|
262
|
+
.map((entry) => entry.slice(0, -TEMPLATE_FILE_EXTENSION.length))
|
|
263
|
+
.filter((entry) => TEMPLATE_NAME_PATTERN.test(entry))
|
|
264
|
+
.sort((left, right) => left.localeCompare(right));
|
|
265
|
+
return {
|
|
266
|
+
templates,
|
|
267
|
+
count: templates.length,
|
|
268
|
+
};
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
export async function runTemplatesShow(rawTemplateName: string, global: GlobalOptions): Promise<TemplatesShowResult> {
|
|
272
|
+
const pmRoot = resolvePmRoot(process.cwd(), global.path);
|
|
273
|
+
await ensureTrackerInitialized(pmRoot);
|
|
274
|
+
const normalizedName = normalizeTemplateName(rawTemplateName);
|
|
275
|
+
const stored = await readStoredTemplateDocument(pmRoot, normalizedName);
|
|
276
|
+
return {
|
|
277
|
+
name: stored.name,
|
|
278
|
+
created_at: stored.created_at,
|
|
279
|
+
updated_at: stored.updated_at,
|
|
280
|
+
path: templatePath(pmRoot, normalizedName),
|
|
281
|
+
options: stored.options,
|
|
282
|
+
};
|
|
283
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@unbrained/pm-package-templates",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"private": true,
|
|
5
|
+
"type": "module",
|
|
6
|
+
"description": "First-party pm package for reusable create templates.",
|
|
7
|
+
"homepage": "https://github.com/unbraind/pm-cli/tree/main/packages/pm-templates#readme",
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "git+https://github.com/unbraind/pm-cli.git",
|
|
11
|
+
"directory": "packages/pm-templates"
|
|
12
|
+
},
|
|
13
|
+
"bugs": {
|
|
14
|
+
"url": "https://github.com/unbraind/pm-cli/issues"
|
|
15
|
+
},
|
|
16
|
+
"keywords": [
|
|
17
|
+
"pm-package",
|
|
18
|
+
"project-management",
|
|
19
|
+
"templates"
|
|
20
|
+
],
|
|
21
|
+
"pm": {
|
|
22
|
+
"aliases": [
|
|
23
|
+
"templates"
|
|
24
|
+
],
|
|
25
|
+
"extensions": [
|
|
26
|
+
"extensions/templates"
|
|
27
|
+
],
|
|
28
|
+
"catalog": {
|
|
29
|
+
"display_name": "Create Templates",
|
|
30
|
+
"category": "workflow",
|
|
31
|
+
"summary": "Save, list, show, and apply reusable pm create option templates.",
|
|
32
|
+
"tags": [
|
|
33
|
+
"templates",
|
|
34
|
+
"create",
|
|
35
|
+
"workflow"
|
|
36
|
+
],
|
|
37
|
+
"links": {
|
|
38
|
+
"docs": "https://github.com/unbraind/pm-cli/tree/main/packages/pm-templates#readme",
|
|
39
|
+
"repository": "https://github.com/unbraind/pm-cli/tree/main/packages/pm-templates",
|
|
40
|
+
"report": "https://github.com/unbraind/pm-cli/issues"
|
|
41
|
+
}
|
|
42
|
+
},
|
|
43
|
+
"docs": [
|
|
44
|
+
"README.md"
|
|
45
|
+
],
|
|
46
|
+
"examples": [
|
|
47
|
+
"README.md"
|
|
48
|
+
]
|
|
49
|
+
}
|
|
50
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# pm Todos Package
|
|
2
|
+
|
|
3
|
+
First-party pm package for Todo markdown import and export.
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
pm install ./packages/pm-todos --project
|
|
7
|
+
pm todos import --folder .pm/todos
|
|
8
|
+
pm todos export --folder .pm/todos
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
The package exposes the `todos import` and `todos export` extension commands through the `pm.extensions` package manifest. Runtime sources are authored in TypeScript and shipped with JavaScript entry artifacts for Node extension loading.
|