@parall/cli 1.55.3 → 1.55.5

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.
@@ -0,0 +1,3 @@
1
+ import { Command } from 'commander';
2
+ export declare function registerProfileCommands(program: Command): void;
3
+ //# sourceMappingURL=profile.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"profile.d.ts","sourceRoot":"","sources":["../../src/commands/profile.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAWpC,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,OAAO,QA8GvD"}
@@ -0,0 +1,114 @@
1
+ import { readFileSync } from 'node:fs';
2
+ import { resolveCredentials } from '../lib/client.js';
3
+ import { printJson, printError } from '../lib/output.js';
4
+ // Profile commands follow the platform's uniform permission loop: `set` calls
5
+ // the ATOMIC endpoints directly. Callers with edit power (humans on personal
6
+ // keys) mutate immediately; a denied caller (an agent editing itself) gets the
7
+ // structured 403 whose printed `Request approval:` command carries the exact
8
+ // payload that was attempted — running it files the proposal for a human to
9
+ // decide. There is no separate "propose" verb.
10
+ export function registerProfileCommands(program) {
11
+ const profile = program.command('profile').description('Read and edit your profile');
12
+ profile
13
+ .command('show')
14
+ .description('Show your identity, org profile, Instructions and manager')
15
+ .action(async () => {
16
+ try {
17
+ const { client, orgId } = resolveCredentials();
18
+ const me = await client.getMe();
19
+ const result = { user: me };
20
+ try {
21
+ result.org_profile = await client.getMemberProfile(orgId, me.id);
22
+ }
23
+ catch {
24
+ /* display-only enrichment */
25
+ }
26
+ if (me.type === 'agent') {
27
+ try {
28
+ result.instructions = await client.getAgentInstructions(orgId, me.id);
29
+ }
30
+ catch {
31
+ /* not readable */
32
+ }
33
+ try {
34
+ result.manager = await client.getAgentManager(orgId, me.id);
35
+ }
36
+ catch {
37
+ /* not readable */
38
+ }
39
+ }
40
+ printJson(result);
41
+ }
42
+ catch (err) {
43
+ printError(err);
44
+ }
45
+ });
46
+ profile
47
+ .command('set')
48
+ .description('Edit your profile (title/about, Instructions, display name). If you lack permission, the error prints a ready-made approval request carrying exactly what you tried to write.')
49
+ .option('--title <text>', 'Org-profile title')
50
+ .option('--about <text>', 'Org-profile about/description')
51
+ .option('--instructions <text>', 'Full replacement Instructions')
52
+ .option('--instructions-file <path>', 'Read replacement Instructions from a file')
53
+ .option('--display-name <name>', 'Display name')
54
+ .action(async (opts) => {
55
+ if (opts.instructions !== undefined && opts.instructionsFile !== undefined) {
56
+ printError(new Error('--instructions and --instructions-file are mutually exclusive'));
57
+ }
58
+ const wantsProfile = opts.title !== undefined || opts.about !== undefined;
59
+ const instructionsBody = opts.instructions ??
60
+ (opts.instructionsFile ? readFileSync(opts.instructionsFile, 'utf8') : undefined);
61
+ if (!wantsProfile && instructionsBody === undefined && opts.displayName === undefined) {
62
+ printError(new Error('Nothing to change. Provide --title/--about, --instructions(-file), or --display-name.'));
63
+ }
64
+ const { client, orgId } = resolveCredentials();
65
+ const me = await client.getMe().catch((err) => printError(err));
66
+ const applied = {};
67
+ // Field groups apply independently, in order; the first denial exits
68
+ // with the prefilled proposal command for THAT group.
69
+ if (wantsProfile) {
70
+ // Read-modify-write like any client: a missing profile row is a
71
+ // successful empty read at version 0. Unspecified fields keep their
72
+ // current value so the write (and any resulting proposal) is always
73
+ // the full final state.
74
+ const current = await client.getMemberProfile(orgId, me.id).catch((err) => printError(err));
75
+ const body = {
76
+ title: opts.title ?? current.title,
77
+ description: opts.about ?? current.description,
78
+ expected_version: current.profile_version,
79
+ };
80
+ try {
81
+ applied.org_profile = await client.updateMemberProfile(orgId, me.id, body);
82
+ }
83
+ catch (err) {
84
+ printError(err, body);
85
+ }
86
+ }
87
+ if (instructionsBody !== undefined) {
88
+ const current = await client
89
+ .getAgentInstructions(orgId, me.id)
90
+ .catch((err) => printError(err));
91
+ const body = { instructions: instructionsBody, expected_version: current.version };
92
+ try {
93
+ applied.instructions = await client.updateAgentInstructions(orgId, me.id, body);
94
+ }
95
+ catch (err) {
96
+ printError(err, body);
97
+ }
98
+ }
99
+ if (opts.displayName !== undefined) {
100
+ try {
101
+ applied.user = await client.updateMe({ display_name: opts.displayName });
102
+ }
103
+ catch (err) {
104
+ // The identity proposal needs the CAS base (name at filing time),
105
+ // which the direct PATCH body does not carry — prefill it here.
106
+ printError(err, {
107
+ display_name: opts.displayName,
108
+ previous_display_name: me.display_name,
109
+ });
110
+ }
111
+ }
112
+ printJson(applied);
113
+ });
114
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"projects.d.ts","sourceRoot":"","sources":["../../src/commands/projects.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAKpC,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,OAAO,QA8FvD"}
1
+ {"version":3,"file":"projects.d.ts","sourceRoot":"","sources":["../../src/commands/projects.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAQpC,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,OAAO,QAyOvD"}
@@ -1,5 +1,6 @@
1
1
  import { resolveCredentials } from '../lib/client.js';
2
2
  import { printJson, printError, stripPrllScheme } from '../lib/output.js';
3
+ const VISIBILITY_HELP = 'public (any org member self-joins), restricted (join by approved request), or private (invite only); reading requires roster membership, with org owners/admins implicit';
3
4
  export function registerProjectCommands(program) {
4
5
  const projects = program.command('projects').description('Manage projects');
5
6
  projects
@@ -35,7 +36,7 @@ export function registerProjectCommands(program) {
35
36
  .requiredOption('--name <name>', 'Project name')
36
37
  .requiredOption('--key <key>', 'Project key')
37
38
  .option('--description <text>', 'Project description')
38
- .option('--visibility <visibility>', 'public (whole org) or private (roster only)')
39
+ .option('--visibility <visibility>', VISIBILITY_HELP)
39
40
  .option('--color <hex>', 'Project color (hex)')
40
41
  .action(async (opts) => {
41
42
  try {
@@ -60,7 +61,7 @@ export function registerProjectCommands(program) {
60
61
  .argument('<projectId>', 'Project ID')
61
62
  .option('--name <name>', 'Project name')
62
63
  .option('--description <text>', 'Project description')
63
- .option('--visibility <visibility>', 'public (whole org) or private (roster only)')
64
+ .option('--visibility <visibility>', VISIBILITY_HELP)
64
65
  .option('--color <hex>', 'Project color (hex)')
65
66
  .option('--status <status>', 'Project status')
66
67
  .action(async (projectId, opts) => {
@@ -102,4 +103,133 @@ export function registerProjectCommands(program) {
102
103
  printError(err);
103
104
  }
104
105
  });
106
+ projects
107
+ .command('library')
108
+ .description('Browse joinable projects (public + restricted) with your admission state. If task creation fails with no available projects, start here.')
109
+ .action(async () => {
110
+ try {
111
+ const { client, orgId } = resolveCredentials();
112
+ const result = await client.getProjectLibrary(orgId);
113
+ printJson(result);
114
+ }
115
+ catch (err) {
116
+ printError(err);
117
+ }
118
+ });
119
+ projects
120
+ .command('join')
121
+ .description('Join a public project immediately (restricted projects need `projects request-join`)')
122
+ .argument('<projectId>', 'Project ID')
123
+ .action(async (projectId) => {
124
+ try {
125
+ const { client, orgId } = resolveCredentials();
126
+ const result = await client.joinProject(orgId, stripPrllScheme(projectId));
127
+ printJson(result);
128
+ }
129
+ catch (err) {
130
+ printError(err);
131
+ }
132
+ });
133
+ projects
134
+ .command('request-join')
135
+ .description('File a join request for a restricted project; a project manager approves it')
136
+ .argument('<projectId>', 'Project ID')
137
+ .option('--cancel', 'Withdraw your pending join request instead')
138
+ .action(async (projectId, opts) => {
139
+ try {
140
+ const { client, orgId } = resolveCredentials();
141
+ const id = stripPrllScheme(projectId);
142
+ if (opts.cancel) {
143
+ await client.cancelProjectJoinRequest(orgId, id);
144
+ printJson({ cancelled: id });
145
+ return;
146
+ }
147
+ const result = await client.createProjectJoinRequest(orgId, id);
148
+ printJson(result);
149
+ }
150
+ catch (err) {
151
+ printError(err);
152
+ }
153
+ });
154
+ projects
155
+ .command('join-requests')
156
+ .description('List pending join requests for a project (manager standing required)')
157
+ .argument('<projectId>', 'Project ID')
158
+ .action(async (projectId) => {
159
+ try {
160
+ const { client, orgId } = resolveCredentials();
161
+ const result = await client.listProjectJoinRequests(orgId, stripPrllScheme(projectId));
162
+ printJson(result);
163
+ }
164
+ catch (err) {
165
+ printError(err);
166
+ }
167
+ });
168
+ const members = projects.command('members').description('Manage a project roster');
169
+ members
170
+ .command('list')
171
+ .description('List roster members (readable by anyone who can read the project)')
172
+ .argument('<projectId>', 'Project ID')
173
+ .action(async (projectId) => {
174
+ try {
175
+ const { client, orgId } = resolveCredentials();
176
+ const result = await client.getProjectMembers(orgId, stripPrllScheme(projectId));
177
+ printJson(result);
178
+ }
179
+ catch (err) {
180
+ printError(err);
181
+ }
182
+ });
183
+ members
184
+ .command('add')
185
+ .description('Add a user or team to the roster (manager standing required)')
186
+ .argument('<projectId>', 'Project ID')
187
+ .argument('<subject>', 'User ID (usr_...) or team ID (team_...)')
188
+ .option('--role <role>', 'member or manager', 'member')
189
+ .action(async (projectId, subject, opts) => {
190
+ try {
191
+ const { client, orgId } = resolveCredentials();
192
+ const result = await client.addProjectMember(orgId, stripPrllScheme(projectId), {
193
+ subject: stripPrllScheme(subject),
194
+ role: opts.role,
195
+ });
196
+ printJson(result);
197
+ }
198
+ catch (err) {
199
+ printError(err);
200
+ }
201
+ });
202
+ members
203
+ .command('remove')
204
+ .description('Remove a roster entry (manager standing required)')
205
+ .argument('<projectId>', 'Project ID')
206
+ .argument('<subject>', 'User ID or team ID')
207
+ .action(async (projectId, subject) => {
208
+ try {
209
+ const { client, orgId } = resolveCredentials();
210
+ const id = stripPrllScheme(projectId);
211
+ const sub = stripPrllScheme(subject);
212
+ await client.removeProjectMember(orgId, id, sub);
213
+ printJson({ removed: sub, project: id });
214
+ }
215
+ catch (err) {
216
+ printError(err);
217
+ }
218
+ });
219
+ members
220
+ .command('set-role')
221
+ .description('Change a roster entry role (manager standing required)')
222
+ .argument('<projectId>', 'Project ID')
223
+ .argument('<subject>', 'User ID or team ID')
224
+ .requiredOption('--role <role>', 'member or manager')
225
+ .action(async (projectId, subject, opts) => {
226
+ try {
227
+ const { client, orgId } = resolveCredentials();
228
+ const result = await client.updateProjectMember(orgId, stripPrllScheme(projectId), stripPrllScheme(subject), { role: opts.role });
229
+ printJson(result);
230
+ }
231
+ catch (err) {
232
+ printError(err);
233
+ }
234
+ });
105
235
  }
@@ -4,8 +4,9 @@ import type { Command } from 'commander';
4
4
  * none. --strict forces none and cannot be combined with --from; non-message
5
5
  * runtime triggers are never accepted as message authority.
6
6
  */
7
- export declare function buildResolveRefsOptions(explicitFrom: string | undefined, triggerMessageId: string | undefined, strict?: boolean): {
8
- sourceMessageId: string;
7
+ export declare function buildResolveRefsOptions(explicitFrom: string | undefined, triggerMessageId: string | undefined, strict?: boolean, full?: boolean): {
8
+ sourceMessageId?: string;
9
+ full?: boolean;
9
10
  } | undefined;
10
11
  /** Build the SDK ref-graph params from the positional URI + CLI flags. */
11
12
  export declare function buildRefGraphParams(uri: string, opts: {
@@ -1 +1 @@
1
- {"version":3,"file":"refs.d.ts","sourceRoot":"","sources":["../../src/commands/refs.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAUzC;;;;GAIG;AACH,wBAAgB,uBAAuB,CACrC,YAAY,EAAE,MAAM,GAAG,SAAS,EAChC,gBAAgB,EAAE,MAAM,GAAG,SAAS,EACpC,MAAM,UAAQ,GACb;IAAE,eAAe,EAAE,MAAM,CAAA;CAAE,GAAG,SAAS,CAqBzC;AAED,0EAA0E;AAC1E,wBAAgB,mBAAmB,CACjC,GAAG,EAAE,MAAM,EACX,IAAI,EAAE;IAAE,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,GACvB;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,CAOjC;AAED,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,OAAO,QAsEnD"}
1
+ {"version":3,"file":"refs.d.ts","sourceRoot":"","sources":["../../src/commands/refs.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAUzC;;;;GAIG;AACH,wBAAgB,uBAAuB,CACrC,YAAY,EAAE,MAAM,GAAG,SAAS,EAChC,gBAAgB,EAAE,MAAM,GAAG,SAAS,EACpC,MAAM,UAAQ,EACd,IAAI,UAAQ,GACX;IAAE,eAAe,CAAC,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,OAAO,CAAA;CAAE,GAAG,SAAS,CAsB1D;AAED,0EAA0E;AAC1E,wBAAgB,mBAAmB,CACjC,GAAG,EAAE,MAAM,EACX,IAAI,EAAE;IAAE,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,GACvB;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,CAOjC;AAED,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,OAAO,QA0EnD"}
@@ -5,28 +5,30 @@ import { ensurePrllScheme, parsePositiveInt, printError, printJson, stripPrllSch
5
5
  * none. --strict forces none and cannot be combined with --from; non-message
6
6
  * runtime triggers are never accepted as message authority.
7
7
  */
8
- export function buildResolveRefsOptions(explicitFrom, triggerMessageId, strict = false) {
8
+ export function buildResolveRefsOptions(explicitFrom, triggerMessageId, strict = false, full = false) {
9
9
  const explicit = explicitFrom?.trim();
10
10
  if (strict && explicit) {
11
11
  throw new Error('--strict cannot be combined with --from');
12
12
  }
13
13
  if (strict)
14
- return undefined;
14
+ return full ? { full: true } : undefined;
15
15
  if (explicit) {
16
16
  const sourceMessageId = stripPrllScheme(explicit);
17
17
  if (!sourceMessageId.startsWith('msg_')) {
18
18
  throw new Error('--from must be a message ID (msg_...) or prll://msg_... URI');
19
19
  }
20
- return { sourceMessageId };
20
+ return full ? { sourceMessageId, full: true } : { sourceMessageId };
21
21
  }
22
22
  // Typed task dispatches can carry a task ID in trigger_message_id. Only a
23
23
  // real message can grant the server's cross-chat reference access; omitting
24
24
  // non-message triggers preserves the normal membership-filtered resolve.
25
25
  const trigger = triggerMessageId?.trim();
26
26
  if (!trigger)
27
- return undefined;
27
+ return full ? { full: true } : undefined;
28
28
  const sourceMessageId = stripPrllScheme(trigger);
29
- return sourceMessageId.startsWith('msg_') ? { sourceMessageId } : undefined;
29
+ if (!sourceMessageId.startsWith('msg_'))
30
+ return full ? { full: true } : undefined;
31
+ return full ? { sourceMessageId, full: true } : { sourceMessageId };
30
32
  }
31
33
  /** Build the SDK ref-graph params from the positional URI + CLI flags. */
32
34
  export function buildRefGraphParams(uri, opts) {
@@ -45,11 +47,15 @@ export function registerRefCommands(program) {
45
47
  .description('Batch resolve prll:// URIs to entity metadata')
46
48
  .option('--from <messageId>', 'Forwarding message for cross-chat access (defaults to trigger)')
47
49
  .option('--strict', 'Ignore runtime trigger and enforce direct membership checks')
50
+ .option('--full', 'Return complete text for authorized message references')
48
51
  .action(async (uris, opts) => {
49
52
  try {
50
53
  const { client, orgId } = resolveCredentials();
51
54
  const runtime = resolveRuntimeContext();
52
- const result = await client.resolveRefs(orgId, uris, buildResolveRefsOptions(opts.from, runtime.triggerMessageId, opts.strict));
55
+ // Normalize schemeless args (msg_x, cht_x#range=…) like `refs graph`
56
+ // does: the server drops non-prll URIs without an error, so a raw id
57
+ // would silently resolve to nothing.
58
+ const result = await client.resolveRefs(orgId, uris.map(ensurePrllScheme), buildResolveRefsOptions(opts.from, runtime.triggerMessageId, opts.strict, opts.full));
53
59
  printJson(result);
54
60
  }
55
61
  catch (err) {
@@ -1 +1 @@
1
- {"version":3,"file":"tasks.d.ts","sourceRoot":"","sources":["../../src/commands/tasks.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAqB,iBAAiB,EAAE,MAAM,aAAa,CAAC;AACxE,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAgEpC,wBAAgB,mBAAmB,CAAC,IAAI,EAAE;IACxC,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;;;;;;;;;;;GAaA;AAED,wBAAgB,mBAAmB,CAAC,IAAI,EAAE;IACxC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;;YAS0B,iBAAiB,CAAC,QAAQ,CAAC;cACvB,iBAAiB,CAAC,UAAU,CAAC;;;;;;;;GAS3D;AAED,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,OAAO,QA2WpD"}
1
+ {"version":3,"file":"tasks.d.ts","sourceRoot":"","sources":["../../src/commands/tasks.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAqB,iBAAiB,EAAE,MAAM,aAAa,CAAC;AACxE,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAiEpC,wBAAgB,mBAAmB,CAAC,IAAI,EAAE;IACxC,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;;;;;;;;;;;GAaA;AAED,wBAAgB,mBAAmB,CAAC,IAAI,EAAE;IACxC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;;YAS0B,iBAAiB,CAAC,QAAQ,CAAC;cACvB,iBAAiB,CAAC,UAAU,CAAC;;;;;;;;GAS3D;AAED,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,OAAO,QAkXpD"}
@@ -1,4 +1,5 @@
1
1
  import { ApiError } from '@parall/sdk';
2
+ import { createHash } from 'node:crypto';
2
3
  import { resolveCredentials, resolveTypedDispatchBinding } from '../lib/client.js';
3
4
  import { printJson, printError, printRefHint, stripPrllScheme } from '../lib/output.js';
4
5
  function stripUndefined(obj) {
@@ -201,10 +202,18 @@ export function registerTaskCommands(program) {
201
202
  const binding = resolveTypedDispatchBinding(taskId);
202
203
  const req = { ...data };
203
204
  if (binding) {
204
- req.dispatch_lane = binding.lane;
205
+ if (binding.lane)
206
+ req.dispatch_lane = binding.lane;
205
207
  req.dispatch_event_id = binding.dispatchEventId;
206
- if (binding.effectKey)
207
- req.dispatch_effect_key = binding.effectKey;
208
+ if (binding.generation !== undefined)
209
+ req.dispatch_generation = binding.generation;
210
+ if (binding.effectKey) {
211
+ req.dispatch_effect_key = binding.lane
212
+ ? binding.effectKey
213
+ : // Converged: content-address the key so a same-payload retry
214
+ // replays idempotently and a different update mints a new key.
215
+ `${binding.effectKey}:${createHash('sha256').update(JSON.stringify(data)).digest('hex').slice(0, 12)}`;
216
+ }
208
217
  }
209
218
  const result = await client.updateTask(orgId, taskId, req);
210
219
  if (binding?.effectKey) {
@@ -0,0 +1,3 @@
1
+ import { Command } from 'commander';
2
+ export declare function registerTeamCommands(program: Command): void;
3
+ //# sourceMappingURL=teams.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"teams.d.ts","sourceRoot":"","sources":["../../src/commands/teams.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAOpC,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,OAAO,QAiGpD"}
@@ -0,0 +1,102 @@
1
+ import { resolveCredentials } from '../lib/client.js';
2
+ import { printJson, printError, stripPrllScheme } from '../lib/output.js';
3
+ // Teams are pure permission rosters (ACL subjects for wiki/project ACLs);
4
+ // server-side authorization decides who may mutate them — the CLI only
5
+ // exposes the same surface the web Teams settings use.
6
+ export function registerTeamCommands(program) {
7
+ const teams = program.command('teams').description('Manage permission teams');
8
+ teams
9
+ .command('list')
10
+ .description('List teams in the organization')
11
+ .action(async () => {
12
+ try {
13
+ const { client, orgId } = resolveCredentials();
14
+ const result = await client.getTeams(orgId);
15
+ printJson(result);
16
+ }
17
+ catch (err) {
18
+ printError(err);
19
+ }
20
+ });
21
+ teams
22
+ .command('get')
23
+ .description('Get a team by ID')
24
+ .argument('<teamId>', 'Team ID')
25
+ .action(async (teamId) => {
26
+ try {
27
+ const { client, orgId } = resolveCredentials();
28
+ const result = await client.getTeam(orgId, stripPrllScheme(teamId));
29
+ printJson(result);
30
+ }
31
+ catch (err) {
32
+ printError(err);
33
+ }
34
+ });
35
+ const members = teams.command('members').description('Manage a team roster');
36
+ members
37
+ .command('list')
38
+ .description('List team members')
39
+ .argument('<teamId>', 'Team ID')
40
+ .action(async (teamId) => {
41
+ try {
42
+ const { client, orgId } = resolveCredentials();
43
+ const result = await client.getTeamMembers(orgId, stripPrllScheme(teamId));
44
+ printJson(result);
45
+ }
46
+ catch (err) {
47
+ printError(err);
48
+ }
49
+ });
50
+ members
51
+ .command('add')
52
+ .description('Add a member (team manager or org admin)')
53
+ .argument('<teamId>', 'Team ID')
54
+ .argument('<userId>', 'User ID (usr_...)')
55
+ .option('--role <role>', 'member or manager', 'member')
56
+ .action(async (teamId, userId, opts) => {
57
+ try {
58
+ const { client, orgId } = resolveCredentials();
59
+ await client.addTeamMember(orgId, stripPrllScheme(teamId), {
60
+ user_id: stripPrllScheme(userId),
61
+ role: opts.role,
62
+ });
63
+ printJson({ added: stripPrllScheme(userId), team: stripPrllScheme(teamId) });
64
+ }
65
+ catch (err) {
66
+ printError(err);
67
+ }
68
+ });
69
+ members
70
+ .command('remove')
71
+ .description('Remove a member (team manager or org admin)')
72
+ .argument('<teamId>', 'Team ID')
73
+ .argument('<userId>', 'User ID')
74
+ .action(async (teamId, userId) => {
75
+ try {
76
+ const { client, orgId } = resolveCredentials();
77
+ await client.removeTeamMember(orgId, stripPrllScheme(teamId), stripPrllScheme(userId));
78
+ printJson({ removed: stripPrllScheme(userId), team: stripPrllScheme(teamId) });
79
+ }
80
+ catch (err) {
81
+ printError(err);
82
+ }
83
+ });
84
+ members
85
+ .command('set-role')
86
+ .description('Change a member role (org admin; managers manage plain members only)')
87
+ .argument('<teamId>', 'Team ID')
88
+ .argument('<userId>', 'User ID')
89
+ .requiredOption('--role <role>', 'member or manager')
90
+ .action(async (teamId, userId, opts) => {
91
+ try {
92
+ const { client, orgId } = resolveCredentials();
93
+ await client.updateTeamMember(orgId, stripPrllScheme(teamId), stripPrllScheme(userId), {
94
+ role: opts.role,
95
+ });
96
+ printJson({ updated: stripPrllScheme(userId), role: opts.role });
97
+ }
98
+ catch (err) {
99
+ printError(err);
100
+ }
101
+ });
102
+ }
@@ -7,9 +7,10 @@ import { Command } from 'commander';
7
7
  * performs the vendor call in-process.
8
8
  *
9
9
  * Verb surface = the vendor's in-scope interface aggregated: postText
10
- * (send), fetchContactsList + getBriefInfo + getChatroomInfo (contacts),
11
- * getProfile (profile), checkOnline (status). Every verb renders
12
- * agent-readable text by default and `--json` for machine output.
10
+ * (send), platform-retained context (history), fetchContactsList +
11
+ * getBriefInfo + getChatroomInfo (contacts), getProfile (profile),
12
+ * checkOnline (status). History always emits its stable page JSON; the other
13
+ * verbs render agent-readable text by default and accept `--json`.
13
14
  *
14
15
  * INTERNAL research preview: the capability is flag-gated to the internal
15
16
  * org. Design: docs/engineering-design/wechat-channel-design.md §3, §4.
@@ -1 +1 @@
1
- {"version":3,"file":"wechat.d.ts","sourceRoot":"","sources":["../../src/commands/wechat.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAKpC;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,OAAO,QAwItD"}
1
+ {"version":3,"file":"wechat.d.ts","sourceRoot":"","sources":["../../src/commands/wechat.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAKpC;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,OAAO,QA0KtD"}
@@ -9,9 +9,10 @@ import { resolveMessageText, TEXT_FILE_OPTION_DESC, TEXT_OPTION_DESC } from '../
9
9
  * performs the vendor call in-process.
10
10
  *
11
11
  * Verb surface = the vendor's in-scope interface aggregated: postText
12
- * (send), fetchContactsList + getBriefInfo + getChatroomInfo (contacts),
13
- * getProfile (profile), checkOnline (status). Every verb renders
14
- * agent-readable text by default and `--json` for machine output.
12
+ * (send), platform-retained context (history), fetchContactsList +
13
+ * getBriefInfo + getChatroomInfo (contacts), getProfile (profile),
14
+ * checkOnline (status). History always emits its stable page JSON; the other
15
+ * verbs render agent-readable text by default and accept `--json`.
15
16
  *
16
17
  * INTERNAL research preview: the capability is flag-gated to the internal
17
18
  * org. Design: docs/engineering-design/wechat-channel-design.md §3, §4.
@@ -52,6 +53,33 @@ export function registerWechatCommands(program) {
52
53
  printError(err);
53
54
  }
54
55
  });
56
+ wechat
57
+ .command('history')
58
+ .description('Read platform-retained context for a WeChat DM or group')
59
+ .requiredOption('--conversation <id>', 'Vendor-native conversation id (a friend wxid, or a room id ending in @chatroom)')
60
+ .option('--before <messageId>', 'Read messages strictly older than this external message id')
61
+ .option('--since <time>', 'Inclusive lower bound: RFC3339 timestamp or YYYY-MM-DD')
62
+ .option('--limit <n>', 'Page size (default 20, max 100)')
63
+ .action(async (opts) => {
64
+ try {
65
+ let limit;
66
+ if (opts.limit !== undefined) {
67
+ limit = Number(opts.limit);
68
+ if (!Number.isInteger(limit) || limit < 1 || limit > 100) {
69
+ throw new Error('--limit must be an integer between 1 and 100');
70
+ }
71
+ }
72
+ const { client, orgId } = resolveCredentials();
73
+ printJson(await client.wechatHistory(orgId, opts.conversation, {
74
+ ...(opts.before ? { before: opts.before } : {}),
75
+ ...(opts.since ? { since: opts.since } : {}),
76
+ ...(limit !== undefined ? { limit } : {}),
77
+ }));
78
+ }
79
+ catch (err) {
80
+ printError(err);
81
+ }
82
+ });
55
83
  wechat
56
84
  .command('contacts')
57
85
  .description('List the account address book with display names: friends, saved groups, followed official accounts')
package/dist/index.js CHANGED
@@ -9,6 +9,8 @@ import { registerMessageCommands } from './commands/messages.js';
9
9
  import { registerTaskCommands } from './commands/tasks.js';
10
10
  import { registerCommentCommands } from './commands/comments.js';
11
11
  import { registerProjectCommands } from './commands/projects.js';
12
+ import { registerProfileCommands } from './commands/profile.js';
13
+ import { registerTeamCommands } from './commands/teams.js';
12
14
  import { registerScheduleCommands } from './commands/schedules.js';
13
15
  import { registerExternalTriggerCommands } from './commands/external-triggers.js';
14
16
  import { registerUserCommands } from './commands/users.js';
@@ -22,6 +24,7 @@ import { registerMachineCommands } from './commands/machines.js';
22
24
  import { registerClipCommands } from './commands/clip.js';
23
25
  import { registerSlackCommands } from './commands/slack.js';
24
26
  import { registerWechatCommands } from './commands/wechat.js';
27
+ import { createUpdateNotifier } from './lib/update-check.js';
25
28
  const require = createRequire(import.meta.url);
26
29
  const pkg = require('../package.json');
27
30
  const program = new Command();
@@ -37,6 +40,8 @@ registerMessageCommands(program);
37
40
  registerTaskCommands(program);
38
41
  registerCommentCommands(program);
39
42
  registerProjectCommands(program);
43
+ registerProfileCommands(program);
44
+ registerTeamCommands(program);
40
45
  registerScheduleCommands(program);
41
46
  registerExternalTriggerCommands(program);
42
47
  registerUserCommands(program);
@@ -50,4 +55,10 @@ registerMachineCommands(program);
50
55
  registerClipCommands(program);
51
56
  registerSlackCommands(program);
52
57
  registerWechatCommands(program);
58
+ const updateNotifier = createUpdateNotifier(pkg.version);
59
+ program.hook('postAction', () => {
60
+ if (process.exitCode === undefined || process.exitCode === 0) {
61
+ updateNotifier.afterSuccessfulCommand();
62
+ }
63
+ });
53
64
  program.parse();