@postplus/cli 0.1.26 → 0.1.28

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/LICENSE CHANGED
@@ -1,186 +1,95 @@
1
- Apache License
2
- Version 2.0, January 2004
3
- http://www.apache.org/licenses/
4
-
5
- TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6
-
7
- 1. Definitions.
8
-
9
- "License" shall mean the terms and conditions for use, reproduction,
10
- and distribution as defined by Sections 1 through 9 of this document.
11
-
12
- "Licensor" shall mean the copyright owner or entity authorized by
13
- the copyright owner that is granting the License.
14
-
15
- "Legal Entity" shall mean the union of the acting entity and all
16
- other entities that control, are controlled by, or are under common
17
- control with that entity. For the purposes of this definition,
18
- "control" means (i) the power, direct or indirect, to cause the
19
- direction or management of such entity, whether by contract or
20
- otherwise, or (ii) ownership of fifty percent (50%) or more of the
21
- outstanding shares, or (iii) beneficial ownership of such entity.
22
-
23
- "You" (or "Your") shall mean an individual or Legal Entity
24
- exercising permissions granted by this License.
25
-
26
- "Source" form shall mean the preferred form for making modifications,
27
- including but not limited to software source code, documentation
28
- source, and configuration files.
29
-
30
- "Object" form shall mean any form resulting from mechanical
31
- transformation or translation of a Source form, including but
32
- not limited to compiled object code, generated documentation,
33
- and conversions to other media types.
34
-
35
- "Work" shall mean the work of authorship made available under
36
- the License, as indicated by a copyright notice that is included in
37
- or attached to the work (an example is provided in the Appendix below).
38
-
39
- "Derivative Works" shall mean any work, whether in Source or Object
40
- form, that is based on (or derived from) the Work and for which the
41
- editorial revisions, annotations, elaborations, or other modifications
42
- represent, as a whole, an original work of authorship. For the purposes
43
- of this License, Derivative Works shall not include works that remain
44
- separable from, or merely link (or bind by name) to the interfaces of,
45
- the Work and Derivative Works thereof.
46
-
47
- "Contribution" shall mean, as submitted to the Licensor for inclusion
48
- in the Work by the copyright owner or by an individual or Legal Entity
49
- authorized to submit on behalf of the copyright owner. For the purposes
50
- of this definition, "submitted" means any form of electronic, verbal,
51
- or written communication sent to the Licensor or its representatives,
52
- including but not limited to communication on electronic mailing lists,
53
- source code control systems, and issue tracking systems that are managed
54
- by, or on behalf of, the Licensor for the purpose of discussing and
55
- improving the Work, but excluding communication that is conspicuously
56
- marked or designated in writing by the copyright owner as "Not a
57
- Contribution."
58
-
59
- "Contributor" shall mean Licensor and any Legal Entity on behalf of
60
- whom a Contribution has been received by the Licensor and incorporated
61
- within the Work.
62
-
63
- 2. Grant of Copyright License. Subject to the terms and conditions of
64
- this License, each Contributor hereby grants to You a perpetual,
65
- worldwide, non-exclusive, no-charge, royalty-free, irrevocable
66
- copyright license to reproduce, prepare Derivative Works of,
67
- publicly display, publicly perform, sublicense, and distribute the
68
- Work and such Derivative Works in Source or Object form.
69
-
70
- 3. Grant of Patent License. Subject to the terms and conditions of
71
- this License, each Contributor hereby grants to You a perpetual,
72
- worldwide, non-exclusive, no-charge, royalty-free, irrevocable
73
- (except as stated in this section) patent license to make, have made,
74
- use, offer to sell, sell, import, and otherwise transfer the Work,
75
- where such license applies only to those patent claims licensable
76
- by such Contributor that are necessarily infringed by their
77
- Contribution(s) alone or by combination of their Contribution(s)
78
- with the Work to which such Contribution(s) was submitted. If You
79
- institute patent litigation against any entity (including a cross-claim
80
- or counterclaim in a lawsuit) alleging that the Work or any
81
- Contribution embodied within the Work constitutes direct or
82
- contributory patent infringement, then any patent licenses granted
83
- to You under this License for that Work shall terminate as of the
84
- date such litigation is filed.
85
-
86
- 4. Redistribution. You may reproduce and distribute copies of the
87
- Work or Derivative Works thereof in any medium, with or without
88
- modifications, and in Source or Object form, provided that You
89
- meet the following conditions:
90
-
91
- (a) You must give any other recipients of the Work or Derivative
92
- Works a copy of this License; and
93
-
94
- (b) You must cause any modified files to carry prominent notices
95
- stating that You changed the files; and
96
-
97
- (c) You must retain, in the Source form of any Derivative Works
98
- that You distribute, all copyright, patent, trademark, and
99
- attribution notices from the Source form of the Work,
100
- excluding those notices that do not pertain to any part of
101
- the Derivative Works; and
102
-
103
- (d) If the Work includes a "NOTICE" text file as part of its
104
- distribution, You must include a readable copy of the
105
- attribution notices contained within such NOTICE file, in
106
- at least one of the following places: within a NOTICE text
107
- file distributed as part of the Derivative Works; within
108
- the Source form or documentation, if provided along with the
109
- Derivative Works; or, within a display generated by the
110
- Derivative Works, if and wherever such third-party notices
111
- normally appear. The contents of the NOTICE file are for
112
- informational purposes only and do not modify the License.
113
- You may add Your own attribution notices within Derivative
114
- Works that You distribute, alongside or as an addendum to
115
- the NOTICE text from the Work, provided that such additional
116
- attribution notices cannot be construed as modifying the
117
- License.
118
-
119
- You may add Your own license statement for Your modifications and
120
- may provide additional grant of rights to use, reproduce, modify,
121
- and distribute Your modifications, or for such Combined Works as a
122
- whole, under terms of Your choice, provided Your use, reproduction,
123
- modification, and distribution of the Work otherwise complies with
124
- the conditions stated in this License.
125
-
126
- 5. Submission of Contributions. Unless You explicitly state otherwise,
127
- any Contribution intentionally submitted for inclusion in the Work
128
- by You to the Licensor shall be under the terms and conditions of
129
- this License, without any additional terms or conditions.
130
- Notwithstanding the above, nothing herein shall supersede or modify
131
- the terms of any separate license agreement you may have executed
132
- with Licensor regarding such Contributions.
133
-
134
- 6. Trademarks. This License does not grant permission to use the trade
135
- names, trademarks, service marks, or product names of the Licensor,
136
- except as required for reasonable and customary use in describing the
137
- origin of the Work and reproducing the content of the NOTICE file.
138
-
139
- 7. Disclaimer of Warranty. Unless required by applicable law or
140
- agreed to in writing, Licensor provides the Work (and each
141
- Contributor provides its Contributions) on an "AS IS" BASIS,
142
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
143
- implied, including, without limitation, any warranties or conditions
144
- of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
145
- PARTICULAR PURPOSE. You are solely responsible for determining the
146
- appropriateness of using or reproducing the Work and assume any
147
- risks associated with Your exercise of permissions under this License.
148
-
149
- 8. Limitation of Liability. In no event and under no legal theory,
150
- whether in tort (including negligence), contract, or otherwise,
151
- unless required by applicable law (such as deliberate and grossly
152
- negligent acts) or agreed to in writing, shall any Contributor be
153
- liable to You for damages, including any direct, indirect, special,
154
- incidental, or exemplary damages of any character arising as a
155
- result of this License or out of the use or inability to use the
156
- Work (including but not limited to damages for loss of goodwill,
157
- work stoppage, computer failure or malfunction, or all other
158
- commercial damages or losses), even if such Contributor has been
159
- advised of the possibility of such damages.
160
-
161
- 9. Accepting Warranty or Additional Liability. While redistributing
162
- the Work or Derivative Works thereof, You may choose to offer,
163
- and charge a fee for, acceptance of support, warranty, indemnity,
164
- or other liability obligations and/or rights consistent with this
165
- License. However, in accepting such obligations, You may act only
166
- on Your own behalf and on Your sole responsibility, not on behalf
167
- of any other Contributor, and only if You agree to indemnify,
168
- defend, and hold each Contributor harmless for any liability
169
- incurred by, or claims asserted against, such Contributor by reason
170
- of your accepting any such warranty or additional liability.
171
-
172
- END OF TERMS AND CONDITIONS
173
-
174
- Copyright 2026 RealProductStudio
175
-
176
- Licensed under the Apache License, Version 2.0 (the "License");
177
- you may not use this file except in compliance with the License.
178
- You may obtain a copy of the License at
179
-
180
- http://www.apache.org/licenses/LICENSE-2.0
181
-
182
- Unless required by applicable law or agreed to in writing, software
183
- distributed under the License is distributed on an "AS IS" BASIS,
184
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
185
- See the License for the specific language governing permissions and
186
- limitations under the License.
1
+ # PolyForm Shield License 1.0.0
2
+
3
+ https://polyformproject.org/licenses/shield/1.0.0
4
+
5
+ ## Acceptance
6
+
7
+ In order to get any license under these terms, you must agree to them as both strict obligations and conditions to all your licenses.
8
+
9
+ ## Copyright License
10
+
11
+ The licensor grants you a copyright license for the software to do everything you might do with the software that would otherwise infringe the licensor's copyright in it for any permitted purpose. However, you may only distribute the software according to Distribution License and make changes or new works based on the software according to Changes and New Works License.
12
+
13
+ ## Distribution License
14
+
15
+ The licensor grants you an additional copyright license to distribute copies of the software. Your license to distribute covers distributing the software with changes and new works permitted by Changes and New Works License.
16
+
17
+ ## Notices
18
+
19
+ You must ensure that anyone who gets a copy of any part of the software from you also gets a copy of these terms or the URL for them above, as well as copies of any plain-text lines beginning with `Required Notice:` that the licensor provided with the software.
20
+
21
+ For example:
22
+
23
+ > Required Notice: Copyright Yoyodyne, Inc. (http://example.com)
24
+
25
+ ## Changes and New Works License
26
+
27
+ The licensor grants you an additional copyright license to make changes and new works based on the software for any permitted purpose.
28
+
29
+ ## Patent License
30
+
31
+ The licensor grants you a patent license for the software that covers patent claims the licensor can license, or becomes able to license, that you would infringe by using the software.
32
+
33
+ ## Noncompete
34
+
35
+ Any purpose is a permitted purpose, except for providing any product that competes with the software or any product the licensor or any of its affiliates provides using the software.
36
+
37
+ ## Competition
38
+
39
+ Goods and services compete even when they provide functionality through different kinds of interfaces or for different technical platforms. Applications can compete with services, libraries with plugins, frameworks with development tools, and so on, even if they're written in different programming languages or for different computer architectures. Goods and services compete even when provided free of charge.
40
+
41
+ If you market a product as a practical substitute for the software or another product, it definitely competes.
42
+
43
+ ## New Products
44
+
45
+ If you are using the software to provide a product that does not compete, but the licensor or any of its affiliates brings your product into competition by providing a new version of the software or another product using the software, you may continue using versions of the software available under these terms beforehand to provide your competing product, but not any later versions.
46
+
47
+ ## Discontinued Products
48
+
49
+ You may begin using the software to compete with a product or service that the licensor or any of its affiliates has stopped providing, unless the licensor includes a plain-text line beginning with `Licensor Line of Business:` with the software that mentions that line of business.
50
+
51
+ For example:
52
+
53
+ > Licensor Line of Business: YoyodyneCMS Content Management System (http://example.com/cms)
54
+
55
+ ## Sales of Business
56
+
57
+ If the licensor or any of its affiliates sells a line of business developing the software or using the software to provide a product, the buyer can also enforce Noncompete for that product.
58
+
59
+ ## Fair Use
60
+
61
+ You may have "fair use" rights for the software under the law. These terms do not limit them.
62
+
63
+ ## No Other Rights
64
+
65
+ These terms do not allow you to sublicense or transfer any of your licenses to anyone else, or prevent the licensor from granting licenses to anyone else. These terms do not imply any other licenses.
66
+
67
+ ## Patent Defense
68
+
69
+ If you make any written claim that the software infringes or contributes to infringement of any patent, your patent license for the software granted under these terms ends immediately. If your company makes such a claim, your patent license ends immediately for work on behalf of your company.
70
+
71
+ ## Violations
72
+
73
+ The first time you are notified in writing that you have violated any of these terms, or done anything with the software not covered by your licenses, your licenses can nonetheless continue if you come into full compliance with these terms, and take practical steps to correct past violations, within 32 days of receiving notice. Otherwise, all your licenses end immediately.
74
+
75
+ ## No Liability
76
+
77
+ As far as the law allows, the software comes as is, without any warranty or condition, and the licensor will not be liable to you for any damages arising out of these terms or the use or nature of the software, under any kind of legal claim.
78
+
79
+ ## Definitions
80
+
81
+ The licensor is the individual or entity offering these terms, and the software is the software the licensor makes available under these terms.
82
+
83
+ A product can be a good or service, or a combination of them.
84
+
85
+ You refers to the individual or entity agreeing to these terms.
86
+
87
+ Your company is any legal entity, sole proprietorship, or other kind of organization that you work for, plus all its affiliates.
88
+
89
+ Affiliates means the other organizations than an organization has control over, is under the control of, or is under common control with.
90
+
91
+ Control means ownership of substantially all the assets of an entity, or the power to direct its management and policies by vote, contract, or otherwise. Control can be direct or indirect.
92
+
93
+ Your licenses are all the licenses granted to you for the software under these terms.
94
+
95
+ Use means anything you do with the software requiring one of your licenses.
package/NOTICE CHANGED
@@ -1,7 +1,10 @@
1
1
  PostPlus CLI
2
- Copyright 2026 RealProductStudio
3
2
 
4
- This product is licensed under the Apache License, Version 2.0.
3
+ Required Notice: PostPlus CLI Copyright 2026 RealProductStudio.
4
+ Required Notice: PostPlus, RealProductStudio, and related names, logos, and marks are trademarks or product identifiers of RealProductStudio.
5
+ Licensor Line of Business: PostPlus marketing workflow automation, AI-agent skills, command-line tooling, hosted cloud capabilities, skill distribution, and related services.
6
+
7
+ This product is licensed under the PolyForm Shield License 1.0.0.
5
8
  See the LICENSE file for the full text.
6
9
 
7
10
  Third-Party Notices
package/README.md CHANGED
@@ -77,7 +77,7 @@ A skill can tell the agent:
77
77
 
78
78
  - when to use a workflow
79
79
  - what evidence to collect first
80
- - which tool or provider to call
80
+ - which PostPlus capability to use
81
81
  - how to normalize raw data
82
82
  - how to decide whether the result is good enough
83
83
  - what artifact to produce at the end
@@ -92,7 +92,7 @@ PostPlus is designed to reduce the risky parts of agent-based marketing work:
92
92
  - It separates routing, collection, synthesis, production, and publishing so each step has a clear job.
93
93
  - It keeps reusable rules in shared files instead of relying on one-off prompt memory.
94
94
  - It produces artifacts that can be inspected, reused, or handed to a teammate or client.
95
- - It makes external dependencies visible when a workflow needs a provider key, account, permission, or publishing destination.
95
+ - It makes external dependencies visible when a workflow needs an account, permission, local dependency, or publishing destination.
96
96
 
97
97
  The result should feel less like asking an agent to "do marketing" and more like giving a skilled operator a repeatable playbook.
98
98
 
@@ -323,7 +323,7 @@ Examples: transcription, subtitles, storyboard grids, reference decoding, B-roll
323
323
 
324
324
  For moving work into tools, reports, calendars, sheets, docs, social systems, or outreach channels.
325
325
 
326
- Examples: Feishu, Google Docs, Google Sheets, Postiz, Gmail outreach, customer folders, campaign reports.
326
+ Examples: Feishu, Google Docs, Google Sheets, social publishing, Gmail outreach, customer folders, campaign reports.
327
327
 
328
328
  ### 6. Grow and Optimize
329
329
 
@@ -347,10 +347,10 @@ This is not a full catalog. It is a practical map of the problems PostPlus is me
347
347
  | Find creators or KOL/KOC partners | Creator discovery, profile enrichment, content-fit scoring, shortlist building, contact signal extraction, outreach prep | TikTok creators, Instagram creators, Xiaohongshu accounts, X accounts, creator graph, follower bands, engagement proxy |
348
348
  | Decide whether a product is worth testing | Product selection, marketplace comparison, channel fit, price bands, review analysis, supply-side checks, sourcing judgment | Amazon, 1688, Google Trends, Xiaohongshu commerce, supplier ranking, SKU, MOQ, margin risk |
349
349
  | Turn references into creative direction | Reference decoding, hook analysis, visual grammar, benchmark-to-brief, persona packs, storyboard planning, prompt QA | TikTok videos, Reels, Xiaohongshu notes, short-form hooks, UGC, product demo, lifestyle, testimonial |
350
- | Produce media assets | Transcription, subtitles, frame extraction, B-roll planning, image generation, video generation, voice generation, edit packaging | Whisper, SRT/VTT/ASS, B-roll, storyboard grid, hosted media generation, image prompts, video requests |
350
+ | Produce media assets | Transcription, subtitles, frame extraction, B-roll planning, image generation, video generation, voice generation, edit packaging | Speech-to-text, SRT/VTT/ASS, B-roll, storyboard grid, hosted media generation, image prompts, video requests |
351
351
  | Plan content and messaging | Positioning, content strategy, copywriting, social content, email sequences, SEO, AI search, launch planning | Blog, landing page, LinkedIn, X, Xiaohongshu, cold email, content pillars, hooks, objections, offers |
352
352
  | Improve conversion or growth | CRO, signup flow, onboarding, paid creative, analytics, A/B tests, pricing, referrals, churn prevention, revenue operations | Landing page, signup, funnel, Google Ads, paid social, GA4, experiments, pricing page, lifecycle |
353
- | Package and hand off work | Client reports, campaign folders, Feishu docs, Google Sheets, social scheduling, Gmail outreach, publishing records | Feishu/Lark, Google Docs, Google Sheets, Postiz, Gmail, customer workspace, campaign report |
353
+ | Package and hand off work | Client reports, campaign folders, Feishu docs, Google Sheets, social scheduling, Gmail outreach, publishing records | Feishu/Lark, Google Docs, Google Sheets, social publishing, Gmail, customer workspace, campaign report |
354
354
 
355
355
  Most workflows combine two or three rows. For example, a creator campaign may start with TikTok research, move into creator discovery, produce an outreach shortlist, then publish the result into Feishu or Google Sheets.
356
356
 
@@ -392,3 +392,19 @@ The best first prompt includes:
392
392
  - the target platform if you already know it
393
393
  - any reference links, files, accounts, or assets
394
394
  - the artifact you want at the end
395
+
396
+ ## License
397
+
398
+ PostPlus CLI and PostPlus Skills are source-available under the PolyForm Shield
399
+ License 1.0.0.
400
+
401
+ You may use PostPlus, including for commercial work, to create your own
402
+ marketing research, strategy, media, reports, outreach lists, and other
403
+ deliverables.
404
+
405
+ You may not sell, host, distribute, repackage, rename, or provide PostPlus or a
406
+ competing product or service as a substitute for PostPlus.
407
+
408
+ Every copy or distribution must include the license terms and the Required
409
+ Notice lines provided with this repository. Contact RealProductStudio for a
410
+ separate commercial license if you need rights outside the public license.
package/build/doctor.js CHANGED
@@ -2,6 +2,7 @@ import { resolveFreshRemoteAuth, } from './auth-session.js';
2
2
  import { buildPostPlusClientCompatibilityHeaders, formatPostPlusCompatibilityError, } from './client-compatibility.js';
3
3
  import { resolveHostedBaseUrl } from './hosted-release.js';
4
4
  import { formatLocalDependencyReport, generateLocalDependencyReport, } from './local-dependencies.js';
5
+ import { loadPublicSkillCatalog, } from './skill-catalog.js';
5
6
  import { readSubscriptionStatusField } from './subscription-status.js';
6
7
  function createPass(id, label, detail, severity = 'required') {
7
8
  return {
@@ -23,12 +24,16 @@ function createFail(id, label, detail, fix, input = {}) {
23
24
  metadata: input.metadata,
24
25
  };
25
26
  }
26
- export async function generateDoctorReport() {
27
+ export async function generateDoctorReport(options = {}) {
27
28
  const hostedBaseUrl = await resolveHostedBaseUrl();
28
29
  const checks = [
29
30
  createPass('hosted_base_url', 'PostPlus Cloud', `Using ${hostedBaseUrl ?? 'https://postplus.io'}`),
30
31
  ];
31
- checks.push(await checkLocalDependencies());
32
+ const skillScope = await resolveSkillScope(options.skillId);
33
+ if (skillScope) {
34
+ checks.push(createPass('skill_catalog', 'Skill selection', `Using ${skillScope.skill.skillId} from catalog ${skillScope.catalog.releaseId}`));
35
+ }
36
+ checks.push(await checkLocalDependencies(skillScope));
32
37
  if (!hostedBaseUrl) {
33
38
  checks.push(createFail('remote_auth', 'Remote auth', 'PostPlus Cloud base URL could not be resolved.', 'Configure POSTPLUS_API_BASE_URL or run `postplus auth login`.'));
34
39
  return buildDoctorReport(checks);
@@ -46,17 +51,40 @@ export async function generateDoctorReport() {
46
51
  const authCheck = await checkRemoteAuth(auth);
47
52
  checks.push(authCheck);
48
53
  if (authCheck.status === 'pass') {
49
- checks.push(await checkHostedCapabilities(auth));
54
+ checks.push(await checkHostedCapabilities(auth, skillScope));
55
+ }
56
+ return buildDoctorReport(checks, options.skillId);
57
+ }
58
+ async function resolveSkillScope(skillId) {
59
+ if (!skillId) {
60
+ return null;
61
+ }
62
+ const catalog = await loadPublicSkillCatalog();
63
+ const skill = catalog.skills.find((entry) => entry.skillId === skillId);
64
+ if (!skill) {
65
+ throw new Error(`Unknown PostPlus skill: ${skillId}. Run \`postplus list\` to see released skill ids.`);
50
66
  }
51
- return buildDoctorReport(checks);
67
+ return { catalog, skill };
52
68
  }
53
- async function checkLocalDependencies() {
69
+ async function checkLocalDependencies(skillScope) {
54
70
  try {
55
- const report = await generateLocalDependencyReport();
71
+ const report = await generateLocalDependencyReport(skillScope
72
+ ? {
73
+ loadCatalog: async () => ({
74
+ ...skillScope.catalog,
75
+ skills: [skillScope.skill],
76
+ }),
77
+ }
78
+ : {});
56
79
  const detail = formatLocalDependencyReport(report);
57
80
  if (!report.ok) {
58
- return createFail('local_dependencies', 'Task-specific local media dependencies', detail, 'Run the affected PostPlus skill in a local agent. The installed postplus-shared rules tell the agent how to bootstrap approved missing media dependencies.', {
59
- severity: 'task_specific',
81
+ const skillId = skillScope?.skill.skillId;
82
+ return createFail('local_dependencies', skillId
83
+ ? `Local dependencies for ${skillId}`
84
+ : 'Task-specific local media dependencies', detail, skillId
85
+ ? 'Run the selected PostPlus skill in a local agent. The installed postplus-shared rules tell the agent how to bootstrap approved missing dependencies.'
86
+ : 'Run the affected PostPlus skill in a local agent. The installed postplus-shared rules tell the agent how to bootstrap approved missing media dependencies.', {
87
+ severity: skillId ? 'required' : 'task_specific',
60
88
  metadata: {
61
89
  bootstrapRule: 'postplus-shared',
62
90
  missingDependencies: report.checks
@@ -69,7 +97,9 @@ async function checkLocalDependencies() {
69
97
  },
70
98
  });
71
99
  }
72
- return createPass('local_dependencies', 'Local dependencies', detail);
100
+ return createPass('local_dependencies', skillScope
101
+ ? `Local dependencies for ${skillScope.skill.skillId}`
102
+ : 'Local dependencies', detail);
73
103
  }
74
104
  catch (error) {
75
105
  return createFail('local_dependencies', 'Local dependencies', error instanceof Error
@@ -77,13 +107,14 @@ async function checkLocalDependencies() {
77
107
  : 'Failed to check local dependencies.');
78
108
  }
79
109
  }
80
- function buildDoctorReport(checks) {
110
+ function buildDoctorReport(checks, skillId) {
81
111
  const requiredOk = checks.every((check) => check.severity !== 'required' || check.status === 'pass');
82
112
  return {
83
113
  schemaVersion: 1,
84
114
  ok: checks.every((check) => check.status === 'pass'),
85
115
  requiredOk,
86
116
  checks,
117
+ ...(skillId ? { skillId } : {}),
87
118
  };
88
119
  }
89
120
  async function checkRemoteAuth(input) {
@@ -114,7 +145,7 @@ async function checkRemoteAuth(input) {
114
145
  : 'Failed to validate PostPlus Cloud auth.', 'Run `postplus auth validate` after confirming network access.');
115
146
  }
116
147
  }
117
- async function checkHostedCapabilities(input) {
148
+ async function checkHostedCapabilities(input, skillScope) {
118
149
  try {
119
150
  let response = await requestWithAuth(input, '/api/postplus-cli/hosted/readiness');
120
151
  if (response.status === 401) {
@@ -127,17 +158,28 @@ async function checkHostedCapabilities(input) {
127
158
  if (!response.ok) {
128
159
  return createFail('hosted_capabilities', 'Hosted capabilities', readErrorMessage(payload, 'PostPlus Cloud hosted readiness check failed.'));
129
160
  }
130
- const capabilities = Array.isArray(payload.capabilities)
131
- ? payload.capabilities
132
- : [];
133
- const failedLabels = capabilities
134
- .map(readCapabilityFailureLabel)
161
+ const capabilities = readHostedCapabilityEntries(payload.capabilities);
162
+ const relevantCapabilities = skillScope
163
+ ? filterCapabilitiesForSkill(capabilities, skillScope.skill.requirements)
164
+ : capabilities;
165
+ const failedLabels = relevantCapabilities
166
+ .map((value) => readCapabilityFailureLabel(value, skillScope))
135
167
  .filter((value) => value !== null);
136
- if (payload.ok !== true || failedLabels.length > 0) {
137
- return createFail('hosted_capabilities', 'Hosted capabilities', `Not ready: ${failedLabels.join(', ') || 'unknown capability failure'}`, 'Check PostPlus Cloud provider configuration and subscription state.');
168
+ if (skillScope && hasHostedRequirements(skillScope.skill.requirements)) {
169
+ const missingRequirements = collectMissingHostedRequirementLabels(relevantCapabilities, skillScope.skill.requirements);
170
+ failedLabels.push(...missingRequirements);
171
+ }
172
+ if (failedLabels.length > 0 ||
173
+ (!skillScope && payload.ok !== true && capabilities.length === 0)) {
174
+ const skillId = skillScope?.skill.skillId;
175
+ return createFail('hosted_capabilities', skillId ? `Hosted capabilities for ${skillId}` : 'Hosted capabilities', `Not ready: ${failedLabels.join(', ') || 'unknown capability failure'}`, 'Check PostPlus Cloud provider configuration and subscription state.', {
176
+ severity: skillId ? 'required' : 'task_specific',
177
+ });
138
178
  }
139
179
  const subscription = readSubscriptionStatusField(payload).label;
140
- return createPass('hosted_capabilities', 'Hosted capabilities', `Ready (${capabilities.length} capability checks passed; subscription ${subscription})`);
180
+ return createPass('hosted_capabilities', skillScope
181
+ ? `Hosted capabilities for ${skillScope.skill.skillId}`
182
+ : 'Hosted capabilities', `Ready (${relevantCapabilities.length} capability checks passed; subscription ${subscription})`);
141
183
  }
142
184
  catch (error) {
143
185
  return createFail('hosted_capabilities', 'Hosted capabilities', error instanceof Error
@@ -145,7 +187,13 @@ async function checkHostedCapabilities(input) {
145
187
  : 'Failed to check hosted capability readiness.');
146
188
  }
147
189
  }
148
- function readCapabilityFailureLabel(value) {
190
+ function readHostedCapabilityEntries(value) {
191
+ if (!Array.isArray(value)) {
192
+ return [];
193
+ }
194
+ return value.filter((entry) => !!entry && typeof entry === 'object' && !Array.isArray(entry));
195
+ }
196
+ function readCapabilityFailureLabel(value, skillScope) {
149
197
  if (!value || typeof value !== 'object') {
150
198
  return 'invalid capability response';
151
199
  }
@@ -163,9 +211,12 @@ function readCapabilityFailureLabel(value) {
163
211
  .map(readReadinessCheckFailureLabel)
164
212
  .filter((check) => check !== null)
165
213
  : [];
166
- return failedChecks.length > 0
214
+ const labelWithFailures = failedChecks.length > 0
167
215
  ? `${label} (${failedChecks.join(', ')})`
168
216
  : label;
217
+ return skillScope
218
+ ? `${labelWithFailures} for ${skillScope.skill.skillId}`
219
+ : labelWithFailures;
169
220
  }
170
221
  function readReadinessCheckFailureLabel(value) {
171
222
  if (!value || typeof value !== 'object') {
@@ -181,6 +232,128 @@ function readReadinessCheckFailureLabel(value) {
181
232
  ? record.id
182
233
  : 'unknown check';
183
234
  }
235
+ function filterCapabilitiesForSkill(capabilities, requirements) {
236
+ if (!hasHostedRequirements(requirements)) {
237
+ return [];
238
+ }
239
+ return capabilities.filter((capability) => capabilityMatchesRequirements(capability, requirements));
240
+ }
241
+ function capabilityMatchesRequirements(capability, requirements) {
242
+ const identifiers = collectCapabilityIdentifiers(capability);
243
+ const hostedCapabilities = new Set(requirements.hostedCapabilities);
244
+ const requirementKeys = collectHostedRequirementKeys(requirements);
245
+ return identifiers.some((identifier) => {
246
+ if (identifier === 'media-file:upload' &&
247
+ hostedCapabilities.has('media-file') &&
248
+ !requiresHostedMediaFileUpload(requirements)) {
249
+ return false;
250
+ }
251
+ const [prefix, suffix] = splitCapabilityIdentifier(identifier);
252
+ if (prefix === 'media-file' &&
253
+ suffix &&
254
+ suffix !== 'upload' &&
255
+ hostedCapabilities.has('media-file')) {
256
+ return true;
257
+ }
258
+ if (hostedCapabilities.has(identifier)) {
259
+ return true;
260
+ }
261
+ if (prefix &&
262
+ suffix &&
263
+ hostedCapabilities.has(prefix) &&
264
+ requirementKeys.has(suffix)) {
265
+ return true;
266
+ }
267
+ return requirementKeys.has(identifier) || requirementKeys.has(suffix);
268
+ });
269
+ }
270
+ function requiresHostedMediaFileUpload(requirements) {
271
+ return (requirements.hostedCapabilities.includes('media-generation') ||
272
+ requirements.endpointKeys.length > 0);
273
+ }
274
+ function collectCapabilityIdentifiers(capability) {
275
+ const identifiers = new Set();
276
+ for (const key of [
277
+ 'id',
278
+ 'key',
279
+ 'capability',
280
+ 'capabilityKey',
281
+ 'collectionKey',
282
+ 'endpointKey',
283
+ 'modelKey',
284
+ 'sourceKey',
285
+ 'accountConnection',
286
+ ]) {
287
+ const value = capability[key];
288
+ if (typeof value === 'string' && value.trim()) {
289
+ identifiers.add(value.trim());
290
+ }
291
+ }
292
+ if (Array.isArray(capability.checks)) {
293
+ for (const check of capability.checks) {
294
+ if (!check || typeof check !== 'object' || Array.isArray(check)) {
295
+ continue;
296
+ }
297
+ for (const identifier of collectCapabilityIdentifiers(check)) {
298
+ identifiers.add(identifier);
299
+ }
300
+ }
301
+ }
302
+ return [...identifiers];
303
+ }
304
+ function collectHostedRequirementKeys(requirements) {
305
+ return new Set([
306
+ ...requirements.accountConnections,
307
+ ...requirements.collectionKeys,
308
+ ...requirements.endpointKeys,
309
+ ...requirements.modelKeys,
310
+ ...requirements.sourceKeys,
311
+ ]);
312
+ }
313
+ function hasHostedRequirements(requirements) {
314
+ return (requirements.accountConnections.length > 0 ||
315
+ requirements.collectionKeys.length > 0 ||
316
+ requirements.endpointKeys.length > 0 ||
317
+ requirements.hostedCapabilities.length > 0 ||
318
+ requirements.modelKeys.length > 0 ||
319
+ requirements.sourceKeys.length > 0);
320
+ }
321
+ function collectMissingHostedRequirementLabels(capabilities, requirements) {
322
+ const availableIdentifiers = new Set(capabilities.flatMap(collectCapabilityIdentifiers));
323
+ const missing = [];
324
+ for (const capability of requirements.hostedCapabilities) {
325
+ if (![...availableIdentifiers].some((identifier) => identifierMatchesCapability(identifier, capability))) {
326
+ missing.push(capability);
327
+ }
328
+ }
329
+ for (const key of collectHostedRequirementKeys(requirements)) {
330
+ if (![...availableIdentifiers].some((identifier) => identifierMatchesKey(identifier, key))) {
331
+ missing.push(key);
332
+ }
333
+ }
334
+ return missing.map((value) => `${value} readiness check missing`);
335
+ }
336
+ function identifierMatchesKey(identifier, key) {
337
+ if (identifier === key) {
338
+ return true;
339
+ }
340
+ const [, suffix] = splitCapabilityIdentifier(identifier);
341
+ return suffix === key;
342
+ }
343
+ function identifierMatchesCapability(identifier, capability) {
344
+ if (identifier === capability) {
345
+ return true;
346
+ }
347
+ const [prefix] = splitCapabilityIdentifier(identifier);
348
+ return prefix === capability;
349
+ }
350
+ function splitCapabilityIdentifier(identifier) {
351
+ const index = identifier.indexOf(':');
352
+ if (index === -1) {
353
+ return [null, identifier];
354
+ }
355
+ return [identifier.slice(0, index), identifier.slice(index + 1)];
356
+ }
184
357
  function readErrorMessage(payload, fallback) {
185
358
  const compatibilityError = formatPostPlusCompatibilityError(payload);
186
359
  if (compatibilityError) {
package/build/index.js CHANGED
@@ -37,11 +37,11 @@ Usage:
37
37
  postplus auth status [--json]
38
38
  postplus auth validate [--json]
39
39
  postplus auth logout [--json]
40
- postplus doctor [--json]
40
+ postplus doctor [--skill <skill-id>] [--json]
41
41
  postplus update
42
42
  postplus uninstall
43
43
  postplus list [--json]
44
- postplus status [--json]
44
+ postplus status [--skill <skill-id>] [--json]
45
45
  postplus version
46
46
  postplus help
47
47
 
@@ -49,15 +49,15 @@ Skills:
49
49
  ${POSTPLUS_SKILLS_INSTALL_COMMAND}
50
50
  `);
51
51
  }
52
- async function runDoctor(json) {
53
- const report = await generateDoctorReport();
54
- if (json) {
52
+ async function runDoctor(options) {
53
+ const report = await generateDoctorReport({ skillId: options.skillId });
54
+ if (options.json) {
55
55
  writeJson(report);
56
56
  }
57
57
  else {
58
58
  process.stdout.write(`${formatDoctorReport(report)}\n`);
59
59
  }
60
- return report.ok ? 0 : 1;
60
+ return report.requiredOk ? 0 : 1;
61
61
  }
62
62
  async function runAuthStatus(json) {
63
63
  const report = await generateAuthStatusReport();
@@ -69,9 +69,9 @@ async function runAuthStatus(json) {
69
69
  }
70
70
  return report.ok ? 0 : 1;
71
71
  }
72
- async function runStatus(json) {
73
- const report = await generateStatusReport();
74
- if (json) {
72
+ async function runStatus(options) {
73
+ const report = await generateStatusReport({ skillId: options.skillId });
74
+ if (options.json) {
75
75
  writeJson(report);
76
76
  }
77
77
  else {
@@ -119,6 +119,29 @@ async function runSkillUninstallCommand() {
119
119
  function writeJson(value) {
120
120
  process.stdout.write(`${JSON.stringify(value, null, 2)}\n`);
121
121
  }
122
+ function parseDiagnosticOptions(args) {
123
+ const options = {
124
+ json: false,
125
+ };
126
+ for (let index = 0; index < args.length; index += 1) {
127
+ const arg = args[index];
128
+ if (arg === '--json') {
129
+ options.json = true;
130
+ continue;
131
+ }
132
+ if (arg === '--skill') {
133
+ const skillId = args[index + 1];
134
+ if (!skillId || skillId.startsWith('--')) {
135
+ throw new Error('Missing value for --skill.');
136
+ }
137
+ options.skillId = skillId;
138
+ index += 1;
139
+ continue;
140
+ }
141
+ throw new Error(`Unknown option for diagnostics command: ${arg}`);
142
+ }
143
+ return options;
144
+ }
122
145
  async function runAuthLogout(json) {
123
146
  const report = await clearAuthState();
124
147
  if (json) {
@@ -199,7 +222,7 @@ async function main() {
199
222
  return;
200
223
  }
201
224
  case 'doctor':
202
- process.exitCode = await runDoctor(json);
225
+ process.exitCode = await runDoctor(parseDiagnosticOptions(rest));
203
226
  return;
204
227
  case 'install':
205
228
  process.stderr.write(`PostPlus CLI does not install skills directly. Run \`${POSTPLUS_SKILLS_INSTALL_COMMAND}\`.\n`);
@@ -215,7 +238,7 @@ async function main() {
215
238
  process.exitCode = await runList(json);
216
239
  return;
217
240
  case 'status':
218
- process.exitCode = await runStatus(json);
241
+ process.exitCode = await runStatus(parseDiagnosticOptions(rest));
219
242
  return;
220
243
  case 'auth': {
221
244
  const [subcommand, ...authRest] = rest;
@@ -15,6 +15,15 @@ export const POSTPLUS_SKILLS_INSTALL_COMMAND = formatPostPlusSkillsInstallComman
15
15
  export const POSTPLUS_SKILLS_LIST_COMMAND = formatPostPlusSkillsListCommand();
16
16
  const POSTPLUS_SKILLS_INDEX_URL = 'https://raw.githubusercontent.com/PostPlusAI/postplus-skills/main/skills/INDEX.md';
17
17
  const POSTPLUS_SKILLS_CATALOG_URL = 'https://raw.githubusercontent.com/PostPlusAI/postplus-skills/main/skills/catalog.json';
18
+ export const PUBLIC_SKILL_REQUIREMENT_KEYS = [
19
+ 'accountConnections',
20
+ 'collectionKeys',
21
+ 'endpointKeys',
22
+ 'hostedCapabilities',
23
+ 'localDependencies',
24
+ 'modelKeys',
25
+ 'sourceKeys',
26
+ ];
18
27
  export async function loadPublicSkillCatalog(fetchFn = fetch, env = process.env) {
19
28
  const catalogUrl = resolvePostPlusSkillsCatalogUrl(env);
20
29
  const skillsSource = resolvePostPlusSkillsSource(env);
@@ -95,17 +104,7 @@ function parsePublicSkillCatalog(payload) {
95
104
  const path = typeof skill.path === 'string' && skill.path.trim()
96
105
  ? skill.path.trim()
97
106
  : null;
98
- const requirements = skill.requirements &&
99
- typeof skill.requirements === 'object' &&
100
- !Array.isArray(skill.requirements)
101
- ? skill.requirements
102
- : {};
103
- const localDependencies = Array.isArray(requirements.localDependencies)
104
- ? requirements.localDependencies
105
- .filter((value) => typeof value === 'string')
106
- .map((value) => value.trim())
107
- .filter(Boolean)
108
- : [];
107
+ const requirements = parsePublicSkillRequirements(skill.requirements);
109
108
  const status = typeof skill.status === 'string' ? skill.status.trim() : '';
110
109
  if (!skillId ||
111
110
  !path ||
@@ -113,9 +112,10 @@ function parsePublicSkillCatalog(payload) {
113
112
  throw new Error('PostPlus public skill catalog has an invalid skill.');
114
113
  }
115
114
  return {
116
- localDependencies,
115
+ localDependencies: requirements.localDependencies,
117
116
  skillId,
118
117
  path,
118
+ requirements,
119
119
  };
120
120
  });
121
121
  if (skills.length === 0) {
@@ -127,3 +127,40 @@ function parsePublicSkillCatalog(payload) {
127
127
  source,
128
128
  };
129
129
  }
130
+ function parsePublicSkillRequirements(value) {
131
+ if (value === undefined) {
132
+ return createEmptyRequirements();
133
+ }
134
+ if (!value || typeof value !== 'object' || Array.isArray(value)) {
135
+ throw new Error('PostPlus public skill catalog has invalid skill requirements.');
136
+ }
137
+ const record = value;
138
+ const requirements = createEmptyRequirements();
139
+ for (const key of PUBLIC_SKILL_REQUIREMENT_KEYS) {
140
+ const raw = record[key];
141
+ if (raw === undefined) {
142
+ continue;
143
+ }
144
+ if (!Array.isArray(raw)) {
145
+ throw new Error(`PostPlus public skill catalog has invalid ${key} requirements.`);
146
+ }
147
+ requirements[key] = raw.map((item) => {
148
+ if (typeof item !== 'string' || !item.trim()) {
149
+ throw new Error(`PostPlus public skill catalog has invalid ${key} requirements.`);
150
+ }
151
+ return item.trim();
152
+ });
153
+ }
154
+ return requirements;
155
+ }
156
+ function createEmptyRequirements() {
157
+ return {
158
+ accountConnections: [],
159
+ collectionKeys: [],
160
+ endpointKeys: [],
161
+ hostedCapabilities: [],
162
+ localDependencies: [],
163
+ modelKeys: [],
164
+ sourceKeys: [],
165
+ };
166
+ }
package/build/status.js CHANGED
@@ -3,17 +3,17 @@ import { writeCurrentCliVersionToLocalConfig } from './client-compatibility.js';
3
3
  import { formatDoctorReport, generateDoctorReport, } from './doctor.js';
4
4
  import { formatSkillInstallStatusReport, generateSkillInstallStatusReport, } from './skill-management.js';
5
5
  import { formatUpdateStatusReport, generateUpdateStatusReport, } from './update-check.js';
6
- export async function generateStatusReport() {
7
- return generateStatusReportWithDependencies();
6
+ export async function generateStatusReport(options = {}) {
7
+ return generateStatusReportWithDependencies({}, options);
8
8
  }
9
- export async function generateStatusReportWithDependencies(dependencies = {}) {
9
+ export async function generateStatusReportWithDependencies(dependencies = {}, options = {}) {
10
10
  await writeCurrentCliVersionToLocalConfig();
11
11
  const generateAuthStatus = dependencies.generateAuthStatus ?? generateAuthStatusReport;
12
12
  const generateDoctor = dependencies.generateDoctor ?? generateDoctorReport;
13
13
  const generateSkillStatus = dependencies.generateSkillStatus ?? generateSkillInstallStatusReport;
14
14
  const generateUpdateStatus = dependencies.generateUpdateStatus ?? generateUpdateStatusReport;
15
15
  const [doctor, auth, skills, updates] = await Promise.all([
16
- generateDoctor(),
16
+ generateDoctor({ skillId: options.skillId }),
17
17
  generateAuthStatus(),
18
18
  generateSkillStatus(),
19
19
  generateUpdateStatus(),
@@ -25,6 +25,7 @@ export async function generateStatusReportWithDependencies(dependencies = {}) {
25
25
  auth,
26
26
  skills,
27
27
  updates,
28
+ ...(options.skillId ? { skillId: options.skillId } : {}),
28
29
  };
29
30
  }
30
31
  export function formatStatusReport(report) {
package/package.json CHANGED
@@ -1,10 +1,10 @@
1
1
  {
2
2
  "name": "@postplus/cli",
3
- "version": "0.1.26",
3
+ "version": "0.1.28",
4
4
  "packageManager": "pnpm@10.30.3+sha512.c961d1e0a2d8e354ecaa5166b822516668b7f44cb5bd95122d590dd81922f606f5473b6d23ec4a5be05e7fcd18e8488d47d978bbe981872f1145d06e9a740017",
5
5
  "type": "module",
6
6
  "description": "PostPlus CLI for PostPlus Cloud auth, status, and diagnostics.",
7
- "license": "Apache-2.0",
7
+ "license": "SEE LICENSE IN LICENSE",
8
8
  "files": [
9
9
  "build/auth-lifecycle.js",
10
10
  "build/auth-login.js",