@optima-chat/dev-skills 0.7.35 → 0.7.36

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 CHANGED
@@ -13,6 +13,7 @@ Prefer the installed CLI tools over reimplementing long shell workflows:
13
13
  - `optima-grant-balance <email> --amount <usd> [options]`
14
14
  - `optima-product <create|update|add-channel|toggle-channel|show> [options]` — manage paid-plugin marketplace Products + Stripe channels (Wave 1.5 admin endpoints; stage default)
15
15
  - `optima-entitlement <grant|revoke|list> [options]` — admin-grant / revoke / list paid-plugin entitlements (refuses revoke of PAYMENT / PARTNER source)
16
+ - `optima-plugin <show|set-paid|set-default> [options]` — flip a plugin's skills-side paid/free state (isPaid) + defaultForUser (the user-facing gate; pairs with optima-product for the billing side)
16
17
 
17
18
  For code-reading tasks across Optima repositories, use `gh` commands against `Optima-Chat/<repo>`.
18
19
 
@@ -43,6 +43,7 @@ const DEV_SKILLS_CLIENT_SECRET_KEY = 'DEV_SKILLS_OAUTH_CLIENT_SECRET';
43
43
  // retry; any transient failure surfaces immediately. Re-run the CLI.
44
44
  const tokenCache: Record<string, string> = {};
45
45
  const billingUrlCache: Record<string, string> = {};
46
+ const skillsUrlCache: Record<string, string> = {};
46
47
 
