ticlawk 0.1.16-dev.10 → 0.1.16-dev.12

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.
@@ -160,6 +160,13 @@ export async function runMessageSendCommand(args) {
160
160
  mediaAssetIds.push(upload.assetId);
161
161
  }
162
162
 
163
+ // Optional --kind <s> classifies the message via metadata.kind. The
164
+ // canonical user-facing value is 'report' (奏折 surface). Anything else
165
+ // is passed through as-is so we don't have to update this list to add
166
+ // new conventions.
167
+ const kind = getArg(args, 'kind');
168
+ const metadata = kind ? { kind } : undefined;
169
+
163
170
  const body = {
164
171
  target,
165
172
  conversation_id: conversationId,
@@ -167,6 +174,7 @@ export async function runMessageSendCommand(args) {
167
174
  seen_up_to_seq: getNumberArg(args, 'seen-up-to-seq'),
168
175
  reply_to_message_id: getArg(args, 'reply-to'),
169
176
  media_asset_ids: mediaAssetIds.length > 0 ? mediaAssetIds : undefined,
177
+ metadata,
170
178
  };
171
179
  const res = await daemonRequest({
172
180
  method: 'POST',
@@ -800,6 +808,338 @@ export async function runGroupMembersCommand(args) {
800
808
  return exitFromStatus(res.statusCode);
801
809
  }
802
810
 
811
+ export async function runWorkstreamCreateCommand(args) {
812
+ const env = requireAgentEnv();
813
+ const name = getArg(args, 'name');
814
+ if (!name) { console.error('--name is required'); return 2; }
815
+ const description = getArg(args, 'description');
816
+ const memberArgs = args.member;
817
+ const memberAgentIds = Array.isArray(memberArgs)
818
+ ? memberArgs
819
+ : memberArgs ? [memberArgs] : [];
820
+ // Optional charter from stdin when --charter is supplied with no value
821
+ let charter = null;
822
+ if (args.charter === true) {
823
+ charter = await readStdin();
824
+ } else if (typeof args.charter === 'string') {
825
+ charter = args.charter;
826
+ }
827
+ const res = await daemonRequest({
828
+ method: 'POST',
829
+ path: '/agent/workstream/create',
830
+ headers: commonHeaders(env),
831
+ body: {
832
+ name,
833
+ description,
834
+ charter,
835
+ member_agent_ids: memberAgentIds.map((s) => String(s).trim()).filter(Boolean),
836
+ },
837
+ });
838
+ printJson(res.body);
839
+ return exitFromStatus(res.statusCode);
840
+ }
841
+
842
+ export async function runWorkstreamDeleteCommand(args) {
843
+ const env = requireAgentEnv();
844
+ const target = getArg(args, 'target');
845
+ const conversationId = getArg(args, 'conversation-id');
846
+ if (!target && !conversationId) {
847
+ console.error('--target or --conversation-id is required');
848
+ return 2;
849
+ }
850
+ const res = await daemonRequest({
851
+ method: 'POST',
852
+ path: '/agent/workstream/delete',
853
+ headers: commonHeaders(env),
854
+ body: { target, conversation_id: conversationId },
855
+ });
856
+ printJson(res.body);
857
+ return exitFromStatus(res.statusCode);
858
+ }
859
+
860
+ export async function runWorkstreamListCommand(args) {
861
+ const env = requireAgentEnv();
862
+ const res = await daemonRequest({
863
+ method: 'GET',
864
+ path: '/agent/workstream/list',
865
+ headers: commonHeaders(env),
866
+ });
867
+ printJson(res.body);
868
+ return exitFromStatus(res.statusCode);
869
+ }
870
+
871
+ export async function runAgentCreateCommand(args) {
872
+ const env = requireAgentEnv();
873
+ const name = getArg(args, 'name');
874
+ const runtime = getArg(args, 'runtime');
875
+ if (!name) { console.error('--name is required'); return 2; }
876
+ if (!runtime) { console.error('--runtime is required'); return 2; }
877
+ const res = await daemonRequest({
878
+ method: 'POST',
879
+ path: '/agent/agent/create',
880
+ headers: commonHeaders(env),
881
+ body: {
882
+ name,
883
+ runtime,
884
+ description: getArg(args, 'description'),
885
+ display_name: getArg(args, 'display-name'),
886
+ model: getArg(args, 'model'),
887
+ },
888
+ });
889
+ printJson(res.body);
890
+ return exitFromStatus(res.statusCode);
891
+ }
892
+
893
+ export async function runAgentDeleteCommand(args) {
894
+ const env = requireAgentEnv();
895
+ const agentId = getArg(args, 'agent-id');
896
+ if (!agentId) { console.error('--agent-id is required'); return 2; }
897
+ const res = await daemonRequest({
898
+ method: 'POST',
899
+ path: '/agent/agent/delete',
900
+ headers: commonHeaders(env),
901
+ body: { agent_id: agentId },
902
+ });
903
+ printJson(res.body);
904
+ return exitFromStatus(res.statusCode);
905
+ }
906
+
907
+ function readJsonFromFileOrInline(value) {
908
+ if (!value) return null;
909
+ // Accept either a path to a .json file or a raw JSON string.
910
+ try {
911
+ return JSON.parse(value);
912
+ } catch {}
913
+ try {
914
+ const fs = require('node:fs');
915
+ const txt = fs.readFileSync(value, 'utf8');
916
+ return JSON.parse(txt);
917
+ } catch (err) {
918
+ throw new Error(`could not parse JSON from ${value}: ${err?.message || err}`);
919
+ }
920
+ }
921
+
922
+ export async function runServiceCreateCommand(args) {
923
+ const env = requireAgentEnv();
924
+ const name = getArg(args, 'name');
925
+ if (!name) { console.error('--name is required'); return 2; }
926
+ const description = getArg(args, 'description');
927
+ let contractSchema = null;
928
+ let endpointConfig = null;
929
+ try {
930
+ const contractRaw = getArg(args, 'contract');
931
+ if (contractRaw) contractSchema = readJsonFromFileOrInline(contractRaw);
932
+ const endpointRaw = getArg(args, 'endpoint');
933
+ if (endpointRaw) endpointConfig = readJsonFromFileOrInline(endpointRaw);
934
+ } catch (err) {
935
+ console.error(err.message);
936
+ return 2;
937
+ }
938
+ if (!endpointConfig) { console.error('--endpoint <path-or-json> is required'); return 2; }
939
+ const res = await daemonRequest({
940
+ method: 'POST', path: '/agent/service/create',
941
+ headers: commonHeaders(env),
942
+ body: { name, description, contract_schema: contractSchema, endpoint_config: endpointConfig },
943
+ });
944
+ printJson(res.body);
945
+ return exitFromStatus(res.statusCode);
946
+ }
947
+
948
+ export async function runServiceUpdateCommand(args) {
949
+ const env = requireAgentEnv();
950
+ const serviceId = getArg(args, 'service-id');
951
+ if (!serviceId) { console.error('--service-id is required'); return 2; }
952
+ const body = { service_id: serviceId };
953
+ if (getArg(args, 'description')) body.description = getArg(args, 'description');
954
+ if (getArg(args, 'contract')) body.contract_schema = readJsonFromFileOrInline(getArg(args, 'contract'));
955
+ if (getArg(args, 'endpoint')) body.endpoint_config = readJsonFromFileOrInline(getArg(args, 'endpoint'));
956
+ if (getArg(args, 'status')) body.status = getArg(args, 'status');
957
+ const res = await daemonRequest({
958
+ method: 'POST', path: '/agent/service/update',
959
+ headers: commonHeaders(env), body,
960
+ });
961
+ printJson(res.body);
962
+ return exitFromStatus(res.statusCode);
963
+ }
964
+
965
+ export async function runServiceDeleteCommand(args) {
966
+ const env = requireAgentEnv();
967
+ const serviceId = getArg(args, 'service-id');
968
+ if (!serviceId) { console.error('--service-id is required'); return 2; }
969
+ const res = await daemonRequest({
970
+ method: 'POST', path: '/agent/service/delete',
971
+ headers: commonHeaders(env),
972
+ body: { service_id: serviceId },
973
+ });
974
+ printJson(res.body);
975
+ return exitFromStatus(res.statusCode);
976
+ }
977
+
978
+ export async function runServiceListCommand(args) {
979
+ const env = requireAgentEnv();
980
+ const res = await daemonRequest({
981
+ method: 'GET', path: '/agent/service/list',
982
+ headers: commonHeaders(env),
983
+ });
984
+ printJson(res.body);
985
+ return exitFromStatus(res.statusCode);
986
+ }
987
+
988
+ export async function runServiceInfoCommand(args) {
989
+ const env = requireAgentEnv();
990
+ const name = getArg(args, 'name');
991
+ if (!name) { console.error('--name is required'); return 2; }
992
+ const params = new URLSearchParams();
993
+ params.set('name', name);
994
+ const res = await daemonRequest({
995
+ method: 'GET', path: `/agent/service/info?${params}`,
996
+ headers: commonHeaders(env),
997
+ });
998
+ printJson(res.body);
999
+ return exitFromStatus(res.statusCode);
1000
+ }
1001
+
1002
+ export async function runServiceCallCommand(args) {
1003
+ const env = requireAgentEnv();
1004
+ const name = getArg(args, 'name');
1005
+ if (!name) { console.error('--name is required'); return 2; }
1006
+ const inputText = await readStdin();
1007
+ let input = null;
1008
+ if (inputText && inputText.trim().length > 0) {
1009
+ try {
1010
+ input = JSON.parse(inputText);
1011
+ } catch (err) {
1012
+ console.error('stdin must be JSON for service call input');
1013
+ return 2;
1014
+ }
1015
+ }
1016
+ const res = await daemonRequest({
1017
+ method: 'POST', path: '/agent/service/call',
1018
+ headers: commonHeaders(env),
1019
+ body: { name, input },
1020
+ });
1021
+ printJson(res.body);
1022
+ return exitFromStatus(res.statusCode);
1023
+ }
1024
+
1025
+ export async function runCredentialRequestCommand(args) {
1026
+ const env = requireAgentEnv();
1027
+ const name = getArg(args, 'name');
1028
+ if (!name) { console.error('--name is required (e.g. GEMINI_API_KEY)'); return 2; }
1029
+ const description = getArg(args, 'description');
1030
+ const workstream = getArg(args, 'workstream');
1031
+ const res = await daemonRequest({
1032
+ method: 'POST',
1033
+ path: '/agent/credential/request',
1034
+ headers: commonHeaders(env),
1035
+ body: {
1036
+ name,
1037
+ description,
1038
+ target: workstream,
1039
+ },
1040
+ });
1041
+ printJson(res.body);
1042
+ return exitFromStatus(res.statusCode);
1043
+ }
1044
+
1045
+ export async function runDashboardSetCommand(args) {
1046
+ const env = requireAgentEnv();
1047
+ const target = getArg(args, 'target');
1048
+ const conversationId = getArg(args, 'conversation-id');
1049
+ if (!target && !conversationId) {
1050
+ console.error('--target or --conversation-id is required');
1051
+ return 2;
1052
+ }
1053
+ // Body is read from stdin as JSON: { data_json?, html_template? }
1054
+ const stdinText = await readStdin();
1055
+ let payload = {};
1056
+ if (stdinText && stdinText.trim().length > 0) {
1057
+ try {
1058
+ payload = JSON.parse(stdinText);
1059
+ } catch (err) {
1060
+ console.error('stdin must be JSON: { data_json?, html_template? }');
1061
+ return 2;
1062
+ }
1063
+ }
1064
+ const res = await daemonRequest({
1065
+ method: 'POST',
1066
+ path: '/agent/dashboard/set',
1067
+ headers: commonHeaders(env),
1068
+ body: {
1069
+ target,
1070
+ conversation_id: conversationId,
1071
+ ...(('data_json' in payload) ? { data_json: payload.data_json } : {}),
1072
+ ...(('html_template' in payload) ? { html_template: payload.html_template } : {}),
1073
+ },
1074
+ });
1075
+ printJson(res.body);
1076
+ return exitFromStatus(res.statusCode);
1077
+ }
1078
+
1079
+ export async function runDashboardGetCommand(args) {
1080
+ const env = requireAgentEnv();
1081
+ const target = getArg(args, 'target');
1082
+ const conversationId = getArg(args, 'conversation-id');
1083
+ if (!target && !conversationId) {
1084
+ console.error('--target or --conversation-id is required');
1085
+ return 2;
1086
+ }
1087
+ const params = new URLSearchParams();
1088
+ if (target) params.set('target', target);
1089
+ if (conversationId) params.set('conversation_id', conversationId);
1090
+ const res = await daemonRequest({
1091
+ method: 'GET',
1092
+ path: `/agent/dashboard/get?${params}`,
1093
+ headers: commonHeaders(env),
1094
+ });
1095
+ printJson(res.body);
1096
+ return exitFromStatus(res.statusCode);
1097
+ }
1098
+
1099
+ export async function runWorkstreamCharterGetCommand(args) {
1100
+ const env = requireAgentEnv();
1101
+ const target = getArg(args, 'target');
1102
+ const conversationId = getArg(args, 'conversation-id');
1103
+ if (!target && !conversationId) {
1104
+ console.error('--target or --conversation-id is required');
1105
+ return 2;
1106
+ }
1107
+ const params = new URLSearchParams();
1108
+ if (target) params.set('target', target);
1109
+ if (conversationId) params.set('conversation_id', conversationId);
1110
+ const res = await daemonRequest({
1111
+ method: 'GET',
1112
+ path: `/agent/workstream/charter/get?${params}`,
1113
+ headers: commonHeaders(env),
1114
+ });
1115
+ printJson(res.body);
1116
+ return exitFromStatus(res.statusCode);
1117
+ }
1118
+
1119
+ export async function runWorkstreamCharterSetCommand(args) {
1120
+ const env = requireAgentEnv();
1121
+ const target = getArg(args, 'target');
1122
+ const conversationId = getArg(args, 'conversation-id');
1123
+ if (!target && !conversationId) {
1124
+ console.error('--target or --conversation-id is required');
1125
+ return 2;
1126
+ }
1127
+ // Body from stdin (heredoc / pipe). Empty input clears the charter.
1128
+ const charter = await readStdin();
1129
+ const res = await daemonRequest({
1130
+ method: 'POST',
1131
+ path: '/agent/workstream/charter/set',
1132
+ headers: commonHeaders(env),
1133
+ body: {
1134
+ target,
1135
+ conversation_id: conversationId,
1136
+ charter: charter ?? '',
1137
+ },
1138
+ });
1139
+ printJson(res.body);
1140
+ return exitFromStatus(res.statusCode);
1141
+ }
1142
+
803
1143
  export async function runServerInfoCommand(args) {
804
1144
  const env = requireAgentEnv();
805
1145
  const params = new URLSearchParams();
@@ -815,8 +1155,11 @@ export async function runServerInfoCommand(args) {
815
1155
 
816
1156
  export const AGENT_COMMAND_HELP = {
817
1157
  message: `ticlawk message <send|read|check|search|react>
818
- ticlawk message send --target "<target>" [--seen-up-to-seq N] [--reply-to <msg-id>] [--attach <file> ...]
1158
+ ticlawk message send --target "<target>" [--seen-up-to-seq N] [--reply-to <msg-id>] [--attach <file> ...] [--kind <kind>]
819
1159
  Body is read from stdin (use <<'EOF' ... EOF for multiline).
1160
+ --kind <kind> tags this message via metadata.kind. The CoS uses
1161
+ --kind report to publish a 奏折 / status report that the Office
1162
+ tab can list separately.
820
1163
  Targets:
821
1164
  dm:@<user> private message
822
1165
  #<group> group conversation
@@ -861,6 +1204,59 @@ export const AGENT_COMMAND_HELP = {
861
1204
  ticlawk task unclaim --task-id <id>
862
1205
  ticlawk task update --task-id <id> --status <todo|in_progress|in_review|done|canceled>
863
1206
  ticlawk task list [--target <target>]
1207
+ `,
1208
+ workstream: `ticlawk workstream <create|delete|list|charter>
1209
+ workstream create --name X [--description Y] [--member <agent-id> ...] [--charter [<text>]]
1210
+ CoS-only. Create a managed group conversation. If --charter has no
1211
+ value, body is read from stdin (heredoc / pipe). Empty stdin = no
1212
+ charter. Members are joined alongside CoS.
1213
+ workstream delete --target "#<group>"
1214
+ CoS-only. Hard-delete the workstream (messages, members, deliveries
1215
+ cascade). Use carefully.
1216
+ workstream list
1217
+ List all workstreams (group conversations) owned by your user.
1218
+ workstream charter get --target "#<group>"
1219
+ Print the workstream's charter (CoS-authored markdown).
1220
+ workstream charter set --target "#<group>" # body via stdin
1221
+ Replace the workstream charter. CoS-only — non-CoS callers get 403.
1222
+ Empty stdin clears the charter. Hard cap 4096 chars (DB-enforced).
1223
+ `,
1224
+ service: `ticlawk service <create|update|delete|list|info|call>
1225
+ service create --name X --endpoint <path-or-json> [--description Y] [--contract <path-or-json>]
1226
+ CoS-only. Register a callable service. --endpoint takes either a
1227
+ .json file path or a JSON string; same for --contract.
1228
+ endpoint_config shape: { url, method?, headers? }.
1229
+ service update --service-id <id> [--description Y] [--contract <json>] [--endpoint <json>] [--status <active|down|archived>]
1230
+ CoS-only.
1231
+ service delete --service-id <id>
1232
+ CoS-only. Soft-archives.
1233
+ service list
1234
+ Any agent. Returns active services (no endpoint_config).
1235
+ service info --name X
1236
+ Any agent. Returns contract_schema + description.
1237
+ service call --name X # input from stdin (JSON)
1238
+ Any agent. Backend proxies to endpoint_config.url. No retry.
1239
+ `,
1240
+ credential: `ticlawk credential request --name <ENV_VAR> [--description Y] [--workstream "#<ws>"]
1241
+ CoS-only. Pre-allocate a credential slot. Response includes a deep
1242
+ link the user opens in the mobile app (Settings → Connections) to
1243
+ fill the value. Once filled, the daemon injects it as an env var
1244
+ when spawning agents.
1245
+ `,
1246
+ dashboard: `ticlawk dashboard <set|get> --target "#<workstream>"
1247
+ dashboard set --target "#<workstream>" # body via stdin (JSON)
1248
+ CoS-only. stdin = { "data_json": ..., "html_template": "..." }.
1249
+ Either field may be omitted (leaves unchanged) or null (clears).
1250
+ dashboard get --target "#<workstream>"
1251
+ Any member of the workstream can read.
1252
+ `,
1253
+ agent: `ticlawk agent <create|delete>
1254
+ agent create --name X --runtime <claude_code|codex|opencode|openclaw|pi> [--description Y] [--display-name N] [--model M]
1255
+ CoS-only. Pre-allocate an agent slot (status='unpaired'). User
1256
+ later pairs a runtime to fill it.
1257
+ agent delete --agent-id <id>
1258
+ CoS-only. Soft-delete the agent (status='archived'). Cannot archive
1259
+ self.
864
1260
  `,
865
1261
  group: `ticlawk group <create|members>
866
1262
  ticlawk group create --name <n> [--description <d>] [--member <agent-id> ...]