@ductape/cli 0.2.17 → 0.2.19

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.
@@ -5,6 +5,12 @@ export declare function runEventTopicCrud(verb: string, opts: {
5
5
  file?: string;
6
6
  json?: boolean;
7
7
  } & CommandInteractiveFlags, extraArgs: string[]): Promise<void>;
8
+ export declare function runNotificationMessageCrud(verb: string, opts: {
9
+ tag?: string;
10
+ notification?: string;
11
+ file?: string;
12
+ json?: boolean;
13
+ }): Promise<void>;
8
14
  export declare function runResourceCrud(typeName: string, verb: string, opts: {
9
15
  tag?: string;
10
16
  file?: string;
@@ -61,6 +61,38 @@ export async function runEventTopicCrud(verb, opts, extraArgs) {
61
61
  const result = await proxy.execute('messageBrokers', method, params);
62
62
  printJson(result, Boolean(opts.json));
63
63
  }
64
+ export async function runNotificationMessageCrud(verb, opts) {
65
+ const crud = verb.toLowerCase();
66
+ if (!['create', 'list', 'get', 'update'].includes(crud)) {
67
+ throw new Error('Notification message verb must be: create | list | get | update');
68
+ }
69
+ const session = requireSession();
70
+ const proxy = getSdkProxy(session);
71
+ const product = session.project.product_tag;
72
+ const body = ['create', 'update'].includes(crud)
73
+ ? await resolveBody({ file: opts.file, patch: crud === 'update', requireBody: crud === 'create', interactive: false })
74
+ : undefined;
75
+ const method = `messages.${crud === 'get' ? 'fetch' : crud}`;
76
+ let params;
77
+ if (crud === 'create')
78
+ params = [product, body ?? {}];
79
+ else if (crud === 'list') {
80
+ if (!opts.notification)
81
+ throw new Error('--notification <notification-tag> is required');
82
+ params = [product, opts.notification];
83
+ }
84
+ else if (crud === 'get') {
85
+ if (!opts.tag)
86
+ throw new Error('--tag <notification:message> is required');
87
+ params = [product, opts.tag];
88
+ }
89
+ else {
90
+ if (!opts.tag)
91
+ throw new Error('--tag <notification:message> is required');
92
+ params = [product, opts.tag, body ?? {}];
93
+ }
94
+ printJson(await proxy.execute('notifications', method, params), Boolean(opts.json));
95
+ }
64
96
  export async function runResourceCrud(typeName, verb, opts, extraArgs) {
65
97
  const crud = verb.toLowerCase();
66
98
  if (!CRUD_VERBS.includes(crud)) {
package/dist/index.js CHANGED
@@ -12,7 +12,7 @@ import { runUnlink } from './commands/unlink.js';
12
12
  import { runInit } from './commands/init.js';
13
13
  import { runInstall } from './commands/install.js';
14
14
  import { runStart, runStop, runStatus } from './commands/platform.js';
15
- import { runResourceCrud, runResourcesList, runEventTopicCrud } from './commands/resources.js';
15
+ import { runResourceCrud, runResourcesList, runEventTopicCrud, runNotificationMessageCrud } from './commands/resources.js';
16
16
  import { runCloud } from './commands/cloud.js';
17
17
  import { runDb, runDbContext } from './commands/db.js';
18
18
  import { runDbMigrate, runDbMigrateStatus, runDbMigrateRollback } from './commands/db-migrate.js';
@@ -255,6 +255,14 @@ eventTopics
255
255
  .option('--json', 'JSON output')
256
256
  .action(wrap((verb, opts) => runEventTopicCrud(verb, opts, [])));
257
257
  const resources = program.command('resources').description('Component CRUD (sdk-proxy)');
258
+ const notificationMessages = program.command('notifications').description('Notification templates');
259
+ notificationMessages
260
+ .command('messages <verb>')
261
+ .option('-t, --tag <tag>', 'Full notification:message tag')
262
+ .option('-n, --notification <tag>', 'Notification tag for list')
263
+ .option('-f, --file <path>', 'JSON template body for create/update')
264
+ .option('--json', 'JSON output')
265
+ .action(wrap((verb, opts) => runNotificationMessageCrud(verb, opts)));
258
266
  resources.command('types').option('--json', 'JSON output').action(wrap((opts) => runResourcesList(Boolean(opts.json))));
259
267
  resources
260
268
  .argument('<type>', 'Resource type (e.g. storage, database, cache …)')
@@ -53,10 +53,10 @@ export async function getProduct(ctx, opts) {
53
53
  return unwrapOne(result);
54
54
  }
55
55
  if (opts.tag) {
56
- const result = await client(ctx).getPath(`/integrations/v1/fetch/tag`, {
57
- ...q,
58
- tag: opts.tag,
59
- });
56
+ // The integrations REST fetch-by-tag route rejects current CLI login tokens with HTTP 401.
57
+ // Product inventory is an administrative read, so route it through the authenticated CLI
58
+ // SDK proxy (x-access-token), not the publishable-key runtime MCP proxy.
59
+ const result = await proxy(ctx).execute('product', 'fetch', [opts.tag]);
60
60
  return unwrapOne(result);
61
61
  }
62
62
  throw new Error('Provide --id <product_id> or --tag <product_tag>');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ductape/cli",
3
- "version": "0.2.17",
3
+ "version": "0.2.19",
4
4
  "description": "Ductape CLI — local platform, login, link projects, and manage resources via the proxy (Workbench-compatible)",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -73,6 +73,36 @@ export async function runEventTopicCrud(
73
73
  printJson(result, Boolean(opts.json));
74
74
  }
75
75
 
76
+ export async function runNotificationMessageCrud(
77
+ verb: string,
78
+ opts: { tag?: string; notification?: string; file?: string; json?: boolean },
79
+ ): Promise<void> {
80
+ const crud = verb.toLowerCase();
81
+ if (!['create', 'list', 'get', 'update'].includes(crud)) {
82
+ throw new Error('Notification message verb must be: create | list | get | update');
83
+ }
84
+ const session = requireSession();
85
+ const proxy = getSdkProxy(session);
86
+ const product = session.project.product_tag;
87
+ const body = ['create', 'update'].includes(crud)
88
+ ? await resolveBody({ file: opts.file, patch: crud === 'update', requireBody: crud === 'create', interactive: false })
89
+ : undefined;
90
+ const method = `messages.${crud === 'get' ? 'fetch' : crud}`;
91
+ let params: unknown[];
92
+ if (crud === 'create') params = [product, body ?? {}];
93
+ else if (crud === 'list') {
94
+ if (!opts.notification) throw new Error('--notification <notification-tag> is required');
95
+ params = [product, opts.notification];
96
+ } else if (crud === 'get') {
97
+ if (!opts.tag) throw new Error('--tag <notification:message> is required');
98
+ params = [product, opts.tag];
99
+ } else {
100
+ if (!opts.tag) throw new Error('--tag <notification:message> is required');
101
+ params = [product, opts.tag, body ?? {}];
102
+ }
103
+ printJson(await proxy.execute('notifications', method, params), Boolean(opts.json));
104
+ }
105
+
76
106
  export async function runResourceCrud(
77
107
  typeName: string,
78
108
  verb: string,
package/src/index.ts CHANGED
@@ -12,7 +12,7 @@ import { runUnlink } from './commands/unlink.js';
12
12
  import { runInit } from './commands/init.js';
13
13
  import { runInstall } from './commands/install.js';
14
14
  import { runStart, runStop, runStatus } from './commands/platform.js';
15
- import { runResourceCrud, runResourcesList, runEventTopicCrud } from './commands/resources.js';
15
+ import { runResourceCrud, runResourcesList, runEventTopicCrud, runNotificationMessageCrud } from './commands/resources.js';
16
16
  import { runCloud } from './commands/cloud.js';
17
17
  import { runDb, runDbContext } from './commands/db.js';
18
18
  import { runDbMigrate, runDbMigrateStatus, runDbMigrateRollback } from './commands/db-migrate.js';
@@ -332,6 +332,15 @@ eventTopics
332
332
 
333
333
  const resources = program.command('resources').description('Component CRUD (sdk-proxy)');
334
334
 
335
+ const notificationMessages = program.command('notifications').description('Notification templates');
336
+ notificationMessages
337
+ .command('messages <verb>')
338
+ .option('-t, --tag <tag>', 'Full notification:message tag')
339
+ .option('-n, --notification <tag>', 'Notification tag for list')
340
+ .option('-f, --file <path>', 'JSON template body for create/update')
341
+ .option('--json', 'JSON output')
342
+ .action(wrap((verb: string, opts) => runNotificationMessageCrud(verb, opts)));
343
+
335
344
  resources.command('types').option('--json', 'JSON output').action(wrap((opts) => runResourcesList(Boolean(opts.json))));
336
345
 
337
346
  resources
@@ -69,10 +69,10 @@ export async function getProduct(
69
69
  return unwrapOne(result);
70
70
  }
71
71
  if (opts.tag) {
72
- const result = await client(ctx).getPath<unknown>(`/integrations/v1/fetch/tag`, {
73
- ...q,
74
- tag: opts.tag,
75
- });
72
+ // The integrations REST fetch-by-tag route rejects current CLI login tokens with HTTP 401.
73
+ // Product inventory is an administrative read, so route it through the authenticated CLI
74
+ // SDK proxy (x-access-token), not the publishable-key runtime MCP proxy.
75
+ const result = await proxy(ctx).execute<unknown>('product', 'fetch', [opts.tag]);
76
76
  return unwrapOne(result);
77
77
  }
78
78
  throw new Error('Provide --id <product_id> or --tag <product_tag>');
@@ -1,5 +1,7 @@
1
1
  import { describe, it } from 'node:test';
2
2
  import assert from 'node:assert/strict';
3
+ import { readFileSync } from 'node:fs';
4
+ import { fileURLToPath } from 'node:url';
3
5
 
4
6
  // Test unwrap helpers indirectly via exported patterns — use small inline copies for unit scope
5
7
  function unwrapList<T>(result: unknown): T[] {
@@ -16,4 +18,25 @@ describe('platform-api helpers', () => {
16
18
  assert.deepEqual(unwrapList({ data: [{ tag: 'b' }] }), [{ tag: 'b' }]);
17
19
  assert.deepEqual(unwrapList({}), []);
18
20
  });
21
+
22
+ it('routes product fetch-by-tag through the authenticated CLI SDK proxy', () => {
23
+ const source = readFileSync(
24
+ fileURLToPath(new URL('../src/lib/platform-api.ts', import.meta.url)),
25
+ 'utf8',
26
+ );
27
+ assert.match(source, /proxy\(ctx\)\.execute<unknown>\('product', 'fetch', \[opts\.tag\]\)/);
28
+ assert.doesNotMatch(
29
+ source,
30
+ /getPath<unknown>\(`\/integrations\/v1\/fetch\/tag`/,
31
+ );
32
+ });
33
+
34
+ it('exposes notification message administration', () => {
35
+ const source = readFileSync(
36
+ fileURLToPath(new URL('../src/commands/resources.ts', import.meta.url)),
37
+ 'utf8',
38
+ );
39
+ assert.match(source, /runNotificationMessageCrud/);
40
+ assert.match(source, /`messages\.\$\{crud === 'get' \? 'fetch' : crud\}`/);
41
+ });
19
42
  });