47
48
  function getBillingUrl(env: string): string {
48
49
  if (billingUrlCache[env]) return billingUrlCache[env];
@@ -51,6 +52,13 @@ function getBillingUrl(env: string): string {
51
52
  return url;
52
53
  }
53
54
 
55
+ function getSkillsUrl(env: string): string {
56
+ if (skillsUrlCache[env]) return skillsUrlCache[env];
57
+ const url = fetchInfisicalSecret(env, '/shared-secrets/domain-urls', 'SKILLS_REGISTRY_URL');
58
+ skillsUrlCache[env] = url;
59
+ return url;
60
+ }
61
+
54
62
  export function getServiceToken(env: string): string {
55
63
  if (tokenCache[env]) return tokenCache[env];
56
64
 
@@ -89,7 +97,7 @@ interface BillingErrorEnvelope {
89
97
  code?: string;
90
98
  }
91
99
 
92
- function formatBillingError(status: number, statusText: string, body: string): string {
100
+ function formatServiceError(status: number, statusText: string, body: string): string {
93
101
  let parsed: BillingErrorEnvelope | null = null;
94
102
  try { parsed = JSON.parse(body); } catch { /* non-JSON */ }
95
103
 
@@ -114,24 +122,25 @@ function formatBillingError(status: number, statusText: string, body: string): s
114
122
  return `❌ Error [${status}] ${statusText}\n Response body (first 500 bytes): ${body.slice(0, 500)}`;
115
123
  }
116
124
 
117
- // ───── Public: callBilling ──────────────────────────────────────────────────
118
- export interface BillingResponse<T> {
125
+ // ───── Public: callService / callBilling / callSkills ───────────────────────
126
+ export interface ServiceResponse<T> {
119
127
  status: number;
120
128
  body: T;
121
129
  }
122
130
 
123
131
  /**
124
- * Make an authenticated call to optima-billing. Returns `{status, body}` on
125
- * 2xx; throws Error with formatted message on non-2xx. Single retry on 5xx
126
- * (one-shot no exponential backoff; admin CLI doesn't justify it).
132
+ * Authenticated call to an Optima service (billing or skills same dev-skills
133
+ * M2M token works for both). Returns `{status, body}` on 2xx; throws Error with
134
+ * formatted message on non-2xx. Single retry on 5xx (no backoff admin CLI).
127
135
  */
128
- export async function callBilling<T = unknown>(
136
+ async function callService<T>(
137
+ baseUrl: string,
129
138
  env: string,
130
139
  method: 'GET' | 'POST' | 'PATCH' | 'PUT' | 'DELETE',
131
140
  path: string,
132
141
  body?: object,
133
- ): Promise<BillingResponse<T>> {
134
- const url = `${getBillingUrl(env)}${path}`;
142
+ ): Promise<ServiceResponse<T>> {
143
+ const url = `${baseUrl}${path}`;
135
144
  const token = getServiceToken(env);
136
145
 
137
146
  const doFetch = async () => fetch(url, {
@@ -145,18 +154,38 @@ export async function callBilling<T = unknown>(
145
154
 
146
155
  let res = await doFetch();
147
156
  if (res.status >= 500) {
148
- // One retry on 5xx
149
157
  res = await doFetch();
150
158
  }
151
159
  const text = await res.text();
152
160
  if (!res.ok) {
153
- throw new Error(formatBillingError(res.status, res.statusText, text));
161
+ throw new Error(formatServiceError(res.status, res.statusText, text));
154
162
  }
155
163
  let parsed: T;
156
164
  try {
157
165
  parsed = text ? (JSON.parse(text) as T) : (undefined as unknown as T);
158
166
  } catch {
159
- throw new Error(`Billing returned non-JSON 2xx body: ${text.slice(0, 200)}`);
167
+ throw new Error(`Service returned non-JSON 2xx body: ${text.slice(0, 200)}`);
160
168
  }
161
169
  return { status: res.status, body: parsed };
162
170
  }
171
+
172
+ /** Kept as an alias for billing-side callers that reference the response type. */
173
+ export interface BillingResponse<T> extends ServiceResponse<T> {}
174
+
175
+ export async function callBilling<T = unknown>(
176
+ env: string,
177
+ method: 'GET' | 'POST' | 'PATCH' | 'PUT' | 'DELETE',
178
+ path: string,
179
+ body?: object,
180
+ ): Promise<ServiceResponse<T>> {
181
+ return callService<T>(getBillingUrl(env), env, method, path, body);
182
+ }
183
+
184
+ export async function callSkills<T = unknown>(
185
+ env: string,
186
+ method: 'GET' | 'POST' | 'PATCH' | 'PUT' | 'DELETE',
187
+ path: string,
188
+ body?: object,
189
+ ): Promise<ServiceResponse<T>> {
190
+ return callService<T>(getSkillsUrl(env), env, method, path, body);
191
+ }
@@ -0,0 +1,65 @@
1
+ import { callSkills, validateEnv } from '../billing-http';
2
+ import { confirmIfProd } from '../confirm-prompt';
3
+
4
+ interface SetDefaultArgs {
5
+ slug: string;
6
+ default: boolean;
7
+ yes: boolean;
8
+ env: string;
9
+ }
10
+
11
+ function parseArgs(argv: string[]): SetDefaultArgs {
12
+ if (argv.length === 0 || argv[0] === '-h' || argv[0] === '--help') {
13
+ console.log(`Usage: optima-plugin set-default --slug <slug> --default true|false [options]
14
+
15
+ Required:
16
+ --slug <slug>
17
+ --default true|false Sets Plugin.defaultForUser
18
+
19
+ Optional:
20
+ --yes Skip prod confirmation prompt (no-op on stage)
21
+ --env stage|prod (default: stage)
22
+
23
+ Note: no skill-sync broadcast — changes what NEW user syncs receive; does not
24
+ retroactively add/remove the plugin for existing users until their next sync.`);
25
+ process.exit(0);
26
+ }
27
+ const out: Partial<SetDefaultArgs> = { env: 'stage', yes: false };
28
+ for (let i = 0; i < argv.length; i++) {
29
+ const a = argv[i];
30
+ const next = argv[i + 1];
31
+ switch (a) {
32
+ case '--slug': out.slug = next; i++; break;
33
+ case '--default':
34
+ if (next !== 'true' && next !== 'false') throw new Error('--default must be true or false');
35
+ out.default = next === 'true'; i++; break;
36
+ case '--yes': out.yes = true; break;
37
+ case '--env': out.env = next; i++; break;
38
+ default: throw new Error(`Unknown arg: ${a}`);
39
+ }
40
+ }
41
+ if (!out.slug) throw new Error('--slug required');
42
+ if (out.default === undefined) throw new Error('--default required (true|false)');
43
+ return out as SetDefaultArgs;
44
+ }
45
+
46
+ export async function runSetDefault(argv: string[]): Promise<void> {
47
+ const args = parseArgs(argv);
48
+ validateEnv(args.env);
49
+
50
+ await confirmIfProd(
51
+ args.env,
52
+ `Action: set defaultForUser=${args.default} on plugin '${args.slug}' (${args.env.toUpperCase()})`,
53
+ args.yes,
54
+ );
55
+
56
+ console.log(`\n🔧 Setting defaultForUser=${args.default} on ${args.slug} (${args.env.toUpperCase()})...`);
57
+ const res = await callSkills(
58
+ args.env,
59
+ 'PATCH',
60
+ `/api/admin/plugins/${encodeURIComponent(args.slug)}`,
61
+ { defaultForUser: args.default },
62
+ );
63
+ console.log(`✓ Updated plugin (HTTP ${res.status}):`);
64
+ console.log(JSON.stringify(res.body, null, 2));
65
+ }
@@ -0,0 +1,69 @@
1
+ import { callSkills, validateEnv } from '../billing-http';
2
+ import { confirmIfProd } from '../confirm-prompt';
3
+
4
+ interface SetPaidArgs {
5
+ slug: string;
6
+ paid: boolean;
7
+ yes: boolean;
8
+ env: string;
9
+ }
10
+
11
+ function parseArgs(argv: string[]): SetPaidArgs {
12
+ if (argv.length === 0 || argv[0] === '-h' || argv[0] === '--help') {
13
+ console.log(`Usage: optima-plugin set-paid --slug <slug> --paid true|false [options]
14
+
15
+ Required:
16
+ --slug <slug>
17
+ --paid true|false Sets Plugin.isPaid (the user-facing paid/free gate)
18
+
19
+ Optional:
20
+ --yes Skip prod confirmation prompt (no-op on stage)
21
+ --env stage|prod (default: stage)
22
+
23
+ Note: salesUrl is NOT settable here (skills PATCH is strict; salesUrl is
24
+ publish-time-only via plugin.json metadata). When isPaid=true and salesUrl is
25
+ null, the 402 falls back to sales.optima.onl.`);
26
+ process.exit(0);
27
+ }
28
+ const out: Partial<SetPaidArgs> = { env: 'stage', yes: false };
29
+ for (let i = 0; i < argv.length; i++) {
30
+ const a = argv[i];
31
+ const next = argv[i + 1];
32
+ switch (a) {
33
+ case '--slug': out.slug = next; i++; break;
34
+ case '--paid':
35
+ if (next !== 'true' && next !== 'false') throw new Error('--paid must be true or false');
36
+ out.paid = next === 'true'; i++; break;
37
+ case '--yes': out.yes = true; break;
38
+ case '--env': out.env = next; i++; break;
39
+ default: throw new Error(`Unknown arg: ${a}`);
40
+ }
41
+ }
42
+ if (!out.slug) throw new Error('--slug required');
43
+ if (out.paid === undefined) throw new Error('--paid required (true|false)');
44
+ return out as SetPaidArgs;
45
+ }
46
+
47
+ export async function runSetPaid(argv: string[]): Promise<void> {
48
+ const args = parseArgs(argv);
49
+ validateEnv(args.env);
50
+
51
+ await confirmIfProd(
52
+ args.env,
53
+ `Action: set isPaid=${args.paid} on plugin '${args.slug}' (${args.env.toUpperCase()})`,
54
+ args.yes,
55
+ );
56
+
57
+ console.log(`\n💰 Setting isPaid=${args.paid} on ${args.slug} (${args.env.toUpperCase()})...`);
58
+ const res = await callSkills(
59
+ args.env,
60
+ 'PATCH',
61
+ `/api/admin/plugins/${encodeURIComponent(args.slug)}`,
62
+ { isPaid: args.paid },
63
+ );
64
+ console.log(`✓ Updated plugin (HTTP ${res.status}):`);
65
+ console.log(JSON.stringify(res.body, null, 2));
66
+ if (args.paid) {
67
+ console.log(`\nℹ️ Reminder: ensure a billing Product + channel exists for '${args.slug}' (optima-product) or users will 402 with no purchase path. salesUrl is publish-time-only (currently shown above).`);
68
+ }
69
+ }
@@ -0,0 +1,42 @@
1
+ import { callSkills, validateEnv } from '../billing-http';
2
+
3
+ interface ShowArgs {
4
+ slug: string;
5
+ env: string;
6
+ }
7
+
8
+ function parseArgs(argv: string[]): ShowArgs {
9
+ if (argv.length === 0 || argv[0] === '-h' || argv[0] === '--help') {
10
+ console.log(`Usage: optima-plugin show --slug <slug> [options]
11
+
12
+ Required:
13
+ --slug <slug>
14
+
15
+ Optional:
16
+ --env stage|prod (default: stage)
17
+
18
+ Note: reads the public GET /api/plugins/:slug — shows isPaid, salesUrl, and
19
+ descriptive fields, but NOT defaultForUser / status / trustLevel (public
20
+ endpoint omits them). Returns 404 for non-ACTIVE plugins.`);
21
+ process.exit(0);
22
+ }
23
+ const out: Partial<ShowArgs> = { env: 'stage' };
24
+ for (let i = 0; i < argv.length; i++) {
25
+ const a = argv[i];
26
+ const next = argv[i + 1];
27
+ switch (a) {
28
+ case '--slug': out.slug = next; i++; break;
29
+ case '--env': out.env = next; i++; break;
30
+ default: throw new Error(`Unknown arg: ${a}`);
31
+ }
32
+ }
33
+ if (!out.slug) throw new Error('--slug required');
34
+ return out as ShowArgs;
35
+ }
36
+
37
+ export async function runShow(argv: string[]): Promise<void> {
38
+ const args = parseArgs(argv);
39
+ validateEnv(args.env);
40
+ const res = await callSkills(args.env, 'GET', `/api/plugins/${encodeURIComponent(args.slug)}`);
41
+ console.log(JSON.stringify(res.body, null, 2));
42
+ }
@@ -0,0 +1,32 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { runShow } from './plugin/show';
4
+ import { runSetPaid } from './plugin/set-paid';
5
+ import { runSetDefault } from './plugin/set-default';
6
+
7
+ function printHelp() {
8
+ console.log(`Usage: optima-plugin <subcommand> [options]
9
+
10
+ Subcommands:
11
+ show Show a plugin's marketplace state (isPaid, salesUrl, ... ACTIVE plugins only)
12
+ set-paid Flip a plugin's isPaid flag (the user-facing paid/free gate)
13
+ set-default Flip a plugin's defaultForUser flag
14
+
15
+ Run 'optima-plugin <subcommand> --help' for subcommand-specific options.`);
16
+ }
17
+
18
+ async function main() {
19
+ const [, , subcommand, ...rest] = process.argv;
20
+ if (!subcommand || subcommand === '-h' || subcommand === '--help') { printHelp(); process.exit(0); }
21
+ switch (subcommand) {
22
+ case 'show': await runShow(rest); break;
23
+ case 'set-paid': await runSetPaid(rest); break;
24
+ case 'set-default': await runSetDefault(rest); break;
25
+ default:
26
+ console.error(`Unknown subcommand: ${subcommand}`);
27
+ printHelp();
28
+ process.exit(1);
29
+ }
30
+ }
31
+
32
+ main().catch((err) => { console.error(err.message); process.exit(1); });
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.validateEnv = validateEnv;
4
4
  exports.getServiceToken = getServiceToken;
5
5
  exports.callBilling = callBilling;
6
+ exports.callSkills = callSkills;
6
7
  const child_process_1 = require("child_process");
7
8
  const infisical_secrets_1 = require("./infisical-secrets");
8
9
  const db_utils_1 = require("./db-utils");
@@ -44,6 +45,7 @@ const DEV_SKILLS_CLIENT_SECRET_KEY = 'DEV_SKILLS_OAUTH_CLIENT_SECRET';
44
45
  // retry; any transient failure surfaces immediately. Re-run the CLI.
45
46
  const tokenCache = {};
46
47
  const billingUrlCache = {};
48
+ const skillsUrlCache = {};
47
49
  function getBillingUrl(env) {
48
50
  if (billingUrlCache[env])
49
51
  return billingUrlCache[env];
@@ -51,6 +53,13 @@ function getBillingUrl(env) {
51
53
  billingUrlCache[env] = url;
52
54
  return url;
53
55
  }
56
+ function getSkillsUrl(env) {
57
+ if (skillsUrlCache[env])
58
+ return skillsUrlCache[env];
59
+ const url = (0, infisical_secrets_1.fetchInfisicalSecret)(env, '/shared-secrets/domain-urls', 'SKILLS_REGISTRY_URL');
60
+ skillsUrlCache[env] = url;
61
+ return url;
62
+ }
54
63
  function getServiceToken(env) {
55
64
  if (tokenCache[env])
56
65
  return tokenCache[env];
@@ -77,7 +86,7 @@ function getServiceToken(env) {
77
86
  tokenCache[env] = parsed.access_token;
78
87
  return parsed.access_token;
79
88
  }
80
- function formatBillingError(status, statusText, body) {
89
+ function formatServiceError(status, statusText, body) {
81
90
  let parsed = null;
82
91
  try {
83
92
  parsed = JSON.parse(body);
@@ -104,12 +113,12 @@ function formatBillingError(status, statusText, body) {
104
113
  return `❌ Error [${status}] ${statusText}\n Response body (first 500 bytes): ${body.slice(0, 500)}`;
105
114
  }
106
115
  /**
107
- * Make an authenticated call to optima-billing. Returns `{status, body}` on
108
- * 2xx; throws Error with formatted message on non-2xx. Single retry on 5xx
109
- * (one-shot no exponential backoff; admin CLI doesn't justify it).
116
+ * Authenticated call to an Optima service (billing or skills same dev-skills
117
+ * M2M token works for both). Returns `{status, body}` on 2xx; throws Error with
118
+ * formatted message on non-2xx. Single retry on 5xx (no backoff admin CLI).
110
119
  */
111
- async function callBilling(env, method, path, body) {
112
- const url = `${getBillingUrl(env)}${path}`;
120
+ async function callService(baseUrl, env, method, path, body) {
121
+ const url = `${baseUrl}${path}`;
113
122
  const token = getServiceToken(env);
114
123
  const doFetch = async () => fetch(url, {
115
124
  method,
@@ -121,19 +130,24 @@ async function callBilling(env, method, path, body) {
121
130
  });
122
131
  let res = await doFetch();
123
132
  if (res.status >= 500) {
124
- // One retry on 5xx
125
133
  res = await doFetch();
126
134
  }
127
135
  const text = await res.text();
128
136
  if (!res.ok) {
129
- throw new Error(formatBillingError(res.status, res.statusText, text));
137
+ throw new Error(formatServiceError(res.status, res.statusText, text));
130
138
  }
131
139
  let parsed;
132
140
  try {
133
141
  parsed = text ? JSON.parse(text) : undefined;
134
142
  }
135
143
  catch {
136
- throw new Error(`Billing returned non-JSON 2xx body: ${text.slice(0, 200)}`);
144
+ throw new Error(`Service returned non-JSON 2xx body: ${text.slice(0, 200)}`);
137
145
  }
138
146
  return { status: res.status, body: parsed };
139
147
  }
148
+ async function callBilling(env, method, path, body) {
149
+ return callService(getBillingUrl(env), env, method, path, body);
150
+ }
151
+ async function callSkills(env, method, path, body) {
152
+ return callService(getSkillsUrl(env), env, method, path, body);
153
+ }
@@ -0,0 +1,61 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.runSetDefault = runSetDefault;
4
+ const billing_http_1 = require("../billing-http");
5
+ const confirm_prompt_1 = require("../confirm-prompt");
6
+ function parseArgs(argv) {
7
+ if (argv.length === 0 || argv[0] === '-h' || argv[0] === '--help') {
8
+ console.log(`Usage: optima-plugin set-default --slug <slug> --default true|false [options]
9
+
10
+ Required:
11
+ --slug <slug>
12
+ --default true|false Sets Plugin.defaultForUser
13
+
14
+ Optional:
15
+ --yes Skip prod confirmation prompt (no-op on stage)
16
+ --env stage|prod (default: stage)
17
+
18
+ Note: no skill-sync broadcast — changes what NEW user syncs receive; does not
19
+ retroactively add/remove the plugin for existing users until their next sync.`);
20
+ process.exit(0);
21
+ }
22
+ const out = { env: 'stage', yes: false };
23
+ for (let i = 0; i < argv.length; i++) {
24
+ const a = argv[i];
25
+ const next = argv[i + 1];
26
+ switch (a) {
27
+ case '--slug':
28
+ out.slug = next;
29
+ i++;
30
+ break;
31
+ case '--default':
32
+ if (next !== 'true' && next !== 'false')
33
+ throw new Error('--default must be true or false');
34
+ out.default = next === 'true';
35
+ i++;
36
+ break;
37
+ case '--yes':
38
+ out.yes = true;
39
+ break;
40
+ case '--env':
41
+ out.env = next;
42
+ i++;
43
+ break;
44
+ default: throw new Error(`Unknown arg: ${a}`);
45
+ }
46
+ }
47
+ if (!out.slug)
48
+ throw new Error('--slug required');
49
+ if (out.default === undefined)
50
+ throw new Error('--default required (true|false)');
51
+ return out;
52
+ }
53
+ async function runSetDefault(argv) {
54
+ const args = parseArgs(argv);
55
+ (0, billing_http_1.validateEnv)(args.env);
56
+ await (0, confirm_prompt_1.confirmIfProd)(args.env, `Action: set defaultForUser=${args.default} on plugin '${args.slug}' (${args.env.toUpperCase()})`, args.yes);
57
+ console.log(`\n🔧 Setting defaultForUser=${args.default} on ${args.slug} (${args.env.toUpperCase()})...`);
58
+ const res = await (0, billing_http_1.callSkills)(args.env, 'PATCH', `/api/admin/plugins/${encodeURIComponent(args.slug)}`, { defaultForUser: args.default });
59
+ console.log(`✓ Updated plugin (HTTP ${res.status}):`);
60
+ console.log(JSON.stringify(res.body, null, 2));
61
+ }
@@ -0,0 +1,65 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.runSetPaid = runSetPaid;
4
+ const billing_http_1 = require("../billing-http");
5
+ const confirm_prompt_1 = require("../confirm-prompt");
6
+ function parseArgs(argv) {
7
+ if (argv.length === 0 || argv[0] === '-h' || argv[0] === '--help') {
8
+ console.log(`Usage: optima-plugin set-paid --slug <slug> --paid true|false [options]
9
+
10
+ Required:
11
+ --slug <slug>
12
+ --paid true|false Sets Plugin.isPaid (the user-facing paid/free gate)
13
+
14
+ Optional:
15
+ --yes Skip prod confirmation prompt (no-op on stage)
16
+ --env stage|prod (default: stage)
17
+
18
+ Note: salesUrl is NOT settable here (skills PATCH is strict; salesUrl is
19
+ publish-time-only via plugin.json metadata). When isPaid=true and salesUrl is
20
+ null, the 402 falls back to sales.optima.onl.`);
21
+ process.exit(0);
22
+ }
23
+ const out = { env: 'stage', yes: false };
24
+ for (let i = 0; i < argv.length; i++) {
25
+ const a = argv[i];
26
+ const next = argv[i + 1];
27
+ switch (a) {
28
+ case '--slug':
29
+ out.slug = next;
30
+ i++;
31
+ break;
32
+ case '--paid':
33
+ if (next !== 'true' && next !== 'false')
34
+ throw new Error('--paid must be true or false');
35
+ out.paid = next === 'true';
36
+ i++;
37
+ break;
38
+ case '--yes':
39
+ out.yes = true;
40
+ break;
41
+ case '--env':
42
+ out.env = next;
43
+ i++;
44
+ break;
45
+ default: throw new Error(`Unknown arg: ${a}`);
46
+ }
47
+ }
48
+ if (!out.slug)
49
+ throw new Error('--slug required');
50
+ if (out.paid === undefined)
51
+ throw new Error('--paid required (true|false)');
52
+ return out;
53
+ }
54
+ async function runSetPaid(argv) {
55
+ const args = parseArgs(argv);
56
+ (0, billing_http_1.validateEnv)(args.env);
57
+ await (0, confirm_prompt_1.confirmIfProd)(args.env, `Action: set isPaid=${args.paid} on plugin '${args.slug}' (${args.env.toUpperCase()})`, args.yes);
58
+ console.log(`\n💰 Setting isPaid=${args.paid} on ${args.slug} (${args.env.toUpperCase()})...`);
59
+ const res = await (0, billing_http_1.callSkills)(args.env, 'PATCH', `/api/admin/plugins/${encodeURIComponent(args.slug)}`, { isPaid: args.paid });
60
+ console.log(`✓ Updated plugin (HTTP ${res.status}):`);
61
+ console.log(JSON.stringify(res.body, null, 2));
62
+ if (args.paid) {
63
+ console.log(`\nℹ️ Reminder: ensure a billing Product + channel exists for '${args.slug}' (optima-product) or users will 402 with no purchase path. salesUrl is publish-time-only (currently shown above).`);
64
+ }
65
+ }
@@ -0,0 +1,45 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.runShow = runShow;
4
+ const billing_http_1 = require("../billing-http");
5
+ function parseArgs(argv) {
6
+ if (argv.length === 0 || argv[0] === '-h' || argv[0] === '--help') {
7
+ console.log(`Usage: optima-plugin show --slug <slug> [options]
8
+
9
+ Required:
10
+ --slug <slug>
11
+
12
+ Optional:
13
+ --env stage|prod (default: stage)
14
+
15
+ Note: reads the public GET /api/plugins/:slug — shows isPaid, salesUrl, and
16
+ descriptive fields, but NOT defaultForUser / status / trustLevel (public
17
+ endpoint omits them). Returns 404 for non-ACTIVE plugins.`);
18
+ process.exit(0);
19
+ }
20
+ const out = { env: 'stage' };
21
+ for (let i = 0; i < argv.length; i++) {
22
+ const a = argv[i];
23
+ const next = argv[i + 1];
24
+ switch (a) {
25
+ case '--slug':
26
+ out.slug = next;
27
+ i++;
28
+ break;
29
+ case '--env':
30
+ out.env = next;
31
+ i++;
32
+ break;
33
+ default: throw new Error(`Unknown arg: ${a}`);
34
+ }
35
+ }
36
+ if (!out.slug)
37
+ throw new Error('--slug required');
38
+ return out;
39
+ }
40
+ async function runShow(argv) {
41
+ const args = parseArgs(argv);
42
+ (0, billing_http_1.validateEnv)(args.env);
43
+ const res = await (0, billing_http_1.callSkills)(args.env, 'GET', `/api/plugins/${encodeURIComponent(args.slug)}`);
44
+ console.log(JSON.stringify(res.body, null, 2));
45
+ }
@@ -0,0 +1,39 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ Object.defineProperty(exports, "__esModule", { value: true });
4
+ const show_1 = require("./plugin/show");
5
+ const set_paid_1 = require("./plugin/set-paid");
6
+ const set_default_1 = require("./plugin/set-default");
7
+ function printHelp() {
8
+ console.log(`Usage: optima-plugin <subcommand> [options]
9
+
10
+ Subcommands:
11
+ show Show a plugin's marketplace state (isPaid, salesUrl, ... ACTIVE plugins only)
12
+ set-paid Flip a plugin's isPaid flag (the user-facing paid/free gate)
13
+ set-default Flip a plugin's defaultForUser flag
14
+
15
+ Run 'optima-plugin <subcommand> --help' for subcommand-specific options.`);
16
+ }
17
+ async function main() {
18
+ const [, , subcommand, ...rest] = process.argv;
19
+ if (!subcommand || subcommand === '-h' || subcommand === '--help') {
20
+ printHelp();
21
+ process.exit(0);
22
+ }
23
+ switch (subcommand) {
24
+ case 'show':
25
+ await (0, show_1.runShow)(rest);
26
+ break;
27
+ case 'set-paid':
28
+ await (0, set_paid_1.runSetPaid)(rest);
29
+ break;
30
+ case 'set-default':
31
+ await (0, set_default_1.runSetDefault)(rest);
32
+ break;
33
+ default:
34
+ console.error(`Unknown subcommand: ${subcommand}`);
35
+ printHelp();
36
+ process.exit(1);
37
+ }
38
+ }
39
+ main().catch((err) => { console.error(err.message); process.exit(1); });