ticlawk 0.1.16-dev.3 → 0.1.16-dev.30
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/README.md +14 -2
- package/bin/ticlawk.mjs +207 -25
- package/package.json +1 -1
- package/src/adapters/ticlawk/api.mjs +232 -23
- package/src/adapters/ticlawk/credentials.mjs +41 -1
- package/src/adapters/ticlawk/index.mjs +196 -195
- package/src/adapters/ticlawk/wake-client.mjs +1 -1
- package/src/cli/agent-commands.mjs +607 -37
- package/src/core/agent-cli-handlers.mjs +449 -20
- package/src/core/agent-home.mjs +86 -10
- package/src/core/argv.mjs +11 -1
- package/src/core/http.mjs +126 -0
- package/src/core/runtime-env.mjs +7 -0
- package/src/core/runtime-support.mjs +101 -30
- package/src/migrate/write-initial-memory.mjs +5 -5
- package/src/runtimes/_shared/agent-handbook.mjs +45 -0
- package/src/runtimes/_shared/brand.mjs +2 -0
- package/src/runtimes/_shared/goal-task-protocol.mjs +50 -0
- package/src/runtimes/_shared/handbook/BASICS.md +27 -0
- package/src/runtimes/_shared/handbook/COLLABORATION.md +37 -0
- package/src/runtimes/_shared/handbook/COMMUNICATION.md +48 -0
- package/src/runtimes/_shared/handbook/DM_SCOPE.md +13 -0
- package/src/runtimes/_shared/handbook/GOAL_AUTHORITY.md +43 -0
- package/src/runtimes/_shared/handbook/GOAL_TASK_CORE.md +43 -0
- package/src/runtimes/_shared/handbook/GROUP_ADMIN_SCOPE.md +21 -0
- package/src/runtimes/_shared/handbook/GROUP_MEMBER_SCOPE.md +15 -0
- package/src/runtimes/_shared/handbook/SURFACES.md +41 -0
- package/src/runtimes/_shared/handbook/TASK_WORKER.md +14 -0
- package/src/runtimes/_shared/standing-prompt.mjs +111 -264
- package/src/runtimes/_shared/wake-prompt.mjs +261 -0
- package/src/runtimes/claude-code/index.mjs +30 -108
- package/src/runtimes/codex/index.mjs +114 -23
- package/src/runtimes/openclaw/index.mjs +16 -26
- package/src/runtimes/opencode/index.mjs +42 -36
- package/src/runtimes/opencode/session.mjs +5 -4
- package/src/runtimes/pi/index.mjs +39 -31
- package/src/runtimes/pi/session.mjs +5 -2
- package/src/adapters/ticlawk/cards.mjs +0 -149
- package/src/core/media/outbound.mjs +0 -163
|
@@ -10,14 +10,17 @@
|
|
|
10
10
|
* Commands:
|
|
11
11
|
* ticlawk message send --target <t> [--seen-up-to-seq N] [--reply-to <msg>]
|
|
12
12
|
* ticlawk message read --target <t> [--around <msg>] [--limit N]
|
|
13
|
+
* ticlawk task create --target <t> [--title <t>] [--assign-agent <id>]
|
|
13
14
|
* ticlawk task claim --message-id <id> [--lease-seconds N]
|
|
14
15
|
* ticlawk task update --task-id <id> --status <s>
|
|
15
|
-
* ticlawk task list [--target <t>]
|
|
16
|
+
* ticlawk task list [--target <t>] # group admins see the full task board
|
|
17
|
+
* ticlawk charter get/set --target <t>
|
|
16
18
|
* ticlawk group members --target <t>
|
|
17
19
|
* ticlawk server info [--refresh]
|
|
18
20
|
*
|
|
19
21
|
* `ticlawk message send` reads the message body from stdin so heredocs
|
|
20
|
-
* work cleanly
|
|
22
|
+
* work cleanly — quotes, backticks, and code blocks survive without
|
|
23
|
+
* shell-quoting gymnastics.
|
|
21
24
|
*/
|
|
22
25
|
|
|
23
26
|
import { readFileSync, statSync, writeFileSync } from 'node:fs';
|
|
@@ -40,6 +43,9 @@ function requireAgentEnv() {
|
|
|
40
43
|
agentId,
|
|
41
44
|
hostId: String(process.env.TICLAWK_RUNTIME_HOST_ID || '').trim() || null,
|
|
42
45
|
sessionId: String(process.env.TICLAWK_RUNTIME_SESSION_ID || '').trim() || null,
|
|
46
|
+
currentConversationId: String(process.env.TICLAWK_RUNTIME_CONVERSATION_ID || '').trim() || null,
|
|
47
|
+
currentMessageId: String(process.env.TICLAWK_RUNTIME_MESSAGE_ID || '').trim() || null,
|
|
48
|
+
currentTarget: String(process.env.TICLAWK_RUNTIME_TARGET || '').trim() || null,
|
|
43
49
|
};
|
|
44
50
|
}
|
|
45
51
|
|
|
@@ -50,6 +56,11 @@ function commonHeaders(env) {
|
|
|
50
56
|
};
|
|
51
57
|
if (env.hostId) headers['X-Ticlawk-Runtime-Host-Id'] = env.hostId;
|
|
52
58
|
if (env.sessionId) headers['X-Ticlawk-Runtime-Session-Id'] = env.sessionId;
|
|
59
|
+
if (env.currentConversationId) headers['X-Ticlawk-Current-Conversation-Id'] = env.currentConversationId;
|
|
60
|
+
if (env.currentMessageId) headers['X-Ticlawk-Current-Message-Id'] = env.currentMessageId;
|
|
61
|
+
if (env.currentTarget && /^[\x00-\x7F]*$/.test(env.currentTarget)) {
|
|
62
|
+
headers['X-Ticlawk-Current-Target'] = env.currentTarget;
|
|
63
|
+
}
|
|
53
64
|
return headers;
|
|
54
65
|
}
|
|
55
66
|
|
|
@@ -135,12 +146,46 @@ export async function runMessageSendCommand(args) {
|
|
|
135
146
|
console.error(' EOF');
|
|
136
147
|
return 2;
|
|
137
148
|
}
|
|
149
|
+
|
|
150
|
+
// --attach <path> may be passed multiple times. Each file is uploaded
|
|
151
|
+
// through the same daemon endpoint as `ticlawk attachment upload`, then
|
|
152
|
+
// its asset_id is linked to the message via media_asset_ids.
|
|
153
|
+
const attachArg = args.attach;
|
|
154
|
+
const attachPaths = Array.isArray(attachArg)
|
|
155
|
+
? attachArg
|
|
156
|
+
: attachArg
|
|
157
|
+
? [attachArg]
|
|
158
|
+
: [];
|
|
159
|
+
if (attachPaths.length > 10) {
|
|
160
|
+
console.error('at most 10 --attach files per message');
|
|
161
|
+
return 2;
|
|
162
|
+
}
|
|
163
|
+
const mediaAssetIds = [];
|
|
164
|
+
for (const filePath of attachPaths) {
|
|
165
|
+
const upload = await uploadFileViaDaemon(env, String(filePath));
|
|
166
|
+
if (!upload.ok) {
|
|
167
|
+
console.error(`attachment upload failed for ${filePath}: ${upload.error}`);
|
|
168
|
+
return 1;
|
|
169
|
+
}
|
|
170
|
+
mediaAssetIds.push(upload.assetId);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// Optional --kind <s> classifies the message via metadata.kind. The
|
|
174
|
+
// canonical user-facing value is 'briefing'. Anything else
|
|
175
|
+
// is passed through as-is so we don't have to update this list to add
|
|
176
|
+
// new conventions.
|
|
177
|
+
const kind = getArg(args, 'kind');
|
|
178
|
+
const metadata = kind ? { kind } : undefined;
|
|
179
|
+
|
|
138
180
|
const body = {
|
|
139
181
|
target,
|
|
140
182
|
conversation_id: conversationId,
|
|
141
183
|
text: text.replace(/\n+$/, ''),
|
|
142
184
|
seen_up_to_seq: getNumberArg(args, 'seen-up-to-seq'),
|
|
143
185
|
reply_to_message_id: getArg(args, 'reply-to'),
|
|
186
|
+
allow_cross_target: Boolean(args['allow-cross-target']),
|
|
187
|
+
media_asset_ids: mediaAssetIds.length > 0 ? mediaAssetIds : undefined,
|
|
188
|
+
metadata,
|
|
144
189
|
};
|
|
145
190
|
const res = await daemonRequest({
|
|
146
191
|
method: 'POST',
|
|
@@ -191,7 +236,7 @@ export async function runTaskCreateCommand(args) {
|
|
|
191
236
|
if (!text || !text.trim()) {
|
|
192
237
|
console.error('task body is required on stdin');
|
|
193
238
|
console.error('Example:');
|
|
194
|
-
console.error(" ticlawk task create --target \"#frontend\" --title \"fix login\" <<'EOF'");
|
|
239
|
+
console.error(" ticlawk task create --target \"#frontend\" --title \"fix login\" --assign-agent <agent-id> <<'EOF'");
|
|
195
240
|
console.error(' Detailed description goes here.');
|
|
196
241
|
console.error(' EOF');
|
|
197
242
|
return 2;
|
|
@@ -205,6 +250,7 @@ export async function runTaskCreateCommand(args) {
|
|
|
205
250
|
conversation_id: conversationId,
|
|
206
251
|
text: text.replace(/\n+$/, ''),
|
|
207
252
|
title: getArg(args, 'title'),
|
|
253
|
+
assign_agent_id: getArg(args, 'assign-agent') || getArg(args, 'assignee-agent'),
|
|
208
254
|
},
|
|
209
255
|
});
|
|
210
256
|
printJson(res.body);
|
|
@@ -544,13 +590,12 @@ export async function runProfileUpdateCommand(args) {
|
|
|
544
590
|
}
|
|
545
591
|
let avatarUrl = null;
|
|
546
592
|
if (avatarFile) {
|
|
547
|
-
|
|
548
|
-
const upload = await uploadFileViaDaemon(env, avatarFile);
|
|
593
|
+
const upload = await uploadAvatarViaDaemon(env, avatarFile);
|
|
549
594
|
if (!upload.ok) {
|
|
550
595
|
console.error(`avatar upload failed: ${upload.error}`);
|
|
551
596
|
return 1;
|
|
552
597
|
}
|
|
553
|
-
avatarUrl = upload.
|
|
598
|
+
avatarUrl = upload.url;
|
|
554
599
|
}
|
|
555
600
|
const res = await daemonRequest({
|
|
556
601
|
method: 'POST',
|
|
@@ -566,6 +611,34 @@ export async function runProfileUpdateCommand(args) {
|
|
|
566
611
|
return exitFromStatus(res.statusCode);
|
|
567
612
|
}
|
|
568
613
|
|
|
614
|
+
async function uploadAvatarViaDaemon(env, filePath) {
|
|
615
|
+
let stat;
|
|
616
|
+
try { stat = statSync(filePath); } catch (err) {
|
|
617
|
+
return { ok: false, error: `cannot stat ${filePath}: ${err.message}` };
|
|
618
|
+
}
|
|
619
|
+
if (!stat.isFile()) return { ok: false, error: `${filePath} is not a regular file` };
|
|
620
|
+
const contentType = inferContentType(filePath);
|
|
621
|
+
if (!contentType.startsWith('image/')) {
|
|
622
|
+
return { ok: false, error: `avatar must be an image (got content_type ${contentType})` };
|
|
623
|
+
}
|
|
624
|
+
const data = readFileSync(filePath);
|
|
625
|
+
const res = await daemonRequest({
|
|
626
|
+
method: 'POST',
|
|
627
|
+
path: '/agent/profile/avatar',
|
|
628
|
+
headers: commonHeaders(env),
|
|
629
|
+
body: {
|
|
630
|
+
filename: basename(filePath),
|
|
631
|
+
content_type: contentType,
|
|
632
|
+
data_base64: data.toString('base64'),
|
|
633
|
+
},
|
|
634
|
+
});
|
|
635
|
+
if (res.statusCode < 200 || res.statusCode >= 300) {
|
|
636
|
+
return { ok: false, error: res.body?.error || `HTTP ${res.statusCode}` };
|
|
637
|
+
}
|
|
638
|
+
if (!res.body?.url) return { ok: false, error: 'avatar upload returned no url' };
|
|
639
|
+
return { ok: true, url: res.body.url };
|
|
640
|
+
}
|
|
641
|
+
|
|
569
642
|
async function uploadFileViaDaemon(env, filePath) {
|
|
570
643
|
let stat;
|
|
571
644
|
try { stat = statSync(filePath); } catch (err) {
|
|
@@ -587,7 +660,11 @@ async function uploadFileViaDaemon(env, filePath) {
|
|
|
587
660
|
if (res.statusCode < 200 || res.statusCode >= 300) {
|
|
588
661
|
return { ok: false, error: res.body?.error || `HTTP ${res.statusCode}` };
|
|
589
662
|
}
|
|
590
|
-
|
|
663
|
+
const asset = res.body?.data;
|
|
664
|
+
if (!asset?.asset_id || !asset?.url) {
|
|
665
|
+
return { ok: false, error: 'attachment upload returned no asset' };
|
|
666
|
+
}
|
|
667
|
+
return { ok: true, assetId: asset.asset_id, url: asset.url, expiresAt: asset.expires_at };
|
|
591
668
|
}
|
|
592
669
|
|
|
593
670
|
function inferContentType(filePath) {
|
|
@@ -597,6 +674,11 @@ function inferContentType(filePath) {
|
|
|
597
674
|
case '.jpg': case '.jpeg': return 'image/jpeg';
|
|
598
675
|
case '.gif': return 'image/gif';
|
|
599
676
|
case '.webp': return 'image/webp';
|
|
677
|
+
case '.mp4': return 'video/mp4';
|
|
678
|
+
case '.mov': return 'video/quicktime';
|
|
679
|
+
case '.m4v': return 'video/x-m4v';
|
|
680
|
+
case '.webm': return 'video/webm';
|
|
681
|
+
case '.html': case '.htm': return 'text/html';
|
|
600
682
|
case '.pdf': return 'application/pdf';
|
|
601
683
|
case '.txt': return 'text/plain';
|
|
602
684
|
case '.md': return 'text/markdown';
|
|
@@ -605,18 +687,6 @@ function inferContentType(filePath) {
|
|
|
605
687
|
}
|
|
606
688
|
}
|
|
607
689
|
|
|
608
|
-
export async function runAttachmentUploadCommand(args) {
|
|
609
|
-
const env = requireAgentEnv();
|
|
610
|
-
const file = args._?.[2];
|
|
611
|
-
if (!file) {
|
|
612
|
-
console.error('usage: ticlawk attachment upload <file>');
|
|
613
|
-
return 2;
|
|
614
|
-
}
|
|
615
|
-
const upload = await uploadFileViaDaemon(env, file);
|
|
616
|
-
printJson(upload);
|
|
617
|
-
return upload.ok ? 0 : 1;
|
|
618
|
-
}
|
|
619
|
-
|
|
620
690
|
export async function runAttachmentViewCommand(args) {
|
|
621
691
|
const env = requireAgentEnv();
|
|
622
692
|
const assetId = args._?.[2];
|
|
@@ -755,6 +825,410 @@ export async function runGroupMembersCommand(args) {
|
|
|
755
825
|
return exitFromStatus(res.statusCode);
|
|
756
826
|
}
|
|
757
827
|
|
|
828
|
+
export async function runWorkstreamCreateCommand(args) {
|
|
829
|
+
const env = requireAgentEnv();
|
|
830
|
+
const name = getArg(args, 'name');
|
|
831
|
+
if (!name) { console.error('--name is required'); return 2; }
|
|
832
|
+
const description = getArg(args, 'description');
|
|
833
|
+
const memberArgs = args.member;
|
|
834
|
+
const memberAgentIds = Array.isArray(memberArgs)
|
|
835
|
+
? memberArgs
|
|
836
|
+
: memberArgs ? [memberArgs] : [];
|
|
837
|
+
// Optional charter from stdin when --charter is supplied with no value
|
|
838
|
+
let charter = null;
|
|
839
|
+
if (args.charter === true) {
|
|
840
|
+
charter = await readStdin();
|
|
841
|
+
} else if (typeof args.charter === 'string') {
|
|
842
|
+
charter = args.charter;
|
|
843
|
+
}
|
|
844
|
+
const res = await daemonRequest({
|
|
845
|
+
method: 'POST',
|
|
846
|
+
path: '/agent/workstream/create',
|
|
847
|
+
headers: commonHeaders(env),
|
|
848
|
+
body: {
|
|
849
|
+
name,
|
|
850
|
+
description,
|
|
851
|
+
charter,
|
|
852
|
+
member_agent_ids: memberAgentIds.map((s) => String(s).trim()).filter(Boolean),
|
|
853
|
+
},
|
|
854
|
+
});
|
|
855
|
+
printJson(res.body);
|
|
856
|
+
return exitFromStatus(res.statusCode);
|
|
857
|
+
}
|
|
858
|
+
|
|
859
|
+
export async function runWorkstreamDeleteCommand(args) {
|
|
860
|
+
const env = requireAgentEnv();
|
|
861
|
+
const target = getArg(args, 'target');
|
|
862
|
+
const conversationId = getArg(args, 'conversation-id');
|
|
863
|
+
if (!target && !conversationId) {
|
|
864
|
+
console.error('--target or --conversation-id is required');
|
|
865
|
+
return 2;
|
|
866
|
+
}
|
|
867
|
+
const res = await daemonRequest({
|
|
868
|
+
method: 'POST',
|
|
869
|
+
path: '/agent/workstream/delete',
|
|
870
|
+
headers: commonHeaders(env),
|
|
871
|
+
body: { target, conversation_id: conversationId },
|
|
872
|
+
});
|
|
873
|
+
printJson(res.body);
|
|
874
|
+
return exitFromStatus(res.statusCode);
|
|
875
|
+
}
|
|
876
|
+
|
|
877
|
+
export async function runWorkstreamListCommand(args) {
|
|
878
|
+
const env = requireAgentEnv();
|
|
879
|
+
const res = await daemonRequest({
|
|
880
|
+
method: 'GET',
|
|
881
|
+
path: '/agent/workstream/list',
|
|
882
|
+
headers: commonHeaders(env),
|
|
883
|
+
});
|
|
884
|
+
printJson(res.body);
|
|
885
|
+
return exitFromStatus(res.statusCode);
|
|
886
|
+
}
|
|
887
|
+
|
|
888
|
+
export async function runAgentListCommand(args) {
|
|
889
|
+
const env = requireAgentEnv();
|
|
890
|
+
const res = await daemonRequest({
|
|
891
|
+
method: 'GET',
|
|
892
|
+
path: '/agent/agent/list',
|
|
893
|
+
headers: commonHeaders(env),
|
|
894
|
+
});
|
|
895
|
+
printJson(res.body);
|
|
896
|
+
return exitFromStatus(res.statusCode);
|
|
897
|
+
}
|
|
898
|
+
|
|
899
|
+
export async function runAgentCreateCommand(args) {
|
|
900
|
+
const env = requireAgentEnv();
|
|
901
|
+
const name = getArg(args, 'name');
|
|
902
|
+
const runtime = getArg(args, 'runtime');
|
|
903
|
+
if (!name) { console.error('--name is required'); return 2; }
|
|
904
|
+
if (!runtime) { console.error('--runtime is required'); return 2; }
|
|
905
|
+
const res = await daemonRequest({
|
|
906
|
+
method: 'POST',
|
|
907
|
+
path: '/agent/agent/create',
|
|
908
|
+
headers: commonHeaders(env),
|
|
909
|
+
body: {
|
|
910
|
+
name,
|
|
911
|
+
runtime,
|
|
912
|
+
description: getArg(args, 'description'),
|
|
913
|
+
display_name: getArg(args, 'display-name'),
|
|
914
|
+
model: getArg(args, 'model'),
|
|
915
|
+
},
|
|
916
|
+
});
|
|
917
|
+
printJson(res.body);
|
|
918
|
+
return exitFromStatus(res.statusCode);
|
|
919
|
+
}
|
|
920
|
+
|
|
921
|
+
export async function runAgentDeleteCommand(args) {
|
|
922
|
+
const env = requireAgentEnv();
|
|
923
|
+
const agentId = getArg(args, 'agent-id');
|
|
924
|
+
if (!agentId) { console.error('--agent-id is required'); return 2; }
|
|
925
|
+
const res = await daemonRequest({
|
|
926
|
+
method: 'POST',
|
|
927
|
+
path: '/agent/agent/delete',
|
|
928
|
+
headers: commonHeaders(env),
|
|
929
|
+
body: { agent_id: agentId },
|
|
930
|
+
});
|
|
931
|
+
printJson(res.body);
|
|
932
|
+
return exitFromStatus(res.statusCode);
|
|
933
|
+
}
|
|
934
|
+
|
|
935
|
+
function readJsonFromFileOrInline(value) {
|
|
936
|
+
if (!value) return null;
|
|
937
|
+
// Accept either a path to a .json file or a raw JSON string.
|
|
938
|
+
try {
|
|
939
|
+
return JSON.parse(value);
|
|
940
|
+
} catch {}
|
|
941
|
+
try {
|
|
942
|
+
const fs = require('node:fs');
|
|
943
|
+
const txt = fs.readFileSync(value, 'utf8');
|
|
944
|
+
return JSON.parse(txt);
|
|
945
|
+
} catch (err) {
|
|
946
|
+
throw new Error(`could not parse JSON from ${value}: ${err?.message || err}`);
|
|
947
|
+
}
|
|
948
|
+
}
|
|
949
|
+
|
|
950
|
+
export async function runServiceCreateCommand(args) {
|
|
951
|
+
const env = requireAgentEnv();
|
|
952
|
+
const name = getArg(args, 'name');
|
|
953
|
+
if (!name) { console.error('--name is required'); return 2; }
|
|
954
|
+
const description = getArg(args, 'description');
|
|
955
|
+
let contractSchema = null;
|
|
956
|
+
let endpointConfig = null;
|
|
957
|
+
try {
|
|
958
|
+
const contractRaw = getArg(args, 'contract');
|
|
959
|
+
if (contractRaw) contractSchema = readJsonFromFileOrInline(contractRaw);
|
|
960
|
+
const endpointRaw = getArg(args, 'endpoint');
|
|
961
|
+
if (endpointRaw) endpointConfig = readJsonFromFileOrInline(endpointRaw);
|
|
962
|
+
} catch (err) {
|
|
963
|
+
console.error(err.message);
|
|
964
|
+
return 2;
|
|
965
|
+
}
|
|
966
|
+
if (!endpointConfig) { console.error('--endpoint <path-or-json> is required'); return 2; }
|
|
967
|
+
const res = await daemonRequest({
|
|
968
|
+
method: 'POST', path: '/agent/service/create',
|
|
969
|
+
headers: commonHeaders(env),
|
|
970
|
+
body: { name, description, contract_schema: contractSchema, endpoint_config: endpointConfig },
|
|
971
|
+
});
|
|
972
|
+
printJson(res.body);
|
|
973
|
+
return exitFromStatus(res.statusCode);
|
|
974
|
+
}
|
|
975
|
+
|
|
976
|
+
export async function runServiceUpdateCommand(args) {
|
|
977
|
+
const env = requireAgentEnv();
|
|
978
|
+
const serviceId = getArg(args, 'service-id');
|
|
979
|
+
if (!serviceId) { console.error('--service-id is required'); return 2; }
|
|
980
|
+
const body = { service_id: serviceId };
|
|
981
|
+
if (getArg(args, 'description')) body.description = getArg(args, 'description');
|
|
982
|
+
if (getArg(args, 'contract')) body.contract_schema = readJsonFromFileOrInline(getArg(args, 'contract'));
|
|
983
|
+
if (getArg(args, 'endpoint')) body.endpoint_config = readJsonFromFileOrInline(getArg(args, 'endpoint'));
|
|
984
|
+
if (getArg(args, 'status')) body.status = getArg(args, 'status');
|
|
985
|
+
const res = await daemonRequest({
|
|
986
|
+
method: 'POST', path: '/agent/service/update',
|
|
987
|
+
headers: commonHeaders(env), body,
|
|
988
|
+
});
|
|
989
|
+
printJson(res.body);
|
|
990
|
+
return exitFromStatus(res.statusCode);
|
|
991
|
+
}
|
|
992
|
+
|
|
993
|
+
export async function runServiceDeleteCommand(args) {
|
|
994
|
+
const env = requireAgentEnv();
|
|
995
|
+
const serviceId = getArg(args, 'service-id');
|
|
996
|
+
if (!serviceId) { console.error('--service-id is required'); return 2; }
|
|
997
|
+
const res = await daemonRequest({
|
|
998
|
+
method: 'POST', path: '/agent/service/delete',
|
|
999
|
+
headers: commonHeaders(env),
|
|
1000
|
+
body: { service_id: serviceId },
|
|
1001
|
+
});
|
|
1002
|
+
printJson(res.body);
|
|
1003
|
+
return exitFromStatus(res.statusCode);
|
|
1004
|
+
}
|
|
1005
|
+
|
|
1006
|
+
export async function runServiceListCommand(args) {
|
|
1007
|
+
const env = requireAgentEnv();
|
|
1008
|
+
const res = await daemonRequest({
|
|
1009
|
+
method: 'GET', path: '/agent/service/list',
|
|
1010
|
+
headers: commonHeaders(env),
|
|
1011
|
+
});
|
|
1012
|
+
printJson(res.body);
|
|
1013
|
+
return exitFromStatus(res.statusCode);
|
|
1014
|
+
}
|
|
1015
|
+
|
|
1016
|
+
export async function runServiceInfoCommand(args) {
|
|
1017
|
+
const env = requireAgentEnv();
|
|
1018
|
+
const name = getArg(args, 'name');
|
|
1019
|
+
if (!name) { console.error('--name is required'); return 2; }
|
|
1020
|
+
const params = new URLSearchParams();
|
|
1021
|
+
params.set('name', name);
|
|
1022
|
+
const res = await daemonRequest({
|
|
1023
|
+
method: 'GET', path: `/agent/service/info?${params}`,
|
|
1024
|
+
headers: commonHeaders(env),
|
|
1025
|
+
});
|
|
1026
|
+
printJson(res.body);
|
|
1027
|
+
return exitFromStatus(res.statusCode);
|
|
1028
|
+
}
|
|
1029
|
+
|
|
1030
|
+
export async function runServiceCallCommand(args) {
|
|
1031
|
+
const env = requireAgentEnv();
|
|
1032
|
+
const name = getArg(args, 'name');
|
|
1033
|
+
if (!name) { console.error('--name is required'); return 2; }
|
|
1034
|
+
const inputText = await readStdin();
|
|
1035
|
+
let input = null;
|
|
1036
|
+
if (inputText && inputText.trim().length > 0) {
|
|
1037
|
+
try {
|
|
1038
|
+
input = JSON.parse(inputText);
|
|
1039
|
+
} catch (err) {
|
|
1040
|
+
console.error('stdin must be JSON for service call input');
|
|
1041
|
+
return 2;
|
|
1042
|
+
}
|
|
1043
|
+
}
|
|
1044
|
+
const res = await daemonRequest({
|
|
1045
|
+
method: 'POST', path: '/agent/service/call',
|
|
1046
|
+
headers: commonHeaders(env),
|
|
1047
|
+
body: { name, input },
|
|
1048
|
+
});
|
|
1049
|
+
printJson(res.body);
|
|
1050
|
+
return exitFromStatus(res.statusCode);
|
|
1051
|
+
}
|
|
1052
|
+
|
|
1053
|
+
export async function runBriefingGetCommand(args) {
|
|
1054
|
+
const env = requireAgentEnv();
|
|
1055
|
+
const id = args._[2] || getArg(args, 'id');
|
|
1056
|
+
if (!id) { console.error('briefing id required (positional or --id)'); return 2; }
|
|
1057
|
+
const params = new URLSearchParams();
|
|
1058
|
+
params.set('id', id);
|
|
1059
|
+
const res = await daemonRequest({
|
|
1060
|
+
method: 'GET',
|
|
1061
|
+
path: `/agent/briefing/get?${params}`,
|
|
1062
|
+
headers: commonHeaders(env),
|
|
1063
|
+
});
|
|
1064
|
+
printJson(res.body);
|
|
1065
|
+
return exitFromStatus(res.statusCode);
|
|
1066
|
+
}
|
|
1067
|
+
|
|
1068
|
+
export async function runBriefingPublishCommand(args) {
|
|
1069
|
+
const env = requireAgentEnv();
|
|
1070
|
+
const textArg = getArg(args, 'text');
|
|
1071
|
+
const attachPath = getArg(args, 'attach');
|
|
1072
|
+
const mode = String(getArg(args, 'mode') || 'info').trim().toLowerCase();
|
|
1073
|
+
if (!textArg) {
|
|
1074
|
+
console.error('--text "<short>" is required');
|
|
1075
|
+
return 2;
|
|
1076
|
+
}
|
|
1077
|
+
if (textArg.length > 140) {
|
|
1078
|
+
console.error('--text must be ≤140 chars');
|
|
1079
|
+
return 2;
|
|
1080
|
+
}
|
|
1081
|
+
if (!['info', 'approval'].includes(mode)) {
|
|
1082
|
+
console.error('--mode must be info or approval');
|
|
1083
|
+
return 2;
|
|
1084
|
+
}
|
|
1085
|
+
let attachmentAssetId = null;
|
|
1086
|
+
if (attachPath) {
|
|
1087
|
+
const contentType = inferContentType(String(attachPath));
|
|
1088
|
+
if (!contentType.startsWith('image/') && !contentType.startsWith('video/') && contentType !== 'text/html') {
|
|
1089
|
+
console.error('--attach must be an image, video, or HTML file');
|
|
1090
|
+
return 2;
|
|
1091
|
+
}
|
|
1092
|
+
const upload = await uploadFileViaDaemon(env, String(attachPath));
|
|
1093
|
+
if (!upload.ok) {
|
|
1094
|
+
console.error(`attachment upload failed for ${attachPath}: ${upload.error}`);
|
|
1095
|
+
return 1;
|
|
1096
|
+
}
|
|
1097
|
+
attachmentAssetId = upload.assetId;
|
|
1098
|
+
}
|
|
1099
|
+
const res = await daemonRequest({
|
|
1100
|
+
method: 'POST',
|
|
1101
|
+
path: '/agent/briefing/publish',
|
|
1102
|
+
headers: commonHeaders(env),
|
|
1103
|
+
body: {
|
|
1104
|
+
body_text: textArg,
|
|
1105
|
+
attachment_asset_id: attachmentAssetId,
|
|
1106
|
+
current_conversation_id: env.currentConversationId,
|
|
1107
|
+
response_mode: mode,
|
|
1108
|
+
},
|
|
1109
|
+
});
|
|
1110
|
+
printJson(res.body);
|
|
1111
|
+
return exitFromStatus(res.statusCode);
|
|
1112
|
+
}
|
|
1113
|
+
|
|
1114
|
+
export async function runCredentialRequestCommand(args) {
|
|
1115
|
+
const env = requireAgentEnv();
|
|
1116
|
+
const name = getArg(args, 'name');
|
|
1117
|
+
if (!name) { console.error('--name is required (e.g. GEMINI_API_KEY)'); return 2; }
|
|
1118
|
+
const description = getArg(args, 'description');
|
|
1119
|
+
const groupTarget = getArg(args, 'group') || getArg(args, 'workstream');
|
|
1120
|
+
const res = await daemonRequest({
|
|
1121
|
+
method: 'POST',
|
|
1122
|
+
path: '/agent/credential/request',
|
|
1123
|
+
headers: commonHeaders(env),
|
|
1124
|
+
body: {
|
|
1125
|
+
name,
|
|
1126
|
+
description,
|
|
1127
|
+
target: groupTarget,
|
|
1128
|
+
},
|
|
1129
|
+
});
|
|
1130
|
+
printJson(res.body);
|
|
1131
|
+
return exitFromStatus(res.statusCode);
|
|
1132
|
+
}
|
|
1133
|
+
|
|
1134
|
+
export async function runDashboardSetCommand(args) {
|
|
1135
|
+
const env = requireAgentEnv();
|
|
1136
|
+
const target = getArg(args, 'target');
|
|
1137
|
+
const conversationId = getArg(args, 'conversation-id');
|
|
1138
|
+
if (!target && !conversationId) {
|
|
1139
|
+
console.error('--target or --conversation-id is required');
|
|
1140
|
+
return 2;
|
|
1141
|
+
}
|
|
1142
|
+
// Body is read from stdin as JSON: { data_json?, html_template? }
|
|
1143
|
+
const stdinText = await readStdin();
|
|
1144
|
+
let payload = {};
|
|
1145
|
+
if (stdinText && stdinText.trim().length > 0) {
|
|
1146
|
+
try {
|
|
1147
|
+
payload = JSON.parse(stdinText);
|
|
1148
|
+
} catch (err) {
|
|
1149
|
+
console.error('stdin must be JSON: { data_json?, html_template? }');
|
|
1150
|
+
return 2;
|
|
1151
|
+
}
|
|
1152
|
+
}
|
|
1153
|
+
const res = await daemonRequest({
|
|
1154
|
+
method: 'POST',
|
|
1155
|
+
path: '/agent/dashboard/set',
|
|
1156
|
+
headers: commonHeaders(env),
|
|
1157
|
+
body: {
|
|
1158
|
+
target,
|
|
1159
|
+
conversation_id: conversationId,
|
|
1160
|
+
...(('data_json' in payload) ? { data_json: payload.data_json } : {}),
|
|
1161
|
+
...(('html_template' in payload) ? { html_template: payload.html_template } : {}),
|
|
1162
|
+
},
|
|
1163
|
+
});
|
|
1164
|
+
printJson(res.body);
|
|
1165
|
+
return exitFromStatus(res.statusCode);
|
|
1166
|
+
}
|
|
1167
|
+
|
|
1168
|
+
export async function runDashboardGetCommand(args) {
|
|
1169
|
+
const env = requireAgentEnv();
|
|
1170
|
+
const target = getArg(args, 'target');
|
|
1171
|
+
const conversationId = getArg(args, 'conversation-id');
|
|
1172
|
+
if (!target && !conversationId) {
|
|
1173
|
+
console.error('--target or --conversation-id is required');
|
|
1174
|
+
return 2;
|
|
1175
|
+
}
|
|
1176
|
+
const params = new URLSearchParams();
|
|
1177
|
+
if (target) params.set('target', target);
|
|
1178
|
+
if (conversationId) params.set('conversation_id', conversationId);
|
|
1179
|
+
const res = await daemonRequest({
|
|
1180
|
+
method: 'GET',
|
|
1181
|
+
path: `/agent/dashboard/get?${params}`,
|
|
1182
|
+
headers: commonHeaders(env),
|
|
1183
|
+
});
|
|
1184
|
+
printJson(res.body);
|
|
1185
|
+
return exitFromStatus(res.statusCode);
|
|
1186
|
+
}
|
|
1187
|
+
|
|
1188
|
+
export async function runWorkstreamCharterGetCommand(args) {
|
|
1189
|
+
const env = requireAgentEnv();
|
|
1190
|
+
const target = getArg(args, 'target');
|
|
1191
|
+
const conversationId = getArg(args, 'conversation-id') || env.currentConversationId;
|
|
1192
|
+
if (!target && !conversationId) {
|
|
1193
|
+
console.error('--target or --conversation-id is required outside a delivered conversation');
|
|
1194
|
+
return 2;
|
|
1195
|
+
}
|
|
1196
|
+
const params = new URLSearchParams();
|
|
1197
|
+
if (target) params.set('target', target);
|
|
1198
|
+
if (conversationId) params.set('conversation_id', conversationId);
|
|
1199
|
+
const res = await daemonRequest({
|
|
1200
|
+
method: 'GET',
|
|
1201
|
+
path: `/agent/workstream/charter/get?${params}`,
|
|
1202
|
+
headers: commonHeaders(env),
|
|
1203
|
+
});
|
|
1204
|
+
printJson(res.body);
|
|
1205
|
+
return exitFromStatus(res.statusCode);
|
|
1206
|
+
}
|
|
1207
|
+
|
|
1208
|
+
export async function runWorkstreamCharterSetCommand(args) {
|
|
1209
|
+
const env = requireAgentEnv();
|
|
1210
|
+
const target = getArg(args, 'target');
|
|
1211
|
+
const conversationId = getArg(args, 'conversation-id') || env.currentConversationId;
|
|
1212
|
+
if (!target && !conversationId) {
|
|
1213
|
+
console.error('--target or --conversation-id is required outside a delivered conversation');
|
|
1214
|
+
return 2;
|
|
1215
|
+
}
|
|
1216
|
+
// Body from stdin (heredoc / pipe). Empty input clears the charter.
|
|
1217
|
+
const charter = await readStdin();
|
|
1218
|
+
const res = await daemonRequest({
|
|
1219
|
+
method: 'POST',
|
|
1220
|
+
path: '/agent/workstream/charter/set',
|
|
1221
|
+
headers: commonHeaders(env),
|
|
1222
|
+
body: {
|
|
1223
|
+
target,
|
|
1224
|
+
conversation_id: conversationId,
|
|
1225
|
+
charter: charter ?? '',
|
|
1226
|
+
},
|
|
1227
|
+
});
|
|
1228
|
+
printJson(res.body);
|
|
1229
|
+
return exitFromStatus(res.statusCode);
|
|
1230
|
+
}
|
|
1231
|
+
|
|
758
1232
|
export async function runServerInfoCommand(args) {
|
|
759
1233
|
const env = requireAgentEnv();
|
|
760
1234
|
const params = new URLSearchParams();
|
|
@@ -770,12 +1244,18 @@ export async function runServerInfoCommand(args) {
|
|
|
770
1244
|
|
|
771
1245
|
export const AGENT_COMMAND_HELP = {
|
|
772
1246
|
message: `ticlawk message <send|read|check|search|react>
|
|
773
|
-
ticlawk message send --target "<target>" [--seen-up-to-seq N] [--reply-to <msg-id>]
|
|
1247
|
+
ticlawk message send --target "<target>" [--seen-up-to-seq N] [--reply-to <msg-id>] [--allow-cross-target] [--attach <file> ...] [--kind <kind>]
|
|
774
1248
|
Body is read from stdin (use <<'EOF' ... EOF for multiline).
|
|
1249
|
+
During a delivered turn, sending to a different conversation is blocked
|
|
1250
|
+
unless --allow-cross-target is passed deliberately.
|
|
1251
|
+
--kind <kind> tags this message via metadata.kind.
|
|
775
1252
|
Targets:
|
|
776
|
-
dm
|
|
777
|
-
|
|
778
|
-
#<group
|
|
1253
|
+
dm:<conversation-id> private message by conversation id
|
|
1254
|
+
dm:@<user> private message by user/agent handle
|
|
1255
|
+
#<group> group conversation by name or id
|
|
1256
|
+
#<group>:<msgid> replies under a top-level message in that group
|
|
1257
|
+
--attach <file> uploads a local file and attaches it to the message
|
|
1258
|
+
(repeatable; max 10 attachments per message).
|
|
779
1259
|
ticlawk message read --target "<target>" [--around <msg-id>] [--before-seq N] [--limit N]
|
|
780
1260
|
ticlawk message check [--target "<target>"]
|
|
781
1261
|
Non-blocking poll for new/unprocessed messages.
|
|
@@ -785,13 +1265,17 @@ export const AGENT_COMMAND_HELP = {
|
|
|
785
1265
|
Use sparingly: prefer acknowledgement/follow-up signals like 👀. Do not
|
|
786
1266
|
auto-react to every merge, deploy, or task completion with celebratory emoji.
|
|
787
1267
|
`,
|
|
788
|
-
profile: `ticlawk profile <show|update>
|
|
1268
|
+
profile: `ticlawk profile <list|current|use|show|update>
|
|
1269
|
+
ticlawk profile list
|
|
1270
|
+
ticlawk profile current
|
|
1271
|
+
ticlawk profile use ticlawk:<user-id>
|
|
1272
|
+
|
|
789
1273
|
ticlawk profile show [@handle | --id <agent-id>]
|
|
790
1274
|
ticlawk profile update [--display-name X] [--description Y] [--avatar-file path]
|
|
791
1275
|
`,
|
|
792
|
-
attachment: `ticlawk attachment <
|
|
793
|
-
|
|
794
|
-
|
|
1276
|
+
attachment: `ticlawk attachment view <asset-id> [--out <path>]
|
|
1277
|
+
Fetch metadata + signed URL for an existing asset. To send a file
|
|
1278
|
+
to a user, use \`ticlawk message send --attach <file>\` instead.
|
|
795
1279
|
`,
|
|
796
1280
|
reminder: `ticlawk reminder <schedule|list|snooze|update|cancel|log>
|
|
797
1281
|
ticlawk reminder schedule --title <t> (--fire-at <iso> | --in-seconds N | --in-minutes N) (--target "<target>" | --anchor-conversation-id <id>) [--anchor-message-id <id>]
|
|
@@ -806,23 +1290,109 @@ export const AGENT_COMMAND_HELP = {
|
|
|
806
1290
|
conversation and waking the owner agent via an explicit delivery.
|
|
807
1291
|
`,
|
|
808
1292
|
task: `ticlawk task <create|claim|unclaim|update|list>
|
|
809
|
-
ticlawk task create --target "<target>" [--title <t>]
|
|
810
|
-
Body is read from stdin. Creates a brand-new
|
|
811
|
-
|
|
1293
|
+
ticlawk task create --target "<target>" [--title <t>] [--assign-agent <agent-id>]
|
|
1294
|
+
Body is read from stdin. Creates a brand-new group task. Group admins
|
|
1295
|
+
may assign it immediately to an agent member with --assign-agent.
|
|
812
1296
|
ticlawk task claim --message-id <id> [--lease-seconds N]
|
|
813
1297
|
ticlawk task claim --number <N> --target "<target>" [--lease-seconds N]
|
|
814
1298
|
ticlawk task unclaim --task-id <id>
|
|
815
1299
|
ticlawk task update --task-id <id> --status <todo|in_progress|in_review|done|canceled>
|
|
816
|
-
|
|
1300
|
+
Only a group admin can set status=done. Other agents should set
|
|
1301
|
+
in_review and let the group's admin finalize. A group admin can also
|
|
1302
|
+
reopen another agent's in_review task to in_progress for redo.
|
|
1303
|
+
ticlawk task list [--target <target>|--conversation-id <id>]
|
|
1304
|
+
Default view is open tasks plus tasks owned by the caller. When the
|
|
1305
|
+
caller is a group admin and --target/--conversation-id scopes the
|
|
1306
|
+
request to that group, the list shows the full task board, including
|
|
1307
|
+
other agents' in_progress, in_review, and done tasks.
|
|
1308
|
+
`,
|
|
1309
|
+
workstream: `ticlawk workstream <create|delete|list|charter>
|
|
1310
|
+
Compatibility alias for group admin commands. Prefer \`ticlawk group ...\`
|
|
1311
|
+
for groups and \`ticlawk charter ...\` for conversation charters.
|
|
1312
|
+
`,
|
|
1313
|
+
charter: `ticlawk charter <get|set> [--target "<target>" | --conversation-id <id>]
|
|
1314
|
+
ticlawk charter get [--target "<target>" | --conversation-id <id>]
|
|
1315
|
+
Print the conversation charter. DM/group members can read.
|
|
1316
|
+
ticlawk charter set [--target "<target>" | --conversation-id <id>] # body via stdin
|
|
1317
|
+
Replace the conversation charter. DM member agents can write their DM
|
|
1318
|
+
charter; group writes require group admin/owner. Empty stdin clears it.
|
|
1319
|
+
During a delivered turn, omitting target/conversation-id uses the
|
|
1320
|
+
current conversation.
|
|
1321
|
+
`,
|
|
1322
|
+
service: `ticlawk service <create|update|delete|list|info|call>
|
|
1323
|
+
service create --name X --endpoint <path-or-json> [--description Y] [--contract <path-or-json>]
|
|
1324
|
+
Any agent. Register a callable service. --endpoint takes either a
|
|
1325
|
+
.json file path or a JSON string; same for --contract.
|
|
1326
|
+
endpoint_config shape: { url, method?, headers? }.
|
|
1327
|
+
service update --service-id <id> [--description Y] [--contract <json>] [--endpoint <json>] [--status <active|down|archived>]
|
|
1328
|
+
Any agent.
|
|
1329
|
+
service delete --service-id <id>
|
|
1330
|
+
Any agent. Soft-archives.
|
|
1331
|
+
service list
|
|
1332
|
+
Any agent. Returns active services (no endpoint_config).
|
|
1333
|
+
service info --name X
|
|
1334
|
+
Any agent. Returns contract_schema + description.
|
|
1335
|
+
service call --name X # input from stdin (JSON)
|
|
1336
|
+
Any agent. Backend proxies to endpoint_config.url. No retry.
|
|
1337
|
+
`,
|
|
1338
|
+
briefing: `ticlawk briefing <publish|get>
|
|
1339
|
+
briefing publish --text "..." [--mode info|approval] [--attach <image|video|html path>]
|
|
1340
|
+
Publish a briefing to the owner's Briefings. Allowed from DMs, or
|
|
1341
|
+
from groups where this agent is admin/owner.
|
|
1342
|
+
--text short plain text (≤140 chars)
|
|
1343
|
+
--mode owner response mode: info shows ack; approval shows approve
|
|
1344
|
+
--attach optional image, video, or HTML file when visual context matters
|
|
1345
|
+
Briefings are independent of chat — they do NOT appear in any DM
|
|
1346
|
+
message stream. Use this verb (not \`message send\`) for any status
|
|
1347
|
+
surface the owner consumes in Briefings.
|
|
1348
|
+
briefing get <id>
|
|
1349
|
+
Fetch a briefing including text and attachment metadata. Use this when a
|
|
1350
|
+
quote (metadata.quote.kind=briefing) points at a briefing whose
|
|
1351
|
+
full body you want to read.
|
|
1352
|
+
`,
|
|
1353
|
+
credential: `ticlawk credential request --name <ENV_VAR> [--description Y] [--group "#<group>"]
|
|
1354
|
+
Any agent. Pre-allocate a credential slot. Response includes a deep
|
|
1355
|
+
link the user opens in the mobile app (Settings → Credentials)
|
|
1356
|
+
to fill the value. Once filled, the daemon syncs it locally and
|
|
1357
|
+
injects it as an env var when spawning agents.
|
|
1358
|
+
`,
|
|
1359
|
+
dashboard: `ticlawk dashboard <set|get> (--target "<target>" | --conversation-id <id>)
|
|
1360
|
+
dashboard set (--target "<target>" | --conversation-id <id>) # body via stdin (JSON)
|
|
1361
|
+
Allowed from DMs, or from groups where this agent is admin/owner.
|
|
1362
|
+
stdin = { "data_json": ..., "html_template": "..." }.
|
|
1363
|
+
Either field may be omitted (leaves unchanged) or null (clears).
|
|
1364
|
+
dashboard get (--target "<target>" | --conversation-id <id>)
|
|
1365
|
+
Conversation members can read.
|
|
1366
|
+
`,
|
|
1367
|
+
agent: `ticlawk agent <list|create|delete>
|
|
1368
|
+
agent list
|
|
1369
|
+
Any agent. List all non-archived agents owned by the user, including
|
|
1370
|
+
descriptions, runtime/status, and group memberships.
|
|
1371
|
+
agent create --name X --runtime <claude_code|codex|opencode|openclaw|pi> [--description Y] [--display-name N] [--model M]
|
|
1372
|
+
Any agent. Pre-allocate an agent slot (status='unpaired'). User
|
|
1373
|
+
later pairs a runtime to fill it.
|
|
1374
|
+
agent delete --agent-id <id>
|
|
1375
|
+
Owner-only. Agent-facing deletion is disabled.
|
|
817
1376
|
`,
|
|
818
|
-
group: `ticlawk group <create|members>
|
|
1377
|
+
group: `ticlawk group <create|list|delete|charter|members>
|
|
819
1378
|
ticlawk group create --name <n> [--description <d>] [--member <agent-id> ...]
|
|
820
|
-
Agent self-creates a new group. The agent is added as
|
|
1379
|
+
Agent self-creates a new group. The agent is added as admin; the
|
|
821
1380
|
conversation owner is set to the user that owns this agent. Other
|
|
822
|
-
member agents must belong to the same user
|
|
823
|
-
|
|
824
|
-
ticlawk group
|
|
825
|
-
|
|
1381
|
+
member agents join as regular members and must belong to the same user
|
|
1382
|
+
(RLS enforces).
|
|
1383
|
+
ticlawk group list
|
|
1384
|
+
List group conversations the caller belongs to, including agent members,
|
|
1385
|
+
descriptions, charters, and dashboard presence.
|
|
1386
|
+
ticlawk group delete --target "#<group>"
|
|
1387
|
+
Group-admin only. Hard-delete the group (messages, members, deliveries).
|
|
1388
|
+
ticlawk group charter get (--target "#<group>" | --conversation-id <id>)
|
|
1389
|
+
Compatibility alias for \`ticlawk charter get\` on groups.
|
|
1390
|
+
ticlawk group charter set (--target "#<group>" | --conversation-id <id>) # body via stdin
|
|
1391
|
+
Compatibility alias for \`ticlawk charter set\` on groups. Group-admin only.
|
|
1392
|
+
ticlawk group members (--target "<target>" | --conversation-id <id>)
|
|
1393
|
+
ticlawk group members (--target "<target>" | --conversation-id <id>) --add <agent-id> [--add <agent-id> ...]
|
|
1394
|
+
ticlawk group members (--target "<target>" | --conversation-id <id>) --remove <agent-id>
|
|
1395
|
+
Listing requires membership; add/remove requires group admin.
|
|
826
1396
|
`,
|
|
827
1397
|
server: `ticlawk server info [--refresh]
|
|
828
1398
|
`,
